Skip to content

Commit 2f14fc9

Browse files
release: 2.1.1 (#23)
* fix(imports): a markdown link is not an import The capture ran through `[@user](https://…)` link syntax into the URL, picked up a `/`, and passed the looks-like-a-path gate. Biome's CLAUDE.md credits twenty-two maintainers that way, and every one was an AGF004 error. No real import target contains a `]`, so the capture stops there now. * fix(imports): resolve an import from the file that declares it Measured on Claude Code 2.1.238: a chain of imports loads sub/leaf.md from sub/mid.md while an identically named file at the repository root stays unloaded. Resolution is file-relative, and root-relative is not a fallback. The old resolver used the directory the file governs, which for .github/CLAUDE.md is the root — so n8n's `@../AGENTS.md`, a real 18KB file one directory up, escaped the repository and was reported missing. The same measurement cuts the other way: an import that only resolves against the root is genuinely broken and is now reported. One that escapes the repository is never reported, because a bounded scan cannot prove absence. * fix(frontmatter): read skills, commands, and rules the way the programs do The 2.1.0 lenient parser went to one of five call sites. trigger.dev's drizzle skill — an unquoted description carrying `conventions: ` — loads in Claude Code and echoes its description back verbatim, while agentfile reported a parse error and a missing description. Measured per surface: a command with `description: uses: colons, badly: everywhere` is listed with exactly that description, and a .claude/rules file with the same shape still loads its body. An unclosed fence is still an error in either reading. * fix(skills): a name the loader ignores cannot be an error Measured on Claude Code 2.1.238: a skill named n8n:create-pr in a create-pr/ directory loads and is invoked as create-pr; a skill in a directory named My_Weird.Skill loads and is invoked as exactly that; a SKILL.md with no frontmatter at all is listed with its first heading standing in for the description and resolves when invoked by name. The directory is the identity. So AGF101's grammar and directory-mismatch findings drop to warning, a name that breaks both is one finding rather than two, and AGF102's default drops to warning: missing metadata degrades discovery, it does not break the skill. n8n's 22 namespaced skills were 44 errors; they are 22 warnings. * fix(injection): an emoji's joiner is rendered, not hidden AGF505 flagged the ZERO WIDTH JOINER inside PostHog's 🧑‍💻 — the codepoint that makes two pictographs one glyph. A well-formed emoji ZWJ sequence is exempt now; a joiner that joins nothing is still a finding. The same finding pointed at lines 306 and 311 when the characters sat on 317 and 322: skill and subagent text starts after the frontmatter, and the scanner numbered it from 1. Both now carry bodyLine through the IR, the same anchor instructions already had. * test: every fix carries the measurement that grounds it Each regression case names the repository it came from and the behaviour measured on Claude Code 2.1.238, and each keeps a mutation guard: the unclosed fence still errors, the bare joiner still fires, the root-relative import that never loads is now a true positive. * release: 2.1.1 A patch by docs/stability.md: no new codes, no meaning changes — severity defaults and heuristic reach are documented as not stable. Ten popular repositories produced 69 error-severity findings on 2.1.0; all 69 were wrong, in six ways, and each fix in this release carries its measurement. Internal ranges move with the version, as they did last time. * fix(action): plant a defect that is still a defect The fixture's frontmatter stopped being an error when the readers went lenient — correctly, since the programs load it. The self-test now plants a broken import instead: CLAUDE.md promising a file that does not exist stays an error on every measured path, so the gate has something real to gate on. * chore(deps): take the six open dependabot bumps into the release biome 2.5.11, semver ^7.8.5, react-query ^5.102.8, plugin-react ^6.1.1, lucide-react ^1.35.0, zod ^4.5.2 (core and ui), open ^11.0.2, diff ^9.0.0, checkout@v7 and setup-node@v7 in the action and its self-test. The two red checks on the open and diff PRs were not those dependencies: 2.1.0 reached npm at 13:38:02 and their runs started at 13:38:09, the first runs to resolve latest after the fixture's planted defect stopped being an error. The corrected fixture in this branch is the fix. Closes #16. Closes #17. Closes #18. Closes #19. Closes #20. Closes #21. --------- Co-authored-by: Dennis Havermans <dennis.havermans@ae.be>
1 parent 8b1a0a4 commit 2f14fc9

23 files changed

Lines changed: 483 additions & 161 deletions

.github/workflows/action.yml

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ jobs:
2121
name: Reports and counts known defects
2222
runs-on: ubuntu-latest
2323
steps:
24-
- uses: actions/checkout@v5
24+
- uses: actions/checkout@v7
2525

26-
# Two defects a human reads straight past: a bare * opens a YAML alias
27-
# rather than a glob, so neither rule's frontmatter parses at all.
26+
# Two defects a human reads straight past. The import names a file that
27+
# does not exist, so the instructions it promises are silently absent —
28+
# measured on Claude Code, imports resolve from the declaring file and
29+
# nothing else, so there is no path on which this loads. The bare * in
30+
# the rule's globs is the shape Cursor cannot match.
2831
- name: Write a fixture with defects we can count
2932
run: |
3033
mkdir -p subject/.cursor/rules
@@ -35,8 +38,9 @@ jobs:
3538
---
3639
Use type hints.
3740
RULE
38-
cat > subject/AGENTS.md <<'DOC'
41+
cat > subject/CLAUDE.md <<'DOC'
3942
# Conventions
43+
Read @docs/style.md before writing code.
4044
Use type hints.
4145
DOC
4246
@@ -80,12 +84,12 @@ jobs:
8084
name: Gates the build when findings are present
8185
runs-on: ubuntu-latest
8286
steps:
83-
- uses: actions/checkout@v5
87+
- uses: actions/checkout@v7
8488

8589
- run: |
8690
mkdir -p subject/.cursor/rules
8791
printf -- '---\ndescription: Python rules\nglobs: *.py\n---\nUse type hints.\n' > subject/.cursor/rules/python.mdc
88-
printf '# Conventions\nUse type hints.\n' > subject/AGENTS.md
92+
printf '# Conventions\nRead @docs/style.md before writing code.\nUse type hints.\n' > subject/CLAUDE.md
8993
9094
- id: gated
9195
uses: ./
@@ -109,7 +113,7 @@ jobs:
109113
contents: read
110114
security-events: write
111115
steps:
112-
- uses: actions/checkout@v5
116+
- uses: actions/checkout@v7
113117

114118
- uses: ./
115119
id: agentfile

CHANGELOG.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,75 @@ Versioning follows [Semantic Versioning](https://semver.org/).
99

1010
## [Unreleased]
1111

12+
## [2.1.1] — 2026-09-01
13+
14+
2.1.0 shipped with a claim it had only partly earned. The lesson of that
15+
release was that a parse error belongs to the linter, not the program — and
16+
the fix went to one of the five call sites that had the bug. This release
17+
comes from running 2.1.0 over ten popular repositories in full and measuring
18+
every error it produced against Claude Code 2.1.238. There were 69 findings at
19+
error severity. All 69 were wrong, in six distinct ways, and every fix below
20+
carries the measurement that grounds it. The same run's one true finding — an
21+
invisible U+2060 WORD JOINER inside a code span in trigger.dev's Cursor rules
22+
— still stands, now at the right line number.
23+
24+
### Fixed
25+
26+
- **A markdown link is not an import.** `AGF004`'s capture ran through
27+
`[@user](https://…)` link syntax into the URL, picked up a `/`, and passed
28+
the looks-like-a-path gate. Biome's CLAUDE.md credits twenty-two maintainers
29+
exactly that way, and every one was an error. No real import target contains
30+
a `]`, so the capture now stops there.
31+
32+
- **An import resolves from the file that declares it.** n8n's
33+
`.github/CLAUDE.md` opens with `@../AGENTS.md`, and the target is a real
34+
18KB file. The resolver used the directory the file *governs* — the root —
35+
so the `..` escaped the repository and the import was reported missing.
36+
Measured: a chain of imports loads `sub/leaf.md` from `sub/mid.md` while an
37+
identically named file at the root stays unloaded, so resolution is
38+
file-relative and root-relative is not a fallback. That cuts both ways: an
39+
import that only resolves against the root is genuinely broken and is now
40+
reported, where before it passed. A target that escapes the repository is
41+
never reported — a bounded scan cannot prove absence.
42+
43+
- **Skills, commands, and `.claude/rules` read their frontmatter the way the
44+
programs do.** The 2.1.0 lenient parser went to subagents only.
45+
trigger.dev's `drizzle` skill — an unquoted description carrying
46+
`conventions: ` — loads in Claude Code and echoes its description back
47+
verbatim, while agentfile reported a parse error and a missing description.
48+
Measured per surface: a command whose frontmatter reads
49+
`description: uses: colons, badly: everywhere` is listed with exactly that
50+
description, and a rule with the same shape still loads its body. All five
51+
call sites now share the strict-then-lenient reading; an unclosed fence is
52+
still an error in either reading.
53+
54+
- **An emoji's joiner is rendered, not hidden.** `AGF505` flagged the ZERO
55+
WIDTH JOINER inside PostHog's 🧑‍💻 — the codepoint that makes two
56+
pictographs one glyph. A well-formed emoji ZWJ sequence is now exempt; a
57+
joiner that joins nothing is still a finding.
58+
59+
- **`AGF505` line numbers point at the file, not the body.** Skill and
60+
subagent text starts after the frontmatter, and the scanner numbered it from
61+
1 — PostHog's finding said lines 306 and 311 when the characters sat on 317
62+
and 322. Skills and subagents now carry `bodyLine` through the IR, the same
63+
anchor instructions already had.
64+
65+
### Changed
66+
67+
- **A skill name the loader ignores cannot be an error.** Measured: a skill
68+
named `n8n:create-pr` in a `create-pr/` directory loads and is invoked as
69+
`create-pr`, and a skill in a directory named `My_Weird.Skill` loads and is
70+
invoked as exactly that. The directory is the identity, so `AGF101`'s
71+
name-grammar and directory-mismatch findings drop to warning severity, and a
72+
name that both breaks the grammar and mismatches its directory is one
73+
finding, not two — n8n went from 44 errors to 22 warnings. `AGF102` drops
74+
its default to warning for the same reason: a `SKILL.md` with no frontmatter
75+
at all still loads, is listed with its first heading standing in for the
76+
description, and resolves when invoked by name. Missing metadata degrades
77+
discovery; it does not break the skill. Severity defaults are documented as
78+
not stable in [docs/stability.md](docs/stability.md); pin them in
79+
`agentfile.yaml` if CI depends on them.
80+
1281
## [2.1.0] — 2026-09-01
1382

1483
A minor, not the patch this started as. Most of it is correctness work, but

action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ runs:
7777
fi
7878
7979
- if: steps.node.outputs.satisfied != 'true'
80-
uses: actions/setup-node@v4
80+
uses: actions/setup-node@v7
8181
with:
8282
node-version: 22
8383

docs/diagnostics.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,19 @@ A skill breaks a specification requirement:
116116
* two skills sharing a name, where which one loads depends on directory
117117
precedence the platforms do not document identically
118118

119-
### `AGF102` missing-skill-metadata · error · active
119+
The two name findings are reported at warning severity rather than the code's
120+
default: measured on Claude Code, a skill whose name breaks the grammar or
121+
disagrees with its directory still loads and is invoked under the directory
122+
name, so the finding describes a portability and cross-reference problem in a
123+
skill that works. When a name both breaks the grammar and mismatches the
124+
directory, only the mismatch is reported — one fact, not two.
125+
126+
### `AGF102` missing-skill-metadata · warning · active
120127
A skill omits `name` or `description`, the two fields the specification requires.
121-
A missing description is not a weak skill but an unusable one: the description is
122-
the only thing an agent sees before deciding whether to load it.
128+
The skill still loads and can be invoked by name — measured on Claude Code, a
129+
`SKILL.md` with no frontmatter at all is listed with its first heading standing
130+
in for the description — but the description is what an agent weighs when
131+
deciding to load a skill unprompted, so without one the skill is rarely chosen.
123132

124133
### `AGF103` skill-routing-quality · warning · active
125134
The description is valid but an agent cannot route on it reliably — it is too

0 commit comments

Comments
 (0)