Repository navigation
Conversation
Parse workspace paths from MCP tool output and arguments so glob-based rules attach when code is inspected through an MCP server instead of the native read tool. Handles key/value fields, grep-style rows, JSON payloads, and tool arguments for any language.
frap129
left a comment
There was a problem hiding this comment.
Line numbers refer to pr-93 for changed files and main for docs.
Review: request changes
Thanks for the PR. The windowsHide fix (src/detection/git-branch.ts:19) and the Unicode keyword boundary (src/rules/rule-filter.ts:84-86, tests at src/index.rules.test.ts:857-868) are useful. The MCP observation change is the blocker.
Blocking: this reverses the File-observation trust boundary
The repo deliberately narrowed file matching in #38/#58:
- #58 acceptance criterion 2: “Grep, Glob, shell, custom and MCP tools contribute neither paths nor content, whether successful or failed.”
- #38 story 30: “I want Grep, Glob, shell, custom, MCP, and other unsupported tools excluded.”
- #38 implementation decisions: “Only successful Read, Write, Edit, Apply Patch, and LSP events contribute observations.”
normalizeObservations now calls mcpObservations before the native-tool gate (src/session/file-observation.ts:361-365), and mcpObservations admits MCP paths/content (src/session/file-observation.ts:320-358). This is not a missing case; it is the opposite of the settled design. To land it, we need a new issue/spec that explicitly changes #38/#58 and the documentation. Otherwise I would split this PR down to the Windows and keyword fixes.
If the decision is revisited, these need to be fixed:
-
Make MCP observations path-only.
src/session/file-observation.ts:358sets:return [...paths].slice(0, 5).map(path => ({ path, tool, content: output }));
The contract at
src/session/file-observation.ts:1-3sayscontentis “that file's contribution text.”src/rules/rule-filter.ts:125-127then appliescontentMatchesLiteralsto that content. A multi-file MCP result now gives every path the entire result, soglobs: src/b.goplusfileContains: TODOcan match becausesrc/a.gocontainedTODO. The goal here is glob matching, so usecontent: ''unless per-path text can be derived. The new tests currently assert the wrong contract atsrc/session/file-observation.test.ts:158and:301. -
Use real MCP tool identity.
isMcpFileToolatsrc/session/file-observation.ts:188-195accepts everymcp__*tool (:189) and any non-denylisted name containingread,find,get,list,info,search, etc. (:191-194). A customrepo_searchorget_confignow contributes observations, which #58 explicitly excludes. Prefer the connected MCP server/tool data already available to the runtime (src/detection/mcp-tools.ts) over a name heuristic. -
Only use argument paths when the output has no paths.
src/session/file-observation.ts:334-335callsargPaths(event.args)unconditionally; the walker is at:272-295. The PR body says arguments are used “when a server accepts a target path and returns only the file content.” As written, an argument path can be paired with another file's output, and a successful no-results search with a file argument still creates an observation. -
Harden the parser or narrow its accepted forms.
linePathatsrc/session/file-observation.ts:237-249accepts prose:linePath('Found in src/a.ts:10')returnsFound in src/a.ts, which**/*.tsmatches.IGNORED_PATH_PREFIXESatsrc/session/file-observation.ts:206, used at:304, only rejects.git/andnode_modules/at the start, so./node_modules/x.tsandsrc/node_modules/x.tspass.- The version check at
src/session/file-observation.ts:310-315letsdocs/1.2.3andv1.2.3through.
-
Update the docs.
README.md:112,:246,:290,:568, and:704still describe only live Read, Write, Edit, Apply Patch, and LSP as sources.docs/rules.md:72and:285say the same.CONTEXT.md:14-16defines a File observation as the path and file text one event exposes. This is a hard violation of.opencode/rules/11-readme-and-doc-sync.md:19. -
Add an end-to-end test. The new coverage is confined to
src/session/file-observation.test.ts:139-304. Nothing drives an MCPtool.execute.afterevent through the runtime and asserts that a glob rule is admitted. The existing native/excluded-tool test is atsrc/index.runtime.test.ts:549; add the MCP case alongside it.
Other issues
-
-> pathis claimed but not parsed. The PR body lists-> path, butsrc/session/file-observation.ts:238uses:let rest = line.trim().replace(/^(?:[-*>]|→|->)\s*/, '');
The
[-*>]alternative consumes the leading-before->is tried, leaving> path.normalizePathCandidatethen rejects>viaILLEGAL_PATH_CHARSat:207/:307. Reorder the alternatives or use/^(?:->|→|[-*>])\s*/. -
Keyword fallback.
src/rules/rule-filter.ts:84-89falls back tolowerPrompt.includes(lowerKeyword)when the regex constructor throws. That would match mid-word and contradict the leading-boundary tests atsrc/index.rules.test.ts:857-868. It is unreachable on ES2022; remove it or use a boundary-preserving fallback. -
windowsHideis untested.src/detection/git-branch.ts:19adds the option, butsrc/detection/git-branch.test.ts:27-39only assertscwd,timeout, andkillSignal.
Nits
collectJsonPaths(src/session/file-observation.ts:252-270) andargPaths(:272-295) are the same recursive walker with a different sink and depth limit; one shared helper would remove the duplication.- The extension pattern
\.[A-Za-z0-9_-]{1,12}is repeated atsrc/session/file-observation.ts:204,:241, and:248; make it a named constant.
Verdict: request changes. The Windows and keyword commits look good and can land separately; the MCP observation feature needs a spec decision before code review can approve it.
Summary
globs-based rules attach fromFileObservationrecords. Today those recordsare only produced by the native file-handling tools (
read,write,edit,apply_patch,lsp).When an agent reads or searches the workspace through an MCP server (semantic
indexer, ripgrep wrapper, language server, filesystem or memory server), the
returned paths never become observations, so matching rules never attach.
This PR adds a language-agnostic observation normalizer for MCP tool results.
Native tool behavior is unchanged.
Implementation
MCP tool detection
mcp__<server>__<tool>.<server>_<tool>naming (for example1c-index_read_method),matched by a whole segment from
read,find,search,lookup,inspect,info,get,grep,list,scan,query,glob.Path extraction
path=,file=,file_path=,filePath=,relativePath=,module_path=,filename=; quoted values, spaces, andnon-ASCII/Cyrillic paths.
- src/a.go:10,src/a.go:10:5,→ path,-> path, and bare path lists.resultsoritems.file content.
Normalization
/..git/,node_modules/, wildcard masks, and version-like tokenssuch as
1.2.3are rejected.Also in this PR
windowsHide: truewhen resolving the git branch, so thebackground service does not flash a console window on every dispatch.
\b, soCyrillic and other non-ASCII keywords match.
Compatibility
read/write/edit/apply_patch/lspobservations are untouched.parsed; no extra filesystem access.
Tests
Added coverage in
src/session/file-observation.test.tsfor TypeScript,Python, Go, Rust, Vue, and 1C paths with spaces/Cyrillic names, plus
grep-style rows, JSON payloads, argument fallback, URL/noise rejection, and
the 5-path cap.
Targeted run:
Result: 137 passed (16 file-observation, 110 index.rules, 11 git-branch).
The full suite still reports 5 pre-existing Windows-only failures in
src/config/plugin-options.test.ts,tui/data/rules.test.ts, andsrc/index.runtime.test.ts(POSIX path expectations, file permissions, and ashell side-effect marker). They are unrelated to this change.
Commits
feat(mcp): observe file paths from MCP tool results— main change.fix(windows): hide git subprocess console window— no console flashes.fix(keywords): support non-ASCII keywords— Unicode keyword matching.