diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b279b0e1..566f52bc5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ - GateGuard keys checked files by canonical path, resolving relative targets against the tool `cwd`, so `a.py`, `./a.py`, and the absolute path (or differently cased and separated Windows spellings) share one first touch. Keys from earlier state files are still honoured. - GateGuard gates a MultiEdit call as its `tool_input.file_path` when its entries do not name their own path, which is the tool's own shape; such calls were never gated before, including on sensitive files. - GateGuard gates NotebookEdit as a first-touch edit of its `notebook_path`; notebooks were never gated before. +- GateGuard prior-search credit no longer counts a search that never saw the target. File-type filters (`rg -t`/`-T`/`--type`/`--type-not`, `fd -e`/`--extension`, the Grep tool's `type`), the Glob tool's pattern, shell glob operands, and git pathspecs now filter like include globs, so `rg -t py tokenizer src` or `Glob **/tokenizer.py` no longer credits `tokenizer.js`. A search confined to the target's own file name (`Glob **/tokenizer.js`, `find -name tokenizer.js`, Grep `glob: 'tokenizer.js'`) is a lookup and never credits it. PowerShell searches keep backslashes as path separators. +- GateGuard no longer passes an edit as comment-only when its text contains U+2028, U+2029, or U+0085, which end a JavaScript or C# line comment. ## 2.2.3 - 2026-10-01 diff --git a/docs/gateguard/change-profile.md b/docs/gateguard/change-profile.md index 1bb8d2c1c..d686668a6 100644 --- a/docs/gateguard/change-profile.md +++ b/docs/gateguard/change-profile.md @@ -139,6 +139,9 @@ Anything the lexer cannot read with confidence makes the entry non-trivial: - a line comment ending in `\` (it continues onto the next line in C and Make), the trigraph `??/`, a `/*` inside a block comment, and an unterminated block comment or string; +- U+2028, U+2029 or U+0085 anywhere in the text: JavaScript ends a line at + the first two and C# at all three, so code after one inside a `//` comment + runs, while the lexer splits lines only at `\n` and `\r`; - a code line ending in a line continuation `\` (trailing blanks included): a C macro, a Python explicit continuation or a string continued onto the next line joins that line, so adding or removing a comment-only line after diff --git a/docs/gateguard/design-notes.md b/docs/gateguard/design-notes.md index 5809dc9aa..aaa4a0093 100644 --- a/docs/gateguard/design-notes.md +++ b/docs/gateguard/design-notes.md @@ -31,7 +31,8 @@ and trivial edits) is in [change-profile.md](change-profile.md). [Stdin is not a tree search](#stdin-is-not-a-tree-search), [Search scope](#search-scope), [Stem matching](#stem-matching), [Test stems](#test-stems), [Search filters](#search-filters), - [Include filters](#include-filters), + [Include filters](#include-filters), [Type filters](#type-filters), + [Path patterns](#path-patterns), [Glob matching without RegExp](#glob-matching-without-regexp), [Flag parsing](#flag-parsing), [PowerShell parameter binding](#powershell-parameter-binding). @@ -280,7 +281,12 @@ evidence. Each search offers stem texts, candidate directories, the scope the target must lie in, and path operands. The target must lie inside the scope. A search whose operands are all the target itself read the target rather than searched for it, -and gives nothing. A `Glob` without glob characters is a single-file lookup: it +and gives nothing. The same holds for a search confined to the target's own +file name: when every include glob, or the Glob pattern, or every path pattern +ends in the target's file name with no glob characters (`**/tokenizer.js`, +`find -name tokenizer.js`, `rg -g tokenizer.js`), the search could only find +the target, so its stem never credits; the closest-miss note says it was a +lookup. A `Glob` without glob characters is a single-file lookup: it names no directory. A `Glob` with an empty literal prefix and no explicit path names only the implicit cwd, so it gives no directory credit. Directory evidence from shell operands must name a directory that exists now. @@ -349,6 +355,30 @@ leading `**/` lets it start at any directory; the other tools match names only, so such a glob admits nothing. An include glob that is malformed, past the matching bounds, or one of more than 32 filters admits nothing. +## Type filters + +A file-type filter is an include or exclusion in another spelling, so it +filters the same way. `rg -t`/`--type` adds the type's globs as includes and +`rg -T`/`--type-not` adds them as exclusions, using ripgrep 14.1's built-in +globs for common languages. A type name outside that table, `--type-add` and +`--type-clear` redefine what a type means, so they make the search an +unreadable filter that never credits. `fd -e`/`--extension` is an include of +`*.`. The `Grep` tool's `type` parameter is read like `rg -t`. + +## Path patterns + +A glob in a path argument limits the files a search sees just as an include +does, so it must match the target for the stem to count. The `Glob` tool's +pattern is matched against the target's path below its root, with `*` inside +one directory and `**` across directories. A shell path operand with glob +characters (`rg x src/*.py`) is expanded by the shell before the search runs; +it admits the target when it matches the target or a directory above it, +since a matched directory is searched recursively. A git pathspec +(`git grep x -- '*.py'`) matches the same way, except that its `*` also +matches `/`. Literal operands keep their scope rule, and one operand that +admits the target is enough. PowerShell escapes with a backtick, so a +PowerShell search is tokenized with its backslashes kept as path separators. + ## Glob matching without RegExp Filter globs are matched by dynamic programming over glob tokens, never by a @@ -366,7 +396,8 @@ Only path operands can scope a search: - for grep-family tools, `rg`, `git grep`, `Select-String` and `fd`, the first positional is the pattern unless `-e`/`-f` (grep family, `rg`, `git grep` only; fd's `-e` is an extension) supplied it; -- fd's `-E/--exclude` takes a value; rg's `-r/--replace` takes a value. +- fd's `-E/--exclude` and `--extension` take a value; rg's `-r/--replace`, + `--type-not`, `--type-add` and `--type-clear` take a value. ## PowerShell parameter binding diff --git a/manifests/context-packs/skill-triggers@1.json b/manifests/context-packs/skill-triggers@1.json index ed3be0d40..e4c5c5198 100644 --- a/manifests/context-packs/skill-triggers@1.json +++ b/manifests/context-packs/skill-triggers@1.json @@ -10,7 +10,7 @@ "id": "hand-seeded", "source": "manual-curation-pending-regeneration" }, - "registryDigest": "16549b53055bf5c106e51ca7fb2fe68f8a705b5f6bad40d388349320caedfa63", + "registryDigest": "13486e7747e81dcc199ac6475ca42089fcc02457e7a55c86a8fe94cf0bd41fce", "schemaVersion": 1, "triggers": { "skill:agent-runtime-gateway-smoke-test": [ diff --git a/skills/gateguard/SKILL.md b/skills/gateguard/SKILL.md index e6ebcc1f7..55583bb97 100644 --- a/skills/gateguard/SKILL.md +++ b/skills/gateguard/SKILL.md @@ -368,10 +368,21 @@ An Edit, Write, NotebookEdit, or MultiEdit entry is allowed, with an the hook cannot see, so such a segment never credits. File-name globs that restrict a search (Grep `glob: '*.py'`, `rg -g '*.py'`, `grep --include`, `find -name`, `tree -P`, `-Include`/`-Filter`) must match the target's file - name for its stem to count. + name for its stem to count. File types filter the same way: `rg -t`/ + `--type` and the Grep `type` include the type's globs, `rg -T`/`--type-not` + exclude them, and `fd -e`/`--extension` includes `*.`, so + `rg -t py tokenizer src` never credits `tokenizer.js`. An unknown type name, + `--type-add`, or `--type-clear` makes the segment never credit. +- **Lookups are not searches:** a search confined to the target's own file + name — every include glob, the Glob pattern, or every path pattern ends in + that exact name (`Glob **/tokenizer.js`, `find -name tokenizer.js`, + `rg -g tokenizer.js`, Grep `glob: 'tokenizer.js'`) — could only find the + target, so it never credits it. `Glob **/tokenizer.*` still does. - **Scope:** `Glob`, `Grep`, and `LS` only credit a target strictly inside the directory they searched (their `path`, else the tool `cwd`, plus a Glob - pattern's literal leading directories). A Glob without glob characters is a + pattern's literal leading directories). A Glob pattern is a filter too: it + must match the target's path for the stem to count, so `Glob **/tokenizer.py` + never credits `tokenizer.js`. A Glob without glob characters is a one-file lookup: it names no directory and credits only files directly in its directory, never the looked-up file itself. A search whose path is the target is not a search. - **Shell segments:** a segment without a path operand counts only when it is @@ -384,7 +395,8 @@ An Edit, Write, NotebookEdit, or MultiEdit entry is allowed, with an characters, never credit. - **Shell scope:** a shell segment only credits a target equal to or inside one of its path operands (resolved against the tool `cwd`; a glob operand - counts as its literal directory), or inside the `cwd` when it has none: + must match the target or a directory above it, and a git pathspec's `*` + also matches `/`), or inside the `cwd` when it has none: `rg payment docs` or `grep -rn payment /usr/share/doc` never credit `src/payment.py`. An input redirection before any operand (`rg payment < file`) or a `<` inside a word reads stdin and never counts.