Skip to content

fix(cli): the guard that watches plugin files parses what it guards - #974

Merged
blafourcade merged 13 commits into
nextfrom
fix/backlog-shape-guard
Oct 7, 2026
Merged

blafourcade merged 13 commits into
nextfrom
fix/backlog-shape-guard

Conversation

@blafourcade

Copy link
Copy Markdown
Contributor

🎯 What & why

A cli test reads plugin markdown that no gate watches, so a change confined to those plugin files could land it red on next. The structural assertions move to the guard whose glob already covers plugins/**.

🛠️ How it works

a-backlog-link-the-reader-can-read.test.js checked that the three field names appeared as substrings. It parses the taught example now, and requires each field as a non-empty string — the same predicate the reader applies at task-backlog-adapter.ts:12. A taught value that is not a plain object is refused by the predicate json-file.ts:5 uses, so nothing the reader accepts is rejected here.

The fence rule took three attempts, and the history is worth one line. Requiring exactly one json fence failed a case nobody has produced. Selecting the fence by content then left this guard green on a decoy above the example while its cli/ sibling went red — reopening the very harm this issue names, since no gate runs that sibling on plugins/**. What ships copies the sibling's own pattern, g added for matchAll, so the two cannot disagree about which fence they read.

One case stays green on both, knowingly. A valid decoy carrying backlog at line start, above a broken taught example, is read as the example by both guards. They agree, so nothing lands red on next, and closing it here alone would make this guard red where the sibling is green. Recorded in the plan's Decisions.

🧪 How to verify

  • Empty written_by in plugins/aidd-dev/skills/01-plan/actions/04-plan.md, stage that file alone, and run pnpm exec lefthook run pre-commit: scripts-tests exits 1. Do not pass --all-files --force, which bypasses the globs and so proves nothing about the local gate.
  • Mutations, each verified against both guards: a line-start fence above the example fails on both; a fence opened after text on the same line is ignored by both; the fence markers stripped, null, [1], and an emptied field each fail naming the file, the shape or the field.
  • node scripts/check-tests-leave-git-alone.js -- node --test 'scripts/__tests__/**/*.test.js' → 550 pass.

⚠️ Heads-up

  • The deferred gap is fix(cli): the taught backlog example is pinned harder than the reader requires #973: the sibling pins the placeholder value and demands key-set equality, both stronger than the reader, and a plugins-only edit to either still lands it red on next.
  • review.md in the task folder is round 1's record and reads changes-requested against a head three rounds old. Rounds 2 to 4 each found one item, each fixed here; the last returned no contract violation.
  • Four commit bodies on this branch assert rules the tree no longer has — the one-fence rule, content selection, a renamed-asset mutation that proved another test, and "no number exists to declare yet". This description is the squash message; do not let GitHub concatenate them.

🔗 Linked issue

Closes #972

✅ I certify

  • I DO CERTIFY I READ EACH LINE OF THE PULL REQUEST BECAUSE I AM A SOFTWARE ENGINEER, NOT A AI PUPPY.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

blafourcade and others added 13 commits October 7, 2026 17:13
The scripts guard is the only one of the two that runs on a change confined to
plugins, and it checks substrings. The cli test parses, and no filter runs it
for those paths. So the structural assertions move to the one that sees them.

