aipromptguide-workflows / tests
CLAUDE.md1 starsChanged 8 days agoMITView on GitHub
Who this is for. This file is for changing the workflows. The root CLAUDE.md and each workflows/<x>/CLAUDE.md are for running them inside someone's project. Development lore goes here, not there. Read it before you change an engine, and work §8's steps in order. The engines are deliberately standalone copies (#11 does not apply across them — see §5), so a defect in one is usually a defect in three. And the review workflow structurally cannot see this: each reviewer gets…
In this repository10 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- tools/CLAUDE.mdCLAUDE.md
- brainstormSkill
- debugSkill
- decideSkill
- developSkill
- docsSkill
- enhanceSkill
- investigateSkill
- refineSkill
The file
Show its rules and text
We split this CLAUDE.md into the rules it gives an agent
19 rules. About 0 are common, 11 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 7
Read it before you change an engine, and work §8's steps in order.
- Very specific: line 55
metamust be a PURE LITERAL: No variables, no calls, no ${}. - Distinctive: line 62
The harness is not an LLM: No context window, no tools: it cannot read a file, run git, or shell out.
- Very specific: line 87
Ask of every ?. on an agent return: does the failure case look identical to a success case?
- Very specific: line 105
Write-confirmation asymmetry: If one role's schema has wrote_file and its siblings don't, the ones without can report success having …
- Distinctive: line 169
Save strictly before clear: If the commit cannot be made, leave the tree exactly as it is.
- Distinctive: line 185
The workflows serve any project and any user: No rule, check or prompt sentence exists only to serve the maintainer's own setup, such as a …
- Distinctive: line 188
A fact lives in as few places as possible: Keep it in at most one copy for users (a README) and one for agents (a CLAUDE.md or the prompt …
- Distinctive: line 210
No prompt-quality tests: Not testable here by construction — see §6.
- Distinctive: line 238
Verify a regression test fails against the old code: before trusting it. Run it against the old code with the recipe below and watch it go …
- Very specific: line 394
Run node tests/run.mjs in that worktree as a baseline.
- Very specific: line 397
Add a case covering the change (tests/README.md).
- Distinctive: line 411
Update the workflow's CLAUDE.md for a new required arg, return field, written file or halt condition, and its README.md for a human-visible …
- Distinctive: line 413
Add the root README.md changelog bullet as §10 step 1 says.
- Very specific: line 414
Run node tests/run.mjs green in the worktree and commit.
- Very specific: line 420
When this checkout drives runs in other projects, their agents and operators call tools/ from it mid-run: develop's blockTool, and …
- Very specific: line 445
When a workflow's build lands in this repo, work these steps once that workflow's entire ## 0.
- Distinctive: line 448
Add one bullet per user-visible change to the newest dated section of the root README.md changelog, opening today's section when it is …
- Distinctive: line 451
Commit the changelog on its own.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Working on the engines — gotchas (for Claude)
Who this is for. This file is for changing the workflows. The root CLAUDE.md and each
workflows/<x>/CLAUDE.md are for running them inside someone's project. Development lore goes here, not
there.
Read it before you change an engine, and work §8's steps in order.
1. Fix it in EVERY engine, not the one you found it in
The engines are deliberately standalone copies (#11 does not apply across them — see §5), so a defect in one is usually a defect in three. And the review workflow structurally cannot see this: each reviewer gets one unit, so a sibling's copy of the same bug is invisible to it. Finding the twins is the operator's job at triage.
The families — a fix in one member almost always belongs in the others:
- Build loop:
develop-cyclealone. Itsfeature,sectionandfixmodes share one loop (develop → blind quality → acceptance, park, staging,DISMISSED-<id>.md,NEEDS-USER.md). A defect in one mode's frame can still recur in the other two modes' frames in the same file. - Read-only fan-outs:
audit's two kinds, defects and enhancements, in one engine (finder → verifier, one output file per unit/lens, clean-marker written by the finder). The code both kinds run is one copy, and each kind's prompts, schemas and flow are their own, so a defect in one kind's code may recur in the other kind's parallel code in the same file. - Lensed generative:
brainstorm·docs·auditenhancements (an id-keyed array fanned out to one file each) - Rubric review loops:
investigate·refine(producer ⇄ adversarial critic against a fixed rubric, bounded bymaxRounds, solo critical agents that throw on a null return rather than defaulting to a clean verdict).refinepairs a read-only critic with a minimal-fold editor under a fixed defect bar. Its dead critic throws, because zero gaps from a dead agent would read as convergence. - Bounded chain:
decidealone (a serial investigator chain bounded bymaxInvestigators, then one decider and one final reviewer, with no loop back). Every agent's file is the next agent's input, so every agent is solo critical and throws on a null return.
investigate is the review loops' odd member: its candidates are found round by round, so its loop
state lives in a ledger (DISQUALIFIED.md). decide's chain also finds candidates one agent at a time, so
when fixing how one of them hands the earlier files to the next agent, check the other.
Do it by grep, not memory. grep -n "TARGET.repo" workflows/*/*.mjs finds in seconds what a reviewer
cannot find at all. Then fix each in its own file with parallel wording — never by extracting a shared
module (§5).
2. The runtime contract — things that break silently
An engine is not an ordinary Node module. The harness executes the file body with
agent/parallel/pipeline/phase/log/args/budget/workflow injected as globals.
node --checkdoes not work on an engine — top-levelreturnis legal here and a syntax error there. Use the harness wrap (tests/harness.mjs, ornode tests/run.mjs).metamust be a PURE LITERAL. No variables, no calls, no${}. The tool parses it statically; interpolation breaks discovery at launch, not at edit time.Date.now(),Math.random(), arglessnew Date()THROW. They would break resume. Pass timestamps in viaargs; vary by index instead of randomly.- LF only. One CR byte and the Workflow tool rejects the file outright.
.gitattributesenforces it and the suite checks it, but an editor can still reintroduce it. gen-units.mjsis the exception — ordinary Node with real imports, run directly. Judge it normally.- The harness is not an LLM. No context window, no tools: it cannot read a file, run git, or shell out. "The harness should just read X" is never a valid fix — that work belongs to an agent, or to the main agent before the run (#4).
- The runtime cuts long log lines. A log line over 11,024 chars keeps its first 5,000 and last 5,000
chars around a
... [N characters truncated] ...marker. Only a run's first 1,000 log lines are kept, and later lines drop with no marker. So a log line a tool parses back stays under 10,000 chars, and develop stops its progress lines at 900 to leave room for its status lines inside the first 1,000. A block whose status lines overrun that room halts the next block, and its lines past the cap survive only in the result'sstatusSync. The run record'sresultis never cut, and itslogshold only the engine's own lines (measured in 2.1.287 to 2.1.289).
3. Recurring defect shapes — check these first
A dead agent is the number one source of real bugs. agent() inside parallel()/pipeline()
resolves to null when that agent dies. Every one of these launders a dead agent into a plausible value:
const gaps = curate?.gaps ?? []; if (!gaps.length) break; // dead curator == "finished, no gaps"
const dirty = fix?.baseline_dirty_files ?? -1; // -1 means "n/a"; null collapses into it
const verdicts = v?.verdicts || []; // dead verifier == "nothing to report"Ask of every ?. on an agent return: does the failure case look identical to a success case? If yes,
guard it. The house pattern is a throw for a solo critical agent (decide, docs curator) and an explicit
halt for one inside a loop (develop's round roles).
Sentinel collisions. Any ?? -1 / ?? 0 / || [] meaning "not applicable" must not be reachable by
a missing value. Check the null path separately, before the sentinel logic.
Attestation theater. A schema field the prompt demands and the harness never reads does nothing.
TOP-LEVEL fields are gate-enforced: static.test.mjs resolves every schema a callAgent() call is handed
— including the function-built one — and fails any top-level property with no .field / ['field'] /
destructured read through that site's RECEIVER variable (aliases via bare x = recv; assignments count,
transitively; a receiver name with more than one binding in the file is a loud extractor error — rename the
colliding local). Sites with no resolvable receiver (pipeline stages, .then chains, bare
return callAgent() fall back to the old file-wide name-level match and are LISTED in the test file, so
degraded coverage is visible. NESTED item fields are still on you: they are excluded deliberately (measured —
an all-depth rule was 33 false positives out of 36) because a container is routinely consumed wholesale, so a
dead field one level down is invisible to the sweep.
Write-confirmation asymmetry. If one role's schema has wrote_file and its siblings don't, the ones
without can report success having written nothing. Make the set consistent.
A relaunch's file is a file the agent has not read. A relaunch with the same runId writes to
the paths the earlier launch used, and the Write tool refuses to overwrite a file the agent has not
read. An agent that ignores the refusal still returns wrote_file: true, and the next stage's agent
reads the earlier launch's text as the new round's. Every engine holds REPLACE_NOTE and
WROTE_NOTE once. REPLACE_NOTE sits at each site that replaces a file in full, apart from develop's risk
file, which risk-check.mjs writes or leaves in place before the developer writes it, so its read step says
so. WROTE_NOTE sits once in each prompt whose return attests a file the agent writes with the Write tool.
One return falls outside that rule and carries no WROTE_NOTE: docs' files_cleaned, an in-place Edit count.
develop's park carries one for its backlog entry, and its commit is attested by the commit_sha it returns.
Two replace sites add a guard after REPLACE_NOTE: the investigator's options files and docs' gatherer
capture files. Each engine's test counts both notes in each such prompt through pinWriteNotes, so a new
write site needs a row there.
In every engine but develop, WROTE_NOTE is followed by FILE_WRITE_NOTE, which tells the agent to
write file content, and any script that edits a file, with the Write or Edit tool.
pinFileWriteNote counts it in the same rows.
Docs' scrubber carries FILE_WRITE_NOTE without WROTE_NOTE, so docs' test gives it its own row.
Prose-sniffing for control flow. status = haltReason.includes('needs user') ? … : … silently
reports the wrong thing the moment someone adds a halt reason. Set an explicit haltKind at every halt
site and map it.
A flag derived from a halt-kind DEFAULT inherits a lie no sweep can see. (Its first-order cousin —
a coerced numeric bound silently producing a zero-agent run — is gate-enforced now: static.test.mjs
runs every engine against garbage bounds and expects a throw, so it needs no manual vigilance.)
haltKind starts as 'rounds', so determination: haltKind === 'rounds' ? FILE : '' named a file
nothing wrote. Gate such a flag on proof that an agent actually ran, such as round > 0 or an attested
write, the way decide sets decisionFile only once its decider attests the file.
A pre-filter that drops candidates before the verifier makes the floor invisible. enhance cut
below-floor candidates on the finder's own unverified score, logged the count, and dropped it — so a
minImpact set too high was indistinguishable from a clean system, and those candidates appeared in no
proposal file at all. If a knob silently removes work, its count belongs in the return (#8: a count is
control plane), or the operator cannot tell the knob from the verdict. enhance resolved it by removing
the cut: every finder candidate now reaches the verifier, which rejects a sub-floor one by name in its
lens file's ## Rejected section.
Documented-Required-but-defaulted. abs() resolves a relative path against ROOT, and ROOT is the
tool's directory — so TARGET.repo ?? '.' silently means "operate on the workflow tool itself." For an
engine that runs git checkout -- and deletes files, that is the destructive case. If a doc says
Required, the engine must throw.
Self-contradictory agent returns. {pass: true, regression: true} violates pass's own schema
description. Decide deliberately whether each contradiction halts or is merely flagged — the test we
settled on is does the harm compound? A regression poisons the baseline every later unit is judged
against (halt); an unreachable feature does not (flag).
4. Invariants you must not break
- Every terminal exit leaves a clean unstaged tree. Accepted work is staged, or committed in commit
mode. Unfinished work is parked (saved as a commit on the branch
aipg-unfinished/<backlog file id>, then cleared, and linked by one backlog entry). This is load-bearing: it is what makes the round-1 clean-baseline precondition correct on a resume, not just a fresh run. Add a new exit path and it must either park or have changed nothing. The deliberate exceptions are a dirty or staged baseline (nothing was changed; that work is the operator's), arelayed-requesthalt (a park agent would read the same relayed message, so it runs no park), passed-but-unstaged (the work is good and onegit addfixes it — parking would be worse), a commit-mode halt whose tree is dirty after the commit step, acommit-unverifiedoracceptance-regressionhalt that names the tree dirty check (a hook, the gate run or an unstaged block file may have left the change, so the operator inspects it before anything parks it), and apark-unsafehalt whose park did not confirm a clear. - Save strictly before clear. If the commit cannot be made, leave the tree exactly as it is.
- Never tell a user to restore a parked branch and then resume. It makes the tree dirty and the
round-1 precondition rejects it. The honest route is the park's backlog entry, whose fix block restores
the branch after the clean-baseline check, so both reviewers judge the restored work. Never
git add -Arestored work, which promotes un-reviewed code into the accepted baseline where the blind reviewer cannot see it and every later regression check treats it as known-good. - Staging is the boundary, and only acceptance in commit mode commits to the work branch. A park commits
only to its
aipg-unfinished/branch, in either mode. In stage mode the user commits. In commit mode acceptance commits each block it accepted, and the accepted baseline is HEAD. - The blind reviewer is blind by placement. Run-state lives outside the target repo so it cannot
reach the issue files, and the blind prompt's only run-state paths point into its own
runs/<runId>/gate/(its review files + DISMISSED ledgers). Never hand it a path intoruns/<runId>/outsidegate/— NEEDS-USER, AMENDED, critiques and issue files all live there. - audit's defects kind hunts defects only. Its inventory feeds an autonomous fixer, and an improvement list never converges. Improvements are audit's enhancements kind, which deliberately has no fix-mode output.
- The workflows serve any project and any user. No rule, check or prompt sentence exists only to serve the maintainer's own setup, such as a personal writing style, user-level CLAUDE.md rules, skills or local paths. A user's own rules reach agents through that user's CLAUDE.md.
- A fact lives in as few places as possible. Keep it in at most one copy for users (a README) and
one for agents (a CLAUDE.md or the prompt constant that renders it), and point every other place to
one of those. A rule that two prompts share lives in one constant that both render, as
DOC_REGRESSIONandSPEC_CASESdo indevelop-cycle.mjs.
5. Not defects — don't spend review cycles here
- Duplication between sibling engines. Deliberate: each is standalone and copyable. Never extract a
shared module or import across workflows. (#11 governs facts, not code across independent tools.)
role-map-copies.test.mjsfails when an engine'sroleMap, itsagentTypeMapor its two value sets differ from develop's, so change all seven together.convention-files-copies.test.mjsfails when refine's or develop'sconventionFilesOfdiffers from audit's, so change all three together.notes.test.mjsfails when an engine's exit-survey instruction differs from the test's copy, or when an engine callsagent(anywhere but inside itscallAgent, so route every new call site throughcallAgentand change all seven instructions together.relay-guard.test.mjsfails when an engine'sRELAYED_REQUESTline differs from develop's, so change all seven together. - WHY comments. A one or two line comment stating a constraint the code cannot show is the house style. A long one, a history note or a restated guide is a #13 finding, not a defect: it belongs to an enhance conciseness pass, never to debug.
- Missing JSDoc, "this function is long", naming preferences, formatting.
- No prompt-quality tests. Not testable here by construction — see §6.
6. Testing
node tests/run.mjs. See README.md for the API; the short version:
- A test file runs beside the others, so it shares no resource with them.
README.mdlists the rules. - Guide prose wraps at 110 characters.
static.test.mjsfails on a longer prose line in the rootCLAUDE.mdandREADME.md,tools/CLAUDE.md,tests/CLAUDE.md,workflows/*/CLAUDE.md,workflows/*/README.mdandprinciples/*.md. It exempts table rows, fenced code blocks and a line with one token that no wrap fits under the width, such as a URL. It does not checkdocs/,tests/README.mdorskills/.node tests/run.mjs staticruns that test file without the rest of the suite, so a guide edit runs it before the full suite. - Each workflow guide opens with its checklist.
static.test.mjsfails when aworkflows/*/CLAUDE.mddoes not open with## 0. Checklist, lacks a numbered### Beforeor### Afterlist, numbers its §0 steps with a gap or a repeat, or cites in §0 a§Nits own headings lack. A§Ncites another guide only right after that guide'sCLAUDE.mdpath in backticks or its bare workflow name, such asdevelop §2. A possessive such asdevelop's §2reads as the guide's own §2. - Each pre-launch step carries the same memory check.
static.test.mjsfails when a §0 step of aworkflows/*/CLAUDE.mdthat namesrun_in_backgroundlacks theMEMORY.mdindex sentence, or when a copy of that sentence differs from the one the test pins. Change every copy and the test together. - Test failure paths. The happy path is exercised constantly by real runs. Halts, parks, dead agents, contradictory returns and bad args are what rot silently — and are where every real defect has been.
respond: { 'label': null }simulates a dead agent — one character, and it covers §3's number-one defect shape.- Verify a regression test fails against the old code before trusting it. Run it against the old
code with the recipe below and watch it go red. A green test that would never have caught the bug is
worse than none. The rule covers assertions on new or changed behavior. An assertion that guards kept
behavior passes on both versions and needs no red run. An assertion that reads a field the change adds
or renames reads it defensively, such as
String(out.nextStep ?? ''), so the old code fails that one assertion early and visibly instead of throwing and hiding every assertion after it. An absence assertion on new behavior, such as a new prompt that must not name some file, passes on the old code by construction. Prove it against a mutant that adds what it forbids. - The red-run recipe. Export the code under test at its old version into a NEW, empty scratch folder
outside the repo.
checkout-indexnever overwrites a file that exists, so a reused folder holds stale code and the run passes silently. Export the staged version withgit -C <repo> checkout-index -a --prefix=<scratch>/. It is the old code when the index holds earlier accepted work and your change is unstaged. Export the committed version withgit -C <repo> archive HEAD | tar -x -C <scratch>only when your own change is staged. Copy the new test files over the export and runnode <scratch>/tests/run.mjs <filter>. For an absence assertion, also copy the changed code files over the export and apply the mutant to those copies. Never swap files in the working tree for a red run. - Edit engine template text with the Edit tool or a script file written by the Write tool, never
through a Bash command, as
FILE_WRITE_NOTEin develop-cycle.mjs tells agents. Agents that edited files through a Bash command or a script it carried lost backslashes, in single quotes too, so the exact-match replacement that command feeds then finds nothing. The Edit tool edits only a file the Read tool has read, so a Bashcatdoes not unlock it. - A cross-engine sweep must RUN each engine, not grep it. Grepping for the shape of a fix cannot tell you the fix is wired to the thing it must guard.
- Don't hand an unguarded loop a huge bound to prove a cap exists. Probing
maxRounds: 1e21against an engine that lost its cap does not fail the assertion — it runs until V8 dies, and a heap-exhaustion stack trace buries every other result. Probe just above the cap (51). - Prompt correctness is testable; prompt quality is not. "Does this interpolate a path that
exists" is a contract — assert it with
prompt(prefix). "Is this well written" is what the review loops are for. - The suite tests the ENGINE, never the AGENTS. Green does not mean the workflows work; it means the control plane does.
7. Flow maps — the diagrams are a gate, not decoration
workflows/<x>/FLOW.md is generated: tools/gen-flows.mjs runs each engine through
tests/harness.mjs against a scenario table in tools/flows/<name>.flow.mjs and draws what it watched.
Edit an engine and you must regenerate, or the suite goes red:
node tools/gen-flows.mjs # regenerate all
node tools/gen-flows.mjs develop # just one
node tools/gen-flows.mjs --check # what the gate runs; exit 1 lists the stale filestests/flow-coverage.test.mjs is the part that earns its keep. Per engine it asserts that every
agent() role, every throw new Error( site and every HALT_STATUS value is reached by some scenario,
that the committed map is fresh, and that meta.phases matches the phases agents really run under.
- Phases are read from each call's
opts.phase, never from firedphase()calls.audit.mjsnever callsphase()at all, anddocs-cyclecalls it once against three declared phases — measuring the calls would report both engines as drifted and the "fix" would be deleting correct launch documentation. - One engine may have several specs. audit's two kinds each have one, so each map lists only the
phases its own agents run under, and role, throw and
meta.phasescoverage are judged over the union of every spec that names the engine. - Coverage cannot see a branch that adds no role, no throw and no new terminal. A dead-agent path, a skipped stage, or a second route into an existing terminal is invisible to every assertion. The scenario tables carry comments saying which branches are load-bearing for exactly this reason — read them before deleting a scenario that looks redundant.
- Add a scenario, don't loosen a check.
allowUncovereddemands a reason and fails if the item turns out to be covered, so it cannot quietly become a dumping ground. - The maps are published, so they are dash-free.
dedashflattens em/en/horizontal-bar dashes to ASCII hyphens in the finished document, and a coverage assertion holds every map to it. Do not strip dashes from engine prose to satisfy it: aHALT_STATUSstring is an engine return value that tests assert verbatim, and the normalization is presentation-only by design.
The tables are deliberately parallel to the *.test.mjs files rather than shared with them: the tests
optimize for failure paths, the maps for complete terminal coverage, and a test deleted as redundant
would otherwise blank a branch of the diagram.
Is the diagram READABLE? — tools/render-flows.mjs
gen-flows.mjs proves a map is correct; it cannot tell you the picture is legible. Mermaid does no
collision avoidance on edge labels, so two long ones leaving the same node render on top of each other
and neither can be read — invisible to every text assertion.
node tools/render-flows.mjs # lay every map out in real Mermaid, report overlapping labels
node tools/render-flows.mjs develop # one
node tools/render-flows.mjs --png # also write PNGs to tools/.cache/render/ (gitignored)It drives the Chrome already on the machine with --dump-dom — no puppeteer, no playwright, still zero
npm dependencies. Mermaid itself is fetched once into tools/.cache/. It is not wired into
node tests/run.mjs, deliberately: it needs a browser and (once) the network, and the suite must stay
runnable anywhere. Run it by hand after changing anything that affects layout.
Layout knobs, all tuned against it — raise spacing before shortening text, because room is free and dropped information is not:
NODE_SPACING/RANK_SPACINGingen-flows.mjs(override viaFLOW_NODE_SPACING/FLOW_RANK_SPACINGto re-run the sweep). 60/90 left 8 of 9 maps colliding and 80/200 left 2, which the self-loop and edge markers below cleared.WHEN_BUDGETcaps an edge label by length. It bounds the string Mermaid receives (beforeesc()) — the· +N moretail and the(×N)repeat count are charged against it, not added after. The budget decides how many conditions to show, never how many characters of the last one: a single over-budget condition is emitted whole, because a back edge is not a terminal and no table below carries its text, so truncating destroys the only copy.- Two edge kinds carry a reference instead of their text, and never collide as a result: edges into a
terminal are drawn unlabelled (one agent often fans out to six endings) with their conditions in the
Terminal states table; self-loops carry a marker (
L1 ×4) with their conditions in the Loops table. Both are indexed by the question a reader actually asks, both are complete rather than truncated to fit on an arrow, and both are bounded by construction — which is the property that matters, since it cannot regress when a diagram grows a node or a renderer retunes its layout. That only works while the tables are actually populated. Moving text off an arrow is only half the fix. Assert the destination, whichflow-coverage.test.mjsdoes on the rendered row. - Node labels are NOT capped, and a cap is not the fix for a wide map. Uncapping throw labels was measured
with
render-flows.mjs: +24px on 3 of 9 maps, 6 unchanged, because the widest box on these maps is a ~104-character terminal that nothing caps — the throw boxes were never the binding constraint. The clause cut infirstClausestays (messages run to 289 characters and continue into resume instructions); what survives it is emitted whole. If a map is genuinely too wide, raise the spacing constants or wrap with<br/>. readThrowsreturns TWO forms of each message, and they are not interchangeable.prefixstops at the first${because it is the key a runtime message is matched against (startsWith) — it must stay a literal head.templateis the whole message with values elided toINTERP(...), and it is what a LABEL is built from. Labelling off the key printedthrow: plan id(s) [for a message that goes on to say what is wrong with them. A test that only checks "the label is part of the message" cannot tell the two apart, since the key is itself a prefix of the template — assert the rendered node.- A clause cut must know what it is cutting inside of.
firstClausetreats three spans as atomic, and each was a shipped defect: brackets ((inside{ runId, planPath | plan (markdown string) … }cut to a dangling| plan), double quotes (:inside"## Plan: <id>"ended a node mid-quote), and the elided value (...+ a space reads as the.sentence separator, cuttingargs.runOnly ... matches no plan iddown toargs.runOnly ..). Single quotes are deliberately NOT tracked — the messages are prose full of apostrophes, and one opener leaves the scan unbalanced for the rest of the message. Unbalanced input falls back to the depth-blind cut, never to the whole message.
Every map renders clean. Keep it that way by never putting authored text on a self-loop or on either edge of
a marked node pair. Both shapes are markered now: self-loops carry L<n> (conditions in the Loops table),
the back edge of a boundary+back pair carries E<n> (conditions in the Edges table), and the boundary edge of
a marked pair renders caption-less — mermaid places both labels of a multi-edge pair at the same path midpoint
(measured 39×22px overlap at every one of six swept spacings, even with a 5-char marker), so a pair can carry
at most ONE label; the Edges table's fixed sentence carries the next-item advance instead.
render-flows.mjs pins the Mermaid version and keys its cache by it, since 11.16.0 moved self-loops into
a fixed-width side gutter and broke two maps that 11.15.0 rendered clean.
Measured and ruled out — do not re-run these sweeps: RANK_SPACING to 340 changes nothing (the gutter
does not scale with it); shortening a self-loop character budget makes it worse (relayout put a
third map into collision); layout: elk renders byte-identically to no config at all (ELK left mermaid
core in v11 and GitHub has not installed the separate package). defaultRenderer: "elk" is a trap —
it reports zero overlaps and is a false positive: not ELK at all, it reverts to the legacy self-loop path
and scores "clean" by dangling the label in whitespace at the tip of a long teardrop.
The markers are verified clean on both 11.4.1 (what GitHub was last observed serving) and 11.16.0 (the strictest layout measured).
8. Change steps
Work these steps in order. A step that opens with a condition in parentheses runs only under it. A develop
run's agent works in the target.repo its prompt names, which stands in for the worktree, and skips steps
1, 10, 11 and 12.
- Open a worktree off
mainwithnode tools/wt.mjs open(§9). Output: the path it prints. - Run
node tests/run.mjsin that worktree as a baseline. Output: its result. - Grep the sibling engines for the same defect shape and fix each in its own file (§1). Output: the files changed.
- Add a case covering the change (
tests/README.md). When it asserts new or changed behavior, watch it fail against the old code with the red-run recipe (§6). Output: the new case, and the red run's failing assertion when one ran. - Guard each new agent return for
null(§3), and make each new exit path park or change nothing (§4). Output: the case that exercises each. - (when prompt text changed) Run
node tools/gen-prompts.mjsand read its snapshot diff for variants the change was not meant to reach. Then read each changed prompt intests/snapshots/<engine>.prompts.mdas the agent that receives it reads it, since a constant's new sentence sits beside text the edit did not show. Output: the variants the change reached. - (when an engine changed) Run
node tools/gen-flows.mjs, adding a scenario intools/flows/<name>.flow.mjsfor a new agent, guard or terminal state (§7). Output: the updatedFLOW.md. - (when the change affects a map's layout) Run
node tools/render-flows.mjs(§7). Output: its report with no overlap. - Update the workflow's
CLAUDE.mdfor a new required arg, return field, written file or halt condition, and itsREADME.mdfor a human-visible change. Output: the guide diff. - Add the root
README.mdchangelog bullet as §10 step 1 says. Output: the bullet. - Run
node tests/run.mjsgreen in the worktree and commit. Output: the commit. - Close with
node tools/wt.mjs close ... --into main --gate "node tests/run.mjs"as §9 shows, then runnode tests/run.mjsin the checkout. Output: both results.
9. Build in a worktree, and remove it when it merges
When this checkout drives runs in other projects, their agents and operators call tools/ from it
mid-run: develop's blockTool, and plan-edit.mjs args between passes. Engines are copied at launch,
but an edit or branch switch in this working tree reaches a tool call already in flight. So build each
change in its own worktree off main, and point develop's target.repo at it:
node tools/wt.mjs open --repo <checkout> --name <name> --base main # build, gate and commit in the path it prints
node tools/wt.mjs close --repo <checkout> --name <name> --into main --gate "node tests/run.mjs"
node tests/run.mjs # here, after the mergeopen cuts aipg/<name> from main, whatever branch the checkout is on. Run close once the change is
committed, since finished worktrees left for later pile up in the checkout's .claude/worktrees folder, where
a tool that walks the checkout reads them. close rebases the branch onto main when main has a commit the
branch lacks, gates the exact commit main will point at, and fast-forwards main to it. Only then does it
move the worktree folder to the trash and delete the branch. It refuses a worktree with modified, staged or
untracked files, an unfinished operation or submodule git directories, a rebase that would drop a merge commit
of the branch, and a rebase or fast-forward that would delete an ignored file. Run the suite here after the
merge, since a worktree holds only tracked files (docs/worktree-batches.md, "untracked files"). A detached
snapshot worktree that a read-only run audits is removed when that run ends, since its findings name the
commit. Parallel chains use the batch verbs of tools/wt.mjs, whose clean verb moves the folder to the
trash, runs git worktree prune and keeps the branch.
10. After a build lands here
When a workflow's build lands in this repo, work these steps once that workflow's entire ## 0. Checklist
is done:
- Add one bullet per user-visible change to the newest dated section of the root
README.mdchangelog, opening today's section when it is missing. - Run
node tests/run.mjs static. - Commit the changelog on its own.
- (when the run set
notes) Choice: for each theme in eachSURVEY.md, decide whether it holds. Ask whether the instruction text or code the theme cites shows the gap it names in the current tree, and cite that file and line. A doubtful theme holds, with--decision NEEDS_USERin step 5. Output: one line per theme, holds or not, with its file and line. - (when the run set
notes) For each theme that holds, run `node tools/plan-entry.mjs new --file<dir>/survey-<runId>.md --block survey --run <runId> --file-ref <path:line> --category <c> --size <s> --decision <d>`, where `<dir>` is what `node tools/project-settings.mjs backlog --root <root> --repo <this checkout>` prints and `<d>` follows the decision rule of `workflows/develop/CLAUDE.md` §10 (`tools/CLAUDE.md`, "plan-entry.mjs"). Output: the entry id `new` prints. - (when the run set
notes) Fill each new entry's title,**What:**and**Fix:**with the Edit tool, then runnode tools/plan-entry.mjs check <file>. Output:ok: <file>.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