Neither filter is widened: plugins/** would run 6800 cli tests on a prose
edit, and a narrow path list is the duplication that caused this, since those
paths moved last week.

Source is the local artefact: GitHub returns HTTP 500 on issue creation, so no
number exists to declare yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
…rching it

The guard over the two skills that teach backlog-link.json looked for the
three field names as substrings. A taught example that kept "backlog",
"written_at" and "written_by" while ceasing to be JSON stayed green, and
that case fell between this guard and the cli/ one. Recorded: with the
asset's syntax broken the guard passed 3 of 3.

The guard now parses the asset and the fenced block in 04-plan.md and
requires each field to be a non-empty string, failing with the file and
the field named. Proven by mutation: broken syntax, an emptied
written_by, a broken fence in the plan and a renamed asset each turn this
test red for their own reason.

Neither the scripts-tests nor the cli filter was widened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The fence match took the first json block, so a second one added above the
taught example would be parsed in its place, and the guard could pass while
the example it guards was broken. That is the defect this guard was just fixed
for, one level up.

The markdown form must now carry exactly one json fence. Proven by mutation: a
second fence added before the taught one fails with "found 2".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
GitHub answered HTTP 500 on issue creation while this was framed, so the folder
carried a source and no declaration, which backlog-link.md calls a normal
state. The issue is #972.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
It guarded a case nobody has produced: no teaching file carries two json
fences. It did change behaviour, which is why it was not obviously dead, but it
would have failed a contributor adding an unrelated json block to 04-plan.md
with a message about a rule they never read.

The guard goes back to matching the first json fence. The known weakness is
accepted and stated: a json fence added above the taught example would be
parsed in its place. Simplicity over a hypothetical.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
A reviewer measured two things against the previous two attempts.

The mutation claimed as proof, a renamed asset, proved another test: readFileSync
was already there, so origin/next failed it too. The mutation that
discriminates is the fence markers stripped with the JSON left as prose, which
origin/next passes and this fails.

And dropping the one-fence rule did not remove the failure it was dropped for.
An unrelated fence above the taught one still failed, now claiming the file
does not teach `backlog` when it does.

Selecting the first fence whose parse carries `backlog` answers both, and
closes the weakness the last commit accepted: an unrelated fence above a broken
taught example now fails instead of passing.

Proven by mutation: an unrelated fence above passes; the same with the taught
example broken fails; the fences stripped fails; an emptied field fails naming
it.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The plan reaches implemented and the phase done. The phase gains the task the
review produced: the taught example is found by what it declares, not by where
it sits.

The acceptance criteria are rewritten around the mutations that discriminate. A
renamed asset was not one of them: readFileSync was already there, so it failed
on origin/next too.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
A reviewer measured that selecting the fence by content made one case worse. A
fence without `backlog` above the taught example left this guard green while
`task-backlog-skill-shape.integration.test.ts`, which reads the first fence by
position, went red. No gate runs that test for plugins, so the edit commits
locally and lands red on next: the harm #972 was opened for.

Both guards now read the first json fence. The message says what that fence
must carry and how many were read, and the parse error is reported instead of
swallowed, which was missing for the markdown form.

And the claim that the last commit closed the accepted weakness was wrong. That
weakness is a valid decoy carrying `backlog` above a broken example, and it is
still green. What content selection changed was the message for a fence lacking
`backlog`.

Measured: a fence above the example fails here and in the sibling; a broken
taught example reports its parse error; the fences stripped fails; null and an
array each fail naming the shape; an emptied field fails naming it. Suite
550/550.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
Mine was unanchored where cli/tests/.../task-backlog-skill-shape reads
/^[ \t]*```json.../mu. So a fence opened after text on the same line counted
here and not there, and a valid decoy carrying backlog placed inline above a
broken taught example left this guard green while the sibling went red. No gate
runs the sibling for plugins, so that edit commits locally and lands red on
next: the harm #972 was opened for, reached a second way.

The regex is now copied character for character, close included.

Measured on three decoy shapes, both guards agreeing each time: inline carrying
backlog over a broken example, both red; inline without backlog, both green;
line-start without backlog, both red. Suite 550/550.

The plan records the three decisions it was missing, including why the cli
test pinned value is left alone.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
It lived only in a commit body, and this repository squashes with
COMMIT_MESSAGES. A valid decoy carrying backlog at line start, above a broken
taught example, is read as the example by both guards. They agree, so nothing
lands red on next, and closing it here alone would reinstate the rule this
branch already added and removed.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The docblocks had grown by accretion over four rounds. Gone: that the two
skills decide what gets written, that they teach the same three names, that the
file is read rather than trusted, and the restated field count. Each is in the
code a line below.

Kept, because none of it is derivable: why the field list is restated instead of
imported, which the third case keeps honest; that a drifting shape already
produced two unreadable files; why the example is parsed and not searched; and
which fence the sibling reads.

23 comment lines to 15. Suite 550/550.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The comment cut kept that two guards reading different fences leave one green
and the other red, and dropped the consequence: no gate runs the sibling on
plugins, so that red lands on next. That consequence is why the pattern is
copied rather than improved, and it is what reverted content selection.

The phase records the comment pass and replaces a criterion that pointed at a
commit body with the measurement itself.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
The plan said the cli test pinned value "belongs to its own issue" while no such
issue existed, which is a silent TODO. It is #973.

Refs #972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c
@blafourcade
blafourcade marked this pull request as ready for review October 7, 2026 19:18
@blafourcade
blafourcade requested a review from a team as a code owner October 7, 2026 19:18
@blafourcade
blafourcade merged commit 84dafd6 into next Oct 7, 2026
25 checks passed
@aidd-bot aidd-bot Bot mentioned this pull request Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant