ci: promote next to main - #809
Merged
Merged
Conversation
Convert every `@../` reference in the action files of skill-generate and project-memory to standard markdown links `[name](path)`, so citations are clickable in editors and on GitHub. Propagate the convention to the contract so generated skills follow suit: R6 now mandates a markdown link, R8 drops the `@`-chain wording, and review-protocol says "cited references". Functional `@`-imports in the memory block (`@aidd_docs/...`) are untouched: those are load mechanics, not citations. Sync docs/CATALOG.md skill-generate action names to the shipped set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177RKF7c5Wpv329Q7QYquV2
…ork (#440) * feat(aidd-context): add research and apply actions to cook skill Extend 12-cook from author-only to a fuller loop: - 03-research: refine the target recipe via a checklist, fan out one agent per scouting angle, then present alternatives / coverage gaps / counter-intuitive wins plus a recommendation. Ephemeral, never writes. - 04-apply: execute an existing recipe against the project via an agent that tracks the steps as a todo list and confirms before mutating. - upsert: qualitative overlap guard (none/partial/high) that suggests updating an existing recipe instead of creating a duplicate. - recipe-template: steps become "#### N)" headings with what/why, actionable how-bullets, and a mandatory concrete example (command with output, snippet, or screenshot/video). New references/assets: research-playbook, refine-goal-checklist, research-checklist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(aidd-context): verify each research candidate before presenting Add a Verify step to cook's 03-research: after curation, spawn one agent per surviving candidate to confirm it exists, capture its official link, and record its latest state. Drop anything that cannot be confirmed against an official source. This makes the checklist's "Confirm claims" real and guards the recipe against hallucinated tools or methods. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): add token optimization recipe Add recipes/token-optimization.md: a ranked list of token-saving tips for AI coding assistants (measure first, trim CLAUDE.md, caveman, RTK/SNIP, CLI over MCP, progressive disclosure, compact, model routing, cap thinking), each tip in the new template format with a concrete example and an official link verified to exist. Register it in the recipes index. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): extract recipe contract and tighten cook template Move the recipe authoring rules out of the template's HTML comment into references/recipe-contract.md, cited by SKILL.md and upsert. The template keeps only the scaffold. - Steps heading carries the goal ("## Steps to <outcome>") and each step opens with an emoji. - Step how-bullets follow where-it-is -> install-from-URL -> how-to-use, benefit-first and filler-free. - "## Why" is short, keyword-led, and bold. - SKILL.md description is shorter and high-level, triggered on "recipe" / "cook" / "/cook". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(aidd-context): repair 12-cook SKILL.md YAML frontmatter The description contained an unquoted "sheets: list" colon, which YAML parses as a mapping value and rejects ("mapping values are not allowed here"), breaking the skill's frontmatter. Replace the colon with a dash. The bug pre-dated this branch (same colon on main). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): refine cook recipe structure and apply R13 to cook Review feedback on the cook skill, plus the new includes convention: - Recipe template drops the Time field, groups steps under three level subheadings (Beginner / Intermediate / Expert), and aligns the Level enum to Expert. - upsert now runs `research` (03) first for any new or substantial recipe and drafts only from its verified results, never from memory — the "always research" rule was being skipped as a routing note. - Apply R13 (explicit, scoped includes): the action-specific research aids move out of SKILL.md and are cited from 03-research in fenced blocks; refine-goal-checklist is renamed research-goal-checklist; SKILL.md lists only the global recipe-template and recipe-contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): make recipe level subheadings optional Level grouping was effectively mandatory. Make the contract state it is optional: group steps under Beginner/Intermediate/Expert only when the recipe spans difficulty levels and grouping helps; a short or single-level recipe lists its steps directly. Relax the upsert Test accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): conform token-optimization recipe to recipe contract Align the recipe with references/recipe-contract.md: drop the Time row (no longer in the template), name the section "Steps to <outcome>" instead of a bare Steps, remove the "ranked by impact" filler line, and give each step how-bullets plus a concrete example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(framework): split measure steps per tool and drop metadata table Apply review of the token-optimization recipe: - Goal becomes a plain sentence (no blockquote). - Drop the Level/Prerequisites table; level now lives in the subheadings. - Split the bundled "Measure first" step into one step per tool (/context, /cost, /insights, an analytics tool) — one subheading per action. - Each measure step says how to use and read the command, and carries an example output; the analytics step uses a real prompt-analytics run (volumes rounded, cost omitted) showing cache reads dominate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): drop the Goal label from token-optimization The recipe opens with a plain description sentence, no "Goal:" label. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): recipe header = description sentence, no table Rules extracted from the token-optimization recipe review: - A recipe opens with the H1 title then one plain description sentence — no "Goal:" label, no blockquote, no metadata table. list/upsert parse that sentence; the index becomes `| Recipe | Description |` (Level was dropped, level now lives only in the optional subheadings). - One step = one action; never bundle several tools or commands under a single heading. - Every step's example shows real command output (from docs or by running it); the research playbook now requires capturing a real example and marking interactive output for the human to paste. - `## Why` is one idea per line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): use the real prompt-analytics dashboard image Replace the fabricated text output with the tool's official dashboard screenshot (cost by token type makes "cache reads dominate" visible). The slash-command placeholders now ask for a screenshot of the real output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): prefer images over text examples A real screenshot or GIF is unfakeable, faithful, and credible, where a text example can be approximated (the failure we hit). Make the contract and the research playbook prefer an image that matches the action — e.g. a tool's official screenshot — falling back to real text output only when no image exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): show real tool usage in token-optimization - caveman: show its real `/caveman` invocation (and modes), not just a before/after. - RTK: a Mermaid diagram of the proxy flow + the real `rtk <command>` syntax (was a wrong `rtk proxy …`) and the README's own 200->10 token saving. - CLI vs MCP: a comparison table instead of prose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): add tool-example rules to recipe contract From the token-optimization review: - Show how to invoke a tool (its real command or slash invocation). - Reuse the canonical example from the tool's own site or README, never an invented one. - A "prefer X over Y" step uses a comparison table, not prose. - A structural or flow concept (proxy, pipeline, architecture) gets a small Mermaid diagram. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): use caveman's real before/after example Replace the invented caveman before/after with the verbatim example from its README (rule F: reuse the tool's own canonical example). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): numbered actions, valid-JSON config, tighter sections - Actions are numbered lists; descriptions are prose (no description-bullets). - settings.json shown as valid JSON, not a bash fragment. - RTK Mermaid uses concrete values (cargo test ~25k -> ~2.5k tokens). - CLI vs MCP notes MCP tool/context selection (cheaper, still slower). - Step 10 reworded plainly ("load knowledge on demand"). - Drop the weak Verify and the redundant Related; add a short conclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): add bullet/diagram/config/section rules to contract From the token-optimization review: - Actions are a numbered list; descriptions and indications are prose, never bullets. - A Mermaid diagram carries concrete example values. - A config example uses the file's real syntax (valid JSON for settings.json, valid YAML for frontmatter, ...). - Reuse a tool's canonical example captured verbatim — never a paraphrase or on the strength of a summary that one exists. - No `## Related` section (links live inline); `## Verify` is optional; recipes may end with a short conclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): expert steps use verified Claude Code config - Step 10 now audits which skills/tools run via `Ctrl+O` (toggles the detailed transcript), instead of the vaguer "load on demand". - Step 11 routes by difficulty with the real `model:` frontmatter field on a skill or agent (haiku/sonnet/opus/inherit) — example agent pinned to haiku. Both verified against the official Claude Code docs (interactive-mode, skills, sub-agents). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): point the instruction-file step at AGENTS.md Use the framework's own concise AGENTS.md as the worked example for a trimmed instruction file, with its real rules (answer first, no narration), instead of a generic snippet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): apply analyses a recipe and asks before acting A recipe is often a human tutorial, not an agent-runnable procedure. So apply now reads the recipe, classifies each step agent-doable vs human-only, and asks the user what to do before any change — then runs the chosen agent-doable steps and reports the human-only ones. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): add five verified token levers to the recipe From a research pass, all verified against official Claude Code docs: - Plan mode (Shift+Tab ×2) — avoid execution-phase rework. - `/clear` between tasks — drop stale context instead of re-billing it. - Deny reads (`permissions.deny`) — keep vendor/dist/secrets out of context; the official answer to the unshipped `.claudeignore`. - Subagent offload — run high-volume ops in an isolated context. - Protect cache hits — avoid mid-task model/MCP/effort switches that invalidate the cache (closes the loop on "cache reads dominate"). Also fold the built-in `outputStyle: concise` into the "talk less" step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(aidd-context): bundle cook recipes in skill * feat(framework): publish AIDDy v2 pet * docs(framework): reposition readme as agnostic token-optimized framework * feat: avoid uncached AGENTS.md * docs: add fixing behavior for assertions --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* docs(framework): update README title and tagline Align the project messaging with the framework's current branding and focus. * chore: release main (#453) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> --------- Co-authored-by: Alex <8973343+alexsoyes@users.noreply.github.com> Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> Co-authored-by: Baptiste LAFOURCADE <baptiste.lafourcade@gmail.com>
…455) Splits source out of gather so origin selection (conversation, file, diff, review) is explicit before extraction runs, and renames assets/decision-template.md to adr-template.md to match its actual output. CATALOG.md's 10-learn index is regenerated to match. Folds in fixes found during a headless claude+codex audit of the skill (13 scenarios x 2 tools): - sync locates update_memory.js instead of assuming a fixed path that doesn't exist in either tool's real install layout - never scaffold the memory bank, even under a blanket write pre-approval - sync's own git-diff-cached test reflects a pre-staged index instead of demanding it be empty - sources.md again defaults to conversation when no hint is given, matching pre-refactor behavior, instead of always requiring context inference - the two memory-write transversal rules merge into one, trimming the router closer to the smallest shape that still states every rule Closes #278 Refs #419
Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...7188fc3) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…460) Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...7188fc3) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…rved Merges ai-driven-dev/aidd-cli into this repo as cli/ via git subtree — full commit history preserved (reachable via the subtree-merge commit's second parent, kept on the source branch chore/migrate-cli-into-framework, not deleted by this squash). cli/ stays self-contained: own package.json, own lockfile, not a pnpm workspace member. - release-please: cli added as a ninth package, release-type: node - CI: new cli-ci.yml (typecheck/lint/test/build/knip/jscpd), native path-filter on cli/** only - publish-cli in ci.yml, gated on release-please's paths_released, real npm OIDC trusted publishing (no NPM_TOKEN secret) - Fixed along the way: npm README stub bug, a pnpm-version/cache-path CI bug, a stale duplicate memory-sync script and its dead test, hardcoded personal paths, dead ISSUE_TEMPLATE/CONTRIBUTING.md left over from the standalone repo - ai-driven-dev/aidd-cli stays active, unarchived, until a real release from here is proven end to end Refs #448. Full plan, phase files, and audit reports: cli/aidd_docs/tasks/2026_07/2026_07_20_migrate-cli-into-framework/
package.json never had repository/homepage/bugs fields at all (the migration plan assumed they existed and needed updating — they didn't exist). Added them pointing at ai-driven-dev/framework with directory: "cli" so npm's package page links to the right subtree. aidd_docs/README.md (the standard scaffold every aidd setup generates) had one stale link to the old standalone repo.
package.json was missing license (npm shows this prominently on the package page) and keywords (npm search discoverability). license: MIT matches framework's own root LICENSE — same repo, same license, no ambiguity to resolve.
…474) Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.1 to 4.37.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@7188fc3...e4fba86) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.4.7 to 2.5.5. - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.5/packages/@biomejs/biome) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-version: 2.5.5 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [fast-check](https://github.com/dubzzz/fast-check/tree/HEAD/packages/fast-check) from 4.7.0 to 4.9.0. - [Release notes](https://github.com/dubzzz/fast-check/releases) - [Changelog](https://github.com/dubzzz/fast-check/blob/main/packages/fast-check/CHANGELOG.md) - [Commits](https://github.com/dubzzz/fast-check/commits/v4.9.0/packages/fast-check) --- updated-dependencies: - dependency-name: fast-check dependency-version: 4.9.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.16.1 to 6.27.0. - [Release notes](https://github.com/webpro-nl/knip/releases) - [Commits](https://github.com/webpro-nl/knip/commits/knip@6.27.0/packages/knip) --- updated-dependencies: - dependency-name: knip dependency-version: 6.27.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [jscpd](https://github.com/kucherenko/jscpd/tree/HEAD/rust/jscpd) from 5.0.7 to 5.0.12. - [Release notes](https://github.com/kucherenko/jscpd/releases) - [Changelog](https://github.com/kucherenko/jscpd/blob/master/CHANGELOG.md) - [Commits](https://github.com/kucherenko/jscpd/commits/v5.0.12/rust/jscpd) --- updated-dependencies: - dependency-name: jscpd dependency-version: 5.0.12 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.1 to 4.37.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@7188fc3...e4fba86) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* docs(framework): update README title and tagline Align the project messaging with the framework's current branding and focus. * chore: release main (#453) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * refactor(aidd-context): action citations use markdown links, not @ Convert every `@../` reference in the action files of skill-generate and project-memory to standard markdown links `[name](path)`, so citations are clickable in editors and on GitHub. Propagate the convention to the contract so generated skills follow suit: R6 now mandates a markdown link, R8 drops the `@`-chain wording, and review-protocol says "cited references". Functional `@`-imports in the memory block (`@aidd_docs/...`) are untouched: those are load mechanics, not citations. Sync docs/CATALOG.md skill-generate action names to the shipped set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177RKF7c5Wpv329Q7QYquV2 * docs(framework): reposition readme as agnostic token-optimized framework (#440) * feat(aidd-context): add research and apply actions to cook skill Extend 12-cook from author-only to a fuller loop: - 03-research: refine the target recipe via a checklist, fan out one agent per scouting angle, then present alternatives / coverage gaps / counter-intuitive wins plus a recommendation. Ephemeral, never writes. - 04-apply: execute an existing recipe against the project via an agent that tracks the steps as a todo list and confirms before mutating. - upsert: qualitative overlap guard (none/partial/high) that suggests updating an existing recipe instead of creating a duplicate. - recipe-template: steps become "#### N)" headings with what/why, actionable how-bullets, and a mandatory concrete example (command with output, snippet, or screenshot/video). New references/assets: research-playbook, refine-goal-checklist, research-checklist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(aidd-context): verify each research candidate before presenting Add a Verify step to cook's 03-research: after curation, spawn one agent per surviving candidate to confirm it exists, capture its official link, and record its latest state. Drop anything that cannot be confirmed against an official source. This makes the checklist's "Confirm claims" real and guards the recipe against hallucinated tools or methods. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): add token optimization recipe Add recipes/token-optimization.md: a ranked list of token-saving tips for AI coding assistants (measure first, trim CLAUDE.md, caveman, RTK/SNIP, CLI over MCP, progressive disclosure, compact, model routing, cap thinking), each tip in the new template format with a concrete example and an official link verified to exist. Register it in the recipes index. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): extract recipe contract and tighten cook template Move the recipe authoring rules out of the template's HTML comment into references/recipe-contract.md, cited by SKILL.md and upsert. The template keeps only the scaffold. - Steps heading carries the goal ("## Steps to <outcome>") and each step opens with an emoji. - Step how-bullets follow where-it-is -> install-from-URL -> how-to-use, benefit-first and filler-free. - "## Why" is short, keyword-led, and bold. - SKILL.md description is shorter and high-level, triggered on "recipe" / "cook" / "/cook". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(aidd-context): repair 12-cook SKILL.md YAML frontmatter The description contained an unquoted "sheets: list" colon, which YAML parses as a mapping value and rejects ("mapping values are not allowed here"), breaking the skill's frontmatter. Replace the colon with a dash. The bug pre-dated this branch (same colon on main). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): refine cook recipe structure and apply R13 to cook Review feedback on the cook skill, plus the new includes convention: - Recipe template drops the Time field, groups steps under three level subheadings (Beginner / Intermediate / Expert), and aligns the Level enum to Expert. - upsert now runs `research` (03) first for any new or substantial recipe and drafts only from its verified results, never from memory — the "always research" rule was being skipped as a routing note. - Apply R13 (explicit, scoped includes): the action-specific research aids move out of SKILL.md and are cited from 03-research in fenced blocks; refine-goal-checklist is renamed research-goal-checklist; SKILL.md lists only the global recipe-template and recipe-contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): make recipe level subheadings optional Level grouping was effectively mandatory. Make the contract state it is optional: group steps under Beginner/Intermediate/Expert only when the recipe spans difficulty levels and grouping helps; a short or single-level recipe lists its steps directly. Relax the upsert Test accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): conform token-optimization recipe to recipe contract Align the recipe with references/recipe-contract.md: drop the Time row (no longer in the template), name the section "Steps to <outcome>" instead of a bare Steps, remove the "ranked by impact" filler line, and give each step how-bullets plus a concrete example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(framework): split measure steps per tool and drop metadata table Apply review of the token-optimization recipe: - Goal becomes a plain sentence (no blockquote). - Drop the Level/Prerequisites table; level now lives in the subheadings. - Split the bundled "Measure first" step into one step per tool (/context, /cost, /insights, an analytics tool) — one subheading per action. - Each measure step says how to use and read the command, and carries an example output; the analytics step uses a real prompt-analytics run (volumes rounded, cost omitted) showing cache reads dominate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): drop the Goal label from token-optimization The recipe opens with a plain description sentence, no "Goal:" label. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): recipe header = description sentence, no table Rules extracted from the token-optimization recipe review: - A recipe opens with the H1 title then one plain description sentence — no "Goal:" label, no blockquote, no metadata table. list/upsert parse that sentence; the index becomes `| Recipe | Description |` (Level was dropped, level now lives only in the optional subheadings). - One step = one action; never bundle several tools or commands under a single heading. - Every step's example shows real command output (from docs or by running it); the research playbook now requires capturing a real example and marking interactive output for the human to paste. - `## Why` is one idea per line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): use the real prompt-analytics dashboard image Replace the fabricated text output with the tool's official dashboard screenshot (cost by token type makes "cache reads dominate" visible). The slash-command placeholders now ask for a screenshot of the real output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): prefer images over text examples A real screenshot or GIF is unfakeable, faithful, and credible, where a text example can be approximated (the failure we hit). Make the contract and the research playbook prefer an image that matches the action — e.g. a tool's official screenshot — falling back to real text output only when no image exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): show real tool usage in token-optimization - caveman: show its real `/caveman` invocation (and modes), not just a before/after. - RTK: a Mermaid diagram of the proxy flow + the real `rtk <command>` syntax (was a wrong `rtk proxy …`) and the README's own 200->10 token saving. - CLI vs MCP: a comparison table instead of prose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): add tool-example rules to recipe contract From the token-optimization review: - Show how to invoke a tool (its real command or slash invocation). - Reuse the canonical example from the tool's own site or README, never an invented one. - A "prefer X over Y" step uses a comparison table, not prose. - A structural or flow concept (proxy, pipeline, architecture) gets a small Mermaid diagram. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): use caveman's real before/after example Replace the invented caveman before/after with the verbatim example from its README (rule F: reuse the tool's own canonical example). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): numbered actions, valid-JSON config, tighter sections - Actions are numbered lists; descriptions are prose (no description-bullets). - settings.json shown as valid JSON, not a bash fragment. - RTK Mermaid uses concrete values (cargo test ~25k -> ~2.5k tokens). - CLI vs MCP notes MCP tool/context selection (cheaper, still slower). - Step 10 reworded plainly ("load knowledge on demand"). - Drop the weak Verify and the redundant Related; add a short conclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): add bullet/diagram/config/section rules to contract From the token-optimization review: - Actions are a numbered list; descriptions and indications are prose, never bullets. - A Mermaid diagram carries concrete example values. - A config example uses the file's real syntax (valid JSON for settings.json, valid YAML for frontmatter, ...). - Reuse a tool's canonical example captured verbatim — never a paraphrase or on the strength of a summary that one exists. - No `## Related` section (links live inline); `## Verify` is optional; recipes may end with a short conclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): expert steps use verified Claude Code config - Step 10 now audits which skills/tools run via `Ctrl+O` (toggles the detailed transcript), instead of the vaguer "load on demand". - Step 11 routes by difficulty with the real `model:` frontmatter field on a skill or agent (haiku/sonnet/opus/inherit) — example agent pinned to haiku. Both verified against the official Claude Code docs (interactive-mode, skills, sub-agents). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): point the instruction-file step at AGENTS.md Use the framework's own concise AGENTS.md as the worked example for a trimmed instruction file, with its real rules (answer first, no narration), instead of a generic snippet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): apply analyses a recipe and asks before acting A recipe is often a human tutorial, not an agent-runnable procedure. So apply now reads the recipe, classifies each step agent-doable vs human-only, and asks the user what to do before any change — then runs the chosen agent-doable steps and reports the human-only ones. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): add five verified token levers to the recipe From a research pass, all verified against official Claude Code docs: - Plan mode (Shift+Tab ×2) — avoid execution-phase rework. - `/clear` between tasks — drop stale context instead of re-billing it. - Deny reads (`permissions.deny`) — keep vendor/dist/secrets out of context; the official answer to the unshipped `.claudeignore`. - Subagent offload — run high-volume ops in an isolated context. - Protect cache hits — avoid mid-task model/MCP/effort switches that invalidate the cache (closes the loop on "cache reads dominate"). Also fold the built-in `outputStyle: concise` into the "talk less" step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(aidd-context): bundle cook recipes in skill * feat(framework): publish AIDDy v2 pet * docs(framework): reposition readme as agnostic token-optimized framework * feat: avoid uncached AGENTS.md * docs: add fixing behavior for assertions --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): restructure 10-learn into a 5-action router (#455) Splits source out of gather so origin selection (conversation, file, diff, review) is explicit before extraction runs, and renames assets/decision-template.md to adr-template.md to match its actual output. CATALOG.md's 10-learn index is regenerated to match. Folds in fixes found during a headless claude+codex audit of the skill (13 scenarios x 2 tools): - sync locates update_memory.js instead of assuming a fixed path that doesn't exist in either tool's real install layout - never scaffold the memory bank, even under a blanket write pre-approval - sync's own git-diff-cached test reflects a pre-staged index instead of demanding it be empty - sources.md again defaults to conversation when no hint is given, matching pre-refactor behavior, instead of always requiring context inference - the two memory-write transversal rules merge into one, trimming the router closer to the smallest shape that still states every rule Closes #278 Refs #419 * ci(deps): bump github/codeql-action/analyze from 4.37.0 to 4.37.1 (#456) Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...7188fc3) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(deps): bump github/codeql-action/autobuild from 4.37.0 to 4.37.1 (#460) Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...7188fc3) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(cli): migrate aidd-cli into framework as cli/, full history preserved Merges ai-driven-dev/aidd-cli into this repo as cli/ via git subtree — full commit history preserved (reachable via the subtree-merge commit's second parent, kept on the source branch chore/migrate-cli-into-framework, not deleted by this squash). cli/ stays self-contained: own package.json, own lockfile, not a pnpm workspace member. - release-please: cli added as a ninth package, release-type: node - CI: new cli-ci.yml (typecheck/lint/test/build/knip/jscpd), native path-filter on cli/** only - publish-cli in ci.yml, gated on release-please's paths_released, real npm OIDC trusted publishing (no NPM_TOKEN secret) - Fixed along the way: npm README stub bug, a pnpm-version/cache-path CI bug, a stale duplicate memory-sync script and its dead test, hardcoded personal paths, dead ISSUE_TEMPLATE/CONTRIBUTING.md left over from the standalone repo - ai-driven-dev/aidd-cli stays active, unarchived, until a real release from here is proven end to end Refs #448. Full plan, phase files, and audit reports: cli/aidd_docs/tasks/2026_07/2026_07_20_migrate-cli-into-framework/ * fix(cli): repoint self-references from aidd-cli to framework package.json never had repository/homepage/bugs fields at all (the migration plan assumed they existed and needed updating — they didn't exist). Added them pointing at ai-driven-dev/framework with directory: "cli" so npm's package page links to the right subtree. aidd_docs/README.md (the standard scaffold every aidd setup generates) had one stale link to the old standalone repo. * fix(cli): add license and keywords for the npm package page package.json was missing license (npm shows this prominently on the package page) and keywords (npm search discoverability). license: MIT matches framework's own root LICENSE — same repo, same license, no ambiguity to resolve. * chore: release main (#485) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * fix(cli): restore correct version after release-please regression (#488) release-please's PR #485 computed cli's next version as 4.0.0 instead of bumping forward from 5.1.3. Root cause: cli had no prior cli-v* tag to anchor its version walk, so it fell through to an old, otherwise-already-consumed root Release-As: 4.0.0 directive (commit 5b0fc9d, framework's own major-bump forcing from May 2026, unrelated to cli) and applied it to the new component. Both npm and GitHub Packages correctly rejected the resulting publish (existing 4.0.0 from the pre-migration standalone repo, real current version is 5.1.3 on both registries — untouched). Bad tag/release cli-v4.0.0 already deleted. Reverts cli/package.json and the manifest's cli entry to 5.1.3, and drops the bogus CHANGELOG entry. A cli-v5.1.3 tag/release will be created on top of this commit to anchor future release-please runs so the walk never reaches that old directive again. * chore: release main (#489) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * fix(cli): restore correct version again after second regression, re-anchor for release-please (#493) A second, unrelated release-please cycle (PR #489, "chore: release main") auto-merged moments after #488 and independently recomputed cli's version, reintroducing 4.0.0 the same way #485 did, even though that run's own log showed it correctly reading 5.1.3 from the manifest as the baseline. The manifest baseline alone does not bound the version-bump walk; only a matching cli-v* release does, and none existed yet when that run started. Both npm and GitHub Packages rejected the resulting publish again (pre-existing 4.0.0, same as before), still untouched at the real 5.1.3. Bad tag/release cli-v4.0.0 deleted again. A cli-v5.1.3 anchor release now exists (pointing at bdd12c6, the commit right before the cli migration landed), created before this commit merges, so the next release-please run has a real boundary to stop its walk at and can no longer reach the old, already-consumed version-forcing directive from commit 5b0fc9d (framework's own unrelated major-bump forcing from May 2026, targeting 4.0.0 for the root package only, not cli). This commit also serves as the nudge for release-please to re-evaluate cli against that new anchor and correctly propose 5.1.4 next, carrying the real pending fixes, migration, repoint, license and keywords, into a real changelog entry and npm and GHP publish. * chore: release main (#494) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * fix(release-please): exclude manifest file from root path tracking (#497) Any commit touching .release-please-manifest.json (a root-level file) counts toward the "." package's own version bump, since root has no path restriction. Today's cli version-regression fixes needed to hand-edit that file directly and, as a side effect, cut two extra framework releases (v5.5.3, v5.5.4) that carried no real content change for root. exclude-paths (documented in release-please's manifest schema, ReleaserConfigOptions) skips a commit for a given package when every file it touches falls under the listed paths. Scoped to the "." package only, so cli's and the plugins' own path-based tracking are unaffected. * chore: release main (#498) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Alex <8973343+alexsoyes@users.noreply.github.com> Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> Co-authored-by: Baptiste LAFOURCADE <baptiste.lafourcade@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Baptiste LAFOURCADE <119650761+blafourcade@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…tion (#506) Two confirmed bugs on aidd restore / aidd ai restore / aidd ide restore: A2 (scope leak): RestoreUseCase.runPluginRestore never threaded ctx.toolIds into RestoreAllPluginsUseCase, so `--tool X` still restored every other installed AI tool's plugin files unscoped. A3 (double materialization): RestoreAllUseCase ran two independent, unconditional passes over the same (tool, plugin) pairs on every global `aidd restore` — the second pass ignored --tool scope, ignored fileFilter entirely, and for built-tree tools (cursor/opencode) wrote every plugin file to disk twice with no hash guard. RestoreAllPluginsUseCase now takes an optional toolIds filter and returns {totalFiles, pluginNames} so the single remaining pass can still report "Restored plugins: x, y". RestorePluginUseCase deleted — its one caller was the removed second pass (confirmed via grep). 2048/2048 tests green (2042 existing + 6 new regression tests), tsc clean. Pre-fix reproduction verified via git stash before each fix (double materialization count, interactive file selection not excluding plugins). Ported from the archived aidd-cli repo (BUG-E3-02 + SPIKE-E3-01, pre-migration branches) — original work predates the framework migration (PR #462).
…505) outDir for this path is always .aidd/cache/built/<marketplace>/<target>, an aidd-owned disposable cache, never a user directory — collision here means "cache from a previous build exists", not data at risk. Adds a comment stating that explicitly and a regression test (real FlatBuildStrategy, not the fake buildFor stub) pinning the collision-bypass so it fails if force is ever flipped or outDir stops being cache-only. Ported from the archived aidd-cli repo (BUG-E2-01, pre-migration branch docs/e2-01-force-cache-rebuild) — original work predates the framework migration (PR #462).
…olUseCase (#507) (#508) StatusAllUseCase, RestoreAllUseCase, and UpdateAllUseCase each rebuilt their own duplicate of a collaborator deps.ts already constructs and shares with ai.ts/ide.ts (deps.statusUseCase, deps.restoreUseCase, deps.updateOneToolUseCase consumed directly by those commands). Two of the four duplicate sites rebuilt on every execute() call, not just once. Confirmed all 3 target classes stateless before sharing them as singletons (no memo maps, no accumulator fields — BulkConflictState is passed as an execute() parameter, never stored on UpdateOneToolUseCase). Each class now receives the real instance by constructor injection; every now-dead raw-sub-dependency param removed rather than left unused. grep confirms zero remaining "new StatusUseCase/RestoreUseCase/ UpdateOneToolUseCase" outside deps.ts's 3 original construction sites. 2048/2048 tests pass unmodified — zero behavior change, pure DI wiring. Stacked on fix/e3-02-restore-plugin-scope-dedup (touches the same restore-all-use-case.ts) — needs that branch merged first. Ported/implemented fresh from the archived aidd-cli repo's plan (SPIKE-E1-01 + BUG-E1-02, pre-migration) — original work predates the framework migration (PR #462).
…or name collisions (#509) Two genuinely different symbols shared the same name, confirmed via full read of both sides of each collision — real friction, not cosmetic: mode-b-flat-materialization-translator.ts already had to locally alias one of them (`PluginTranslator as PluginTranslatorHelper`) just to compile. buildClaudeStyleMarketplaceEntry: the catalog-row builder in marketplace-strategy-helpers.ts (framework build time) renamed to buildClaudeStyleCatalogEntry — pairs naturally with the neighboring buildClaudeStyleMarketplace. The domain/capabilities settings-entry builder (install time, consumed by claude.ts/copilot.ts) keeps its name. PluginTranslator: the domain content-format-conversion class renamed to PluginContentTranslator (file renamed to match). The application-layer strategy interface (Section C protected pattern, 4 implementer/factory sites) is untouched. Pure rename, zero behavior change. 2049/2049 tests pass with zero assertion changes; grep confirms each name now maps to exactly one symbol.
… internal discovery map (#510) Restructures 01-brainstorm from a flat probing.md + question-angles.md pair into a discovery-map/readiness/interview-depth apparatus, moves finalize output to a discovery-brief.md template, and hides internal process vocabulary from user-facing text by default. Fixes applied on top of the base restructure, found via headless and live testing: - restore the "state a leaning + tradeoff, even unprompted" rule that the restructure had silently dropped (SKILL.md, 03-integrate.md) - restore the "stay at the idea's altitude, leave finer how-to as a flagged assumption" guardrail (references/probing.md) - fold no longer silently overwrites a contradicted fact — confirms which stands before folding (03-integrate.md) - drop the "use the user's language" instruction, never present before this refactor either - trim 04-finalize's Ask step and Test section to stop restating what the linked reference files already say Closes #504 Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* docs(framework): update README title and tagline Align the project messaging with the framework's current branding and focus. * chore: release main (#453) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * refactor(aidd-context): action citations use markdown links, not @ Convert every `@../` reference in the action files of skill-generate and project-memory to standard markdown links `[name](path)`, so citations are clickable in editors and on GitHub. Propagate the convention to the contract so generated skills follow suit: R6 now mandates a markdown link, R8 drops the `@`-chain wording, and review-protocol says "cited references". Functional `@`-imports in the memory block (`@aidd_docs/...`) are untouched: those are load mechanics, not citations. Sync docs/CATALOG.md skill-generate action names to the shipped set. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177RKF7c5Wpv329Q7QYquV2 * docs(framework): reposition readme as agnostic token-optimized framework (#440) * feat(aidd-context): add research and apply actions to cook skill Extend 12-cook from author-only to a fuller loop: - 03-research: refine the target recipe via a checklist, fan out one agent per scouting angle, then present alternatives / coverage gaps / counter-intuitive wins plus a recommendation. Ephemeral, never writes. - 04-apply: execute an existing recipe against the project via an agent that tracks the steps as a todo list and confirms before mutating. - upsert: qualitative overlap guard (none/partial/high) that suggests updating an existing recipe instead of creating a duplicate. - recipe-template: steps become "#### N)" headings with what/why, actionable how-bullets, and a mandatory concrete example (command with output, snippet, or screenshot/video). New references/assets: research-playbook, refine-goal-checklist, research-checklist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(aidd-context): verify each research candidate before presenting Add a Verify step to cook's 03-research: after curation, spawn one agent per surviving candidate to confirm it exists, capture its official link, and record its latest state. Drop anything that cannot be confirmed against an official source. This makes the checklist's "Confirm claims" real and guards the recipe against hallucinated tools or methods. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): add token optimization recipe Add recipes/token-optimization.md: a ranked list of token-saving tips for AI coding assistants (measure first, trim CLAUDE.md, caveman, RTK/SNIP, CLI over MCP, progressive disclosure, compact, model routing, cap thinking), each tip in the new template format with a concrete example and an official link verified to exist. Register it in the recipes index. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): extract recipe contract and tighten cook template Move the recipe authoring rules out of the template's HTML comment into references/recipe-contract.md, cited by SKILL.md and upsert. The template keeps only the scaffold. - Steps heading carries the goal ("## Steps to <outcome>") and each step opens with an emoji. - Step how-bullets follow where-it-is -> install-from-URL -> how-to-use, benefit-first and filler-free. - "## Why" is short, keyword-led, and bold. - SKILL.md description is shorter and high-level, triggered on "recipe" / "cook" / "/cook". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(aidd-context): repair 12-cook SKILL.md YAML frontmatter The description contained an unquoted "sheets: list" colon, which YAML parses as a mapping value and rejects ("mapping values are not allowed here"), breaking the skill's frontmatter. Replace the colon with a dash. The bug pre-dated this branch (same colon on main). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): refine cook recipe structure and apply R13 to cook Review feedback on the cook skill, plus the new includes convention: - Recipe template drops the Time field, groups steps under three level subheadings (Beginner / Intermediate / Expert), and aligns the Level enum to Expert. - upsert now runs `research` (03) first for any new or substantial recipe and drafts only from its verified results, never from memory — the "always research" rule was being skipped as a routing note. - Apply R13 (explicit, scoped includes): the action-specific research aids move out of SKILL.md and are cited from 03-research in fenced blocks; refine-goal-checklist is renamed research-goal-checklist; SKILL.md lists only the global recipe-template and recipe-contract. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): make recipe level subheadings optional Level grouping was effectively mandatory. Make the contract state it is optional: group steps under Beginner/Intermediate/Expert only when the recipe spans difficulty levels and grouping helps; a short or single-level recipe lists its steps directly. Relax the upsert Test accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): conform token-optimization recipe to recipe contract Align the recipe with references/recipe-contract.md: drop the Time row (no longer in the template), name the section "Steps to <outcome>" instead of a bare Steps, remove the "ranked by impact" filler line, and give each step how-bullets plus a concrete example. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(framework): split measure steps per tool and drop metadata table Apply review of the token-optimization recipe: - Goal becomes a plain sentence (no blockquote). - Drop the Level/Prerequisites table; level now lives in the subheadings. - Split the bundled "Measure first" step into one step per tool (/context, /cost, /insights, an analytics tool) — one subheading per action. - Each measure step says how to use and read the command, and carries an example output; the analytics step uses a real prompt-analytics run (volumes rounded, cost omitted) showing cache reads dominate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): drop the Goal label from token-optimization The recipe opens with a plain description sentence, no "Goal:" label. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): recipe header = description sentence, no table Rules extracted from the token-optimization recipe review: - A recipe opens with the H1 title then one plain description sentence — no "Goal:" label, no blockquote, no metadata table. list/upsert parse that sentence; the index becomes `| Recipe | Description |` (Level was dropped, level now lives only in the optional subheadings). - One step = one action; never bundle several tools or commands under a single heading. - Every step's example shows real command output (from docs or by running it); the research playbook now requires capturing a real example and marking interactive output for the human to paste. - `## Why` is one idea per line. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): use the real prompt-analytics dashboard image Replace the fabricated text output with the tool's official dashboard screenshot (cost by token type makes "cache reads dominate" visible). The slash-command placeholders now ask for a screenshot of the real output. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): prefer images over text examples A real screenshot or GIF is unfakeable, faithful, and credible, where a text example can be approximated (the failure we hit). Make the contract and the research playbook prefer an image that matches the action — e.g. a tool's official screenshot — falling back to real text output only when no image exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): show real tool usage in token-optimization - caveman: show its real `/caveman` invocation (and modes), not just a before/after. - RTK: a Mermaid diagram of the proxy flow + the real `rtk <command>` syntax (was a wrong `rtk proxy …`) and the README's own 200->10 token saving. - CLI vs MCP: a comparison table instead of prose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): add tool-example rules to recipe contract From the token-optimization review: - Show how to invoke a tool (its real command or slash invocation). - Reuse the canonical example from the tool's own site or README, never an invented one. - A "prefer X over Y" step uses a comparison table, not prose. - A structural or flow concept (proxy, pipeline, architecture) gets a small Mermaid diagram. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): use caveman's real before/after example Replace the invented caveman before/after with the verbatim example from its README (rule F: reuse the tool's own canonical example). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): numbered actions, valid-JSON config, tighter sections - Actions are numbered lists; descriptions are prose (no description-bullets). - settings.json shown as valid JSON, not a bash fragment. - RTK Mermaid uses concrete values (cargo test ~25k -> ~2.5k tokens). - CLI vs MCP notes MCP tool/context selection (cheaper, still slower). - Step 10 reworded plainly ("load knowledge on demand"). - Drop the weak Verify and the redundant Related; add a short conclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): add bullet/diagram/config/section rules to contract From the token-optimization review: - Actions are a numbered list; descriptions and indications are prose, never bullets. - A Mermaid diagram carries concrete example values. - A config example uses the file's real syntax (valid JSON for settings.json, valid YAML for frontmatter, ...). - Reuse a tool's canonical example captured verbatim — never a paraphrase or on the strength of a summary that one exists. - No `## Related` section (links live inline); `## Verify` is optional; recipes may end with a short conclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): expert steps use verified Claude Code config - Step 10 now audits which skills/tools run via `Ctrl+O` (toggles the detailed transcript), instead of the vaguer "load on demand". - Step 11 routes by difficulty with the real `model:` frontmatter field on a skill or agent (haiku/sonnet/opus/inherit) — example agent pinned to haiku. Both verified against the official Claude Code docs (interactive-mode, skills, sub-agents). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): point the instruction-file step at AGENTS.md Use the framework's own concise AGENTS.md as the worked example for a trimmed instruction file, with its real rules (answer first, no narration), instead of a generic snippet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): apply analyses a recipe and asks before acting A recipe is often a human tutorial, not an agent-runnable procedure. So apply now reads the recipe, classifies each step agent-doable vs human-only, and asks the user what to do before any change — then runs the chosen agent-doable steps and reports the human-only ones. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(framework): add five verified token levers to the recipe From a research pass, all verified against official Claude Code docs: - Plan mode (Shift+Tab ×2) — avoid execution-phase rework. - `/clear` between tasks — drop stale context instead of re-billing it. - Deny reads (`permissions.deny`) — keep vendor/dist/secrets out of context; the official answer to the unshipped `.claudeignore`. - Subagent offload — run high-volume ops in an isolated context. - Protect cache hits — avoid mid-task model/MCP/effort switches that invalidate the cache (closes the loop on "cache reads dominate"). Also fold the built-in `outputStyle: concise` into the "talk less" step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(aidd-context): bundle cook recipes in skill * feat(framework): publish AIDDy v2 pet * docs(framework): reposition readme as agnostic token-optimized framework * feat: avoid uncached AGENTS.md * docs: add fixing behavior for assertions --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * refactor(aidd-context): restructure 10-learn into a 5-action router (#455) Splits source out of gather so origin selection (conversation, file, diff, review) is explicit before extraction runs, and renames assets/decision-template.md to adr-template.md to match its actual output. CATALOG.md's 10-learn index is regenerated to match. Folds in fixes found during a headless claude+codex audit of the skill (13 scenarios x 2 tools): - sync locates update_memory.js instead of assuming a fixed path that doesn't exist in either tool's real install layout - never scaffold the memory bank, even under a blanket write pre-approval - sync's own git-diff-cached test reflects a pre-staged index instead of demanding it be empty - sources.md again defaults to conversation when no hint is given, matching pre-refactor behavior, instead of always requiring context inference - the two memory-write transversal rules merge into one, trimming the router closer to the smallest shape that still states every rule Closes #278 Refs #419 * ci(deps): bump github/codeql-action/analyze from 4.37.0 to 4.37.1 (#456) Bumps [github/codeql-action/analyze](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...7188fc3) --- updated-dependencies: - dependency-name: github/codeql-action/analyze dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * ci(deps): bump github/codeql-action/autobuild from 4.37.0 to 4.37.1 (#460) Bumps [github/codeql-action/autobuild](https://github.com/github/codeql-action) from 4.37.0 to 4.37.1. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...7188fc3) --- updated-dependencies: - dependency-name: github/codeql-action/autobuild dependency-version: 4.37.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(cli): migrate aidd-cli into framework as cli/, full history preserved Merges ai-driven-dev/aidd-cli into this repo as cli/ via git subtree — full commit history preserved (reachable via the subtree-merge commit's second parent, kept on the source branch chore/migrate-cli-into-framework, not deleted by this squash). cli/ stays self-contained: own package.json, own lockfile, not a pnpm workspace member. - release-please: cli added as a ninth package, release-type: node - CI: new cli-ci.yml (typecheck/lint/test/build/knip/jscpd), native path-filter on cli/** only - publish-cli in ci.yml, gated on release-please's paths_released, real npm OIDC trusted publishing (no NPM_TOKEN secret) - Fixed along the way: npm README stub bug, a pnpm-version/cache-path CI bug, a stale duplicate memory-sync script and its dead test, hardcoded personal paths, dead ISSUE_TEMPLATE/CONTRIBUTING.md left over from the standalone repo - ai-driven-dev/aidd-cli stays active, unarchived, until a real release from here is proven end to end Refs #448. Full plan, phase files, and audit reports: cli/aidd_docs/tasks/2026_07/2026_07_20_migrate-cli-into-framework/ * fix(cli): repoint self-references from aidd-cli to framework package.json never had repository/homepage/bugs fields at all (the migration plan assumed they existed and needed updating — they didn't exist). Added them pointing at ai-driven-dev/framework with directory: "cli" so npm's package page links to the right subtree. aidd_docs/README.md (the standard scaffold every aidd setup generates) had one stale link to the old standalone repo. * fix(cli): add license and keywords for the npm package page package.json was missing license (npm shows this prominently on the package page) and keywords (npm search discoverability). license: MIT matches framework's own root LICENSE — same repo, same license, no ambiguity to resolve. * chore: release main (#485) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * fix(cli): restore correct version after release-please regression (#488) release-please's PR #485 computed cli's next version as 4.0.0 instead of bumping forward from 5.1.3. Root cause: cli had no prior cli-v* tag to anchor its version walk, so it fell through to an old, otherwise-already-consumed root Release-As: 4.0.0 directive (commit 5b0fc9d, framework's own major-bump forcing from May 2026, unrelated to cli) and applied it to the new component. Both npm and GitHub Packages correctly rejected the resulting publish (existing 4.0.0 from the pre-migration standalone repo, real current version is 5.1.3 on both registries — untouched). Bad tag/release cli-v4.0.0 already deleted. Reverts cli/package.json and the manifest's cli entry to 5.1.3, and drops the bogus CHANGELOG entry. A cli-v5.1.3 tag/release will be created on top of this commit to anchor future release-please runs so the walk never reaches that old directive again. * chore: release main (#489) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * fix(cli): restore correct version again after second regression, re-anchor for release-please (#493) A second, unrelated release-please cycle (PR #489, "chore: release main") auto-merged moments after #488 and independently recomputed cli's version, reintroducing 4.0.0 the same way #485 did, even though that run's own log showed it correctly reading 5.1.3 from the manifest as the baseline. The manifest baseline alone does not bound the version-bump walk; only a matching cli-v* release does, and none existed yet when that run started. Both npm and GitHub Packages rejected the resulting publish again (pre-existing 4.0.0, same as before), still untouched at the real 5.1.3. Bad tag/release cli-v4.0.0 deleted again. A cli-v5.1.3 anchor release now exists (pointing at bdd12c6, the commit right before the cli migration landed), created before this commit merges, so the next release-please run has a real boundary to stop its walk at and can no longer reach the old, already-consumed version-forcing directive from commit 5b0fc9d (framework's own unrelated major-bump forcing from May 2026, targeting 4.0.0 for the root package only, not cli). This commit also serves as the nudge for release-please to re-evaluate cli against that new anchor and correctly propose 5.1.4 next, carrying the real pending fixes, migration, repoint, license and keywords, into a real changelog entry and npm and GHP publish. * chore: release main (#494) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * fix(release-please): exclude manifest file from root path tracking (#497) Any commit touching .release-please-manifest.json (a root-level file) counts toward the "." package's own version bump, since root has no path restriction. Today's cli version-regression fixes needed to hand-edit that file directly and, as a side effect, cut two extra framework releases (v5.5.3, v5.5.4) that carried no real content change for root. exclude-paths (documented in release-please's manifest schema, ReleaserConfigOptions) skips a commit for a given package when every file it touches falls under the listed paths. Scoped to the "." package only, so cli's and the plugins' own path-based tracking are unaffected. * chore: release main (#498) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> * fix(docs): resolve real broken markdown links, drop temporary link-check excludes (#500) Two real, distinct issues were hiding behind the migration's temporary --ignore flags: - 8 links in plugin-architecture-master.md pointed at real, existing sibling files whose names contain a literal '#' (e.g. 2026_04_27-#260-plugin-architecture-part-1.md). Markdown link parsers treat an unescaped '#' as a URL fragment separator, truncating the path before the file check ever runs. URL-encoding it as %23 fixes it; the checker already decodeURI()s targets. - The remaining ~235 flagged references under cli/tests/fixtures/** and cli/aidd_docs/tasks/** are not a mix of real links and shorthand: cli/tests/fixtures/** is synthetic mock trees that never materialize every file they reference (confirmed: the full test suite passes with those template files absent), and cli/aidd_docs/tasks/** is a historical record whose @path references and inline rewrite-rule examples are expected to drift as the codebase evolves after the fact. Scrubbing them file-by-file would just rewrite historical docs that were accurate when written. Moved that exemption into the checker itself (DEFAULT_IGNORES, applied on top of any --ignore given) rather than the hook command, so `node scripts/check-markdown-links.js` with no flags is the thing that's actually clean. lefthook.yml's markdown-links command now carries no --ignore flags and no TEMPORARY comment. * chore: release main (#501) Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: Alex <8973343+alexsoyes@users.noreply.github.com> Co-authored-by: aidd-bot[bot] <290648294+aidd-bot[bot]@users.noreply.github.com> Co-authored-by: Baptiste LAFOURCADE <baptiste.lafourcade@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Baptiste LAFOURCADE <119650761+blafourcade@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [github/codeql-action/init](https://github.com/github/codeql-action) from 4.37.0 to 4.37.3. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](github/codeql-action@99df26d...e4fba86) --- updated-dependencies: - dependency-name: github/codeql-action/init dependency-version: 4.37.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@9c091bb...3d3c42e) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.4.0 to 7.0.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@48b55a0...8207627) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.15 to 26.1.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.1.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…478) Bumps [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) from 19.8.1 to 21.2.1. - [Release notes](https://github.com/conventional-changelog/commitlint/releases) - [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/cli/CHANGELOG.md) - [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.2.1/@commitlint/cli) --- updated-dependencies: - dependency-name: "@commitlint/cli" dependency-version: 21.2.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [commander](https://github.com/tj/commander.js) from 12.1.0 to 15.0.0. - [Release notes](https://github.com/tj/commander.js/releases) - [Changelog](https://github.com/tj/commander.js/blob/master/CHANGELOG.md) - [Commits](tj/commander.js@v12.1.0...v15.0.0) --- updated-dependencies: - dependency-name: commander dependency-version: 15.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 7.0.2. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/commits) --- updated-dependencies: - dependency-name: typescript dependency-version: 7.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…egistry (#514) * fix(cli): derive framework build's SUPPORTED_TARGETS from the build registry framework.ts hand-copied a 5-name target list to validate --target, separate from FRAMEWORK_BUILD_REGISTRY (deps.ts) — the real target:mode routing source of truth already used one command down (createFrameworkBuildUseCase returning undefined for an unknown combo). No drift exists today (both list the same 5 targets), but nothing previously prevented one on the next target/mode addition or removal. deps.ts now exports SUPPORTED_BUILD_TARGETS, derived once from FRAMEWORK_BUILD_REGISTRY's keys. framework.ts imports it instead of maintaining its own copy. Pure refactor, zero behavior change. 2049/2049 tests pass, tsc clean. * fix(cli): move build target/mode list to domain, out of infrastructure Review feedback on the prior commit: framework.ts (application/commands) importing SUPPORTED_BUILD_TARGETS from infrastructure/deps.ts was backwards — "which target/mode pairs exist" is a pure data fact, not something requiring adapters or DI, so it belongs in domain and application should depend inward on it, not sideways through infrastructure. FRAMEWORK_BUILD_TARGET_MODES (+ derived SUPPORTED_BUILD_TARGETS) now lives in domain/models/framework-build.ts. deps.ts's FRAMEWORK_BUILD_REGISTRY keeps its own literal keys unchanged (the build-strategy wiring is legitimately infrastructure's job) — a new test (tests/infrastructure/framework-build-registry.unit.test.ts) instead asserts the registry's runtime behavior matches the domain list exactly, both directions, closing the drift risk between the two independently -authored sources. Confirmed via full grep audit: no other file under domain/ imports from application/ or infrastructure/ anywhere in the codebase. 2059/2059 tests pass (2049 + 10 new), tsc clean.
cli / Windows failed with every test passing - 39 files, 279 tests, 0 failures, exit 1 - on `[vitest-worker]: Timeout calling "onTaskUpdate"`. Measured on five runs between 2026-09-02 and 2026-09-04, including three plain merges to next and a re-run of the same commit, so it is neither a branch nor a one-off. Vitest transforms modules on its own main thread while every worker calls back into it after each test, and that call has a fixed 60 s timeout no configuration key exposes. On a 4 vCPU Windows runner whose e2e tests each spawn node dist/cli.js, the default pool queued that thread past 60 s. --max-workers=2 on that step alone. The Linux jobs keep the default: they have never produced this. The workflow also now lists itself in its own path filters. It did not, so this fix's first push ran every workflow but the one it changes, and merging it would have run none either - a change to how the suite runs, landing unverified. A red check that names no failing test is worse than a red check. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both closers compared the two names with `===`. skill-detection.cjs has two capture routes: Claude Code and Copilot write `aidd-dev:01-plan`, Cursor and Codex write the bare directory name `01-plan`. The end is captured by neither - it is read out of the text a skill echoes, and a skill always names itself in full. So on Cursor and Codex a step opened as `01-plan` met an end named `aidd-dev:01-plan`, matched nothing, and ran on as if the skill had never said it was done. One predicate now answers it for the step axis and the flow axis: equal outright, or equal once a `plugin:` prefix comes off, and only when one side carries no plugin at all. Two qualified names that disagree stay two skills. The cost is stated where the function lives: an unqualified `01-plan` closes whichever `01-plan` is open, whatever plugin it came from. The host threw the plugin away before this code saw the line. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`by_flow` decided a record's flow from journal intervals and nothing else. A record whose own transcript states the orchestrating skill it ran under was reported as belonging to no flow, however plainly it said so. Measured on this machine's sink, 30 days, 30,222 requests: `by_step` names `aidd-orchestrator:01-sdlc` on 2,220 requests as `tool-stated`, and `by_flow` placed 2,208 of them outside every flow. They come from a session whose journal holds six `step_end` lines and no `step_start` at all — the shape a session resumed after its context was compacted leaves behind, since nothing is invoked again and no hook fires, while the transcript goes on stating the step on every record it produces. `flowKeyOf` gains one fallback: a record no interval covers, whose own `step_attribution` is `tool-stated` and whose `step` names an orchestrating skill, joins a row for that skill. The row is a distinct kind, keyed on the name, carrying `attribution: "tool-stated"` and no `started_at` — a name is not a run, so it can neither tell two runs apart nor claim a single opening moment. An interval still wins where a record falls inside one, and the reason is granularity, not strength: `withStepBackfill` prefers a tool-stated step over a journal-interval one because there the question is *which skill*; here it is *which run*, and only an interval can say. Twelve records show it in the real data — tool-stated and inside the interval, they stay on the interval's row. `by_flow` still reconciles: the unnamed row drops by exactly what the new row gains. No envelope bump — the flow row gains a field a consumer may ignore, and no existing field changes meaning. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four places called `by_prompt` "the one breakdown that is complete by construction", three of them backed by one measurement: 1073 of 1073 records of one real session carrying a `prompt_id`. Measured the following day on this machine's own sink, 30,714 records: 845 carry none, 2.75%, and every one of the nine sessions has at least one. Of those 845, 34 were written before the CLI stamped a version, 810 by one session's reader before this resolution shipped, and exactly one by the current reader — an assistant line whose `parentUuid` chain reaches no line naming a prompt. The original measurement is also no longer reproducible: that transcript holds 230 assistant lines today where 1,073 records were read from it. What is true is that `by_prompt` is the one breakdown no host limit can empty — it waits on no journal, no identity file, no declaration, no host naming a skill. That is not the same as complete, and the difference has a mechanism. `storeNewCandidates` treats a candidate whose turn is already stored as a correction only when it carries a larger counter. A field the stored record lacks is not a correction, so a reader that later learns to resolve something an earlier one could not names nothing already stored. A record's field set is fixed the first time its turn is seen. Now stated where that code lives. Left as it is, with the arithmetic: of the 811 measured against the transcripts on disk, 720 name a request no transcript still holds and 90 would resolve today. Roughly 90 records in 30,714 is the whole prize, and enriching would mean appending a line whose counters equal one already stored, which `collapseSupersededTurns` picks between by counter weight and then by serialized content — a preference it would have to learn, for 0.3%. Prose only. No behaviour moves, no envelope field, no version. The behaviour was already guarded, correctly, in both directions. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`agentKeyOf` read `agent_name === undefined` as the main thread, whatever the tool. Only Claude Code's reader ever sets that field — the Codex, Copilot and OpenCode readers set it nowhere — so on those three tools every record was reported as the main thread. Not a small error: 100% of the axis, asserted on no evidence. The contract said so out loud and called it correct. `claude-code-transcript.ts` already states the right rule twelve lines below, for `step`: its absence yields no step at all rather than asserting no skill ran. A record with no agent name now joins one of two rows, and only the tool's own declaration says which: `main-thread` is a tool that names agents saying this record belongs to none of them, `not-stated` is a tool whose route never names one. `TelemetryRouteSupply` gains `agentName`, declared beside `toolStatedStep` and checked against real captures by the same honesty guard — a route claiming an agent name its reader never sets fails there. The declaration is read rather than the record because the record cannot carry the absence: a tool that never names an agent writes exactly what a main-thread line writes. Still unfixed, and stated on the row instead: a line marked as a subagent's that carries no agent name reads as the main thread. Measured across 1,852 transcripts, 157 of 122,637 subagent lines, 0.07%. Closing it needs a field no already-stored record could ever gain. `accumulate`'s per-record parameters became a `RecordContext` — nine positional arguments in a fixed order is a call nobody checks by eye, and the linter's own function-length rule made the point at the ninth. Bundle budget raised 593 -> 596 KB, measured 593.8, the same 2.2 KB headroom the raise before it left. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four cases in `smoke-tools.sh` damaged a file and proved nothing about it. They did not know which file they damaged. Four places picked one with `find … | head -1`, and `find` answers in directory order — not sorted, not the same on two filesystems — so each run corrupted whichever file came back first. And the three restore cases never checked the repair: each appended drift, ran `restore --force`, and asserted exit 0. A `restore --force` that exits 0 having restored nothing is precisely the failure #762 fixed inside the command, so the case meant to cover it would have passed throughout. The `ide` case was worse: its drift was a bare newline, which `grep` cannot see, so no check on that file was possible at all. `first_file` (`LC_ALL=C sort | head -1`) replaces every unsorted pick. One named marker, `SMOKE_DRIFT`, is what the three cases append, and `repaired` fails when the mark is still there after the restore, or when nothing was drifted to repair. The harness is not run by CI, so the three guards read its text, the way `smoke-harness-isolation.unit.test.ts` already guards the home sandbox. Each was written first and failed for the reason it names; `bash -n` and `shellcheck -S error` are both clean. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`architecture.md` has stated the rule since the per-tool distributions landed — a skill never links outside itself, because the tree ships both flat and as a marketplace and no relative path survives both — and nothing enforced it. The one checker that looks at links cannot: `check-markdown-links.js` resolves every path against this repository, where the target does exist. A link reaching out of a skill is green there and dead in every installed copy. Five had accumulated. Three point at `aidd_docs/product/cost-report-contract.md`, which no plugin ships at all, so a reader of an installed `aidd-telemetry` was sent to a file that was never in the package. A fourth pointed at the plugin's own README, which is no more reachable than the repository once a tool has installed the skill somewhere of its own choosing — the boundary is the skill, not the plugin. Each becomes a name in prose: a reader can search for a name, and a name cannot rot into a broken link. The guard reads every markdown under `plugins/<plugin>/skills/<skill>/`, resolves each relative link, and fails on one landing outside that directory. It ships with a second test that puts an escaping link in front of it and requires it to be seen — a boundary check that never trips reads exactly like a clean tree, which is the state the existing checker was already reporting. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`MarketplaceSyncSettingsUseCase` wrote `.claude/settings.json`, hashed what it wrote, saved the manifest, and only then shelled out to `claude plugin marketplace add` and `claude plugin enable` — which write into that same file. Claude Code declares no separate `enabledPluginsSettingsPath`, so both halves land there, and the file is hash-tracked precisely because the two paths coincide. Nothing re-read it. The tracked hash described content that stopped existing the moment activation succeeded: `status` and `doctor` reported a file the person never touched as drifted for as long as the manifest stood, and `restore` would have undone the host's own registration to reach a state AIDD held for the length of one function. The first probe of this passed, and was vacuous — the fake activator wrote the same key `mergeEnabledPlugins` already writes, so the content was byte-identical and the hash matched by coincidence. Writing a key this code never writes, which is what the host's own bookkeeping is, turned it red. After activation the settings file is re-read and what is there is stored, for the tools whose own CLI actually ran. `activateTool` now answers whether it drove the CLI. Only those tools: a tool whose binary is absent wrote nothing, so a file that differs for it differs because a person changed it — the drift `status` exists to report — and re-hashing every installed tool would bless that as ours and make the change permanent and invisible. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A re-read appends, so one session's lines sit in different orders on two machines. The report is meant to be blind to that, and the existing check is four records and a single reversal. Four records cannot cover what has been added since. `by_flow` now keys an interval-derived row on a `FlowInterval` object, a tool-stated one on the skill's name, and the remainder on a symbol; `by_agent` keys two of its three rows on symbols. Mixed key kinds in one Map are exactly where insertion order leaks into output, since rows are ranked by size and ties broken on the row's own key — and nothing failed when either tie-break was broken. Verified against the real thing before writing it: 30,222 records of a live sink, shuffled within every day file, produced a byte-identical report to the unshuffled run. Three consecutive runs were byte-identical, and all eleven `--axis` artefacts stable. fast-check over 300 permutations of a fixture carrying every row kind once, plus a second test asserting the fixture exercises all of them — a permutation of records that produce one kind of row proves nothing about mixed keys. The `pickDeterministically` mutation survived the first fixture, which had no two records sharing a `billed_request_id`, so the function was never reached. Adding the pair is what made that guard real. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(cli): a step outlives the pause inside it A `turn_end` closed a step interval, so a skill spanning several prompts was credited with its first turn and nothing after. It is a pause, not the end of a step, which is the rule `buildTaskIntervals` and `buildFlowIntervals` already read from this very journal. Measured on the one orchestrated session captured, 2026-09-04: four steps opened across four hours of continuous work, every one closed by the next pause, the last at 06:02:34 against a session that ran until 09:27:21. Of its 1,073 records, 69 fell inside a step interval. The same journal gave the flow axis 1,052 records and this axis 1 — two walks over identical evidence disagreeing by three orders of magnitude. `buildStepIntervals` now runs through `buildClosedIntervals`, the shared walk the other two already use, over the same three-array merge. A `step_end` naming the step's own skill or the next `step_start` closes an interval; nothing else does. On a 30-day report holding 30,222 requests, `journal-interval` goes from 2 to 974, and all ten axes still reconcile to the period total. Two consequences, both stated in code rather than discovered later: - An unclosed step is capped at the journal's own last witnessed moment instead of left open. The premise for leaving it open — that this walk saw no later moments to cap at — is false now it reads `filesWritten` and `taskDeclarations` like the other two. - A journal whose only line is the opener witnesses no later moment, so the step it opened covers nothing, and `records-join` can read fail where it read ok. Found by running it. Failing there is the honest answer: nothing in such a journal says the step was still running. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp * test(cli): pin what capping an unclosed step costs Two guards for consequences the change states but nothing asserted. `check` on a journal whose only line is the opener: the step covers nothing, so a record carrying no step of its own joins nothing and `records-join` reads fail. That is a real host — Copilot fires no stop event — and `check` is the command whose job is saying measurement is broken, so the verdict belongs in a test rather than in a doc comment alone. A step whose end shares its start's moment covers nothing. The journal stamps `nowIso()`, whose resolution is the second, so two lines sharing a moment is the common case; the shared walk orders them by a stable sort, which for two `boundaries` entries is file order. Pinned because that ordering is inherited from the sort now rather than written out. Mutation run: an unclosed step left open again turns both red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp --------- Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er reads it (#776) The hook stamps `schema_version` on every `session_start` it writes (`record.cjs`'s own `SCHEMA_VERSION`, 2). The reader dropped the field on the floor: `parseSessionStart` never looked at it, and `RunJournalSessionStart` had nowhere to put it. So a journal written under a schema whose line shapes had changed was read as if it were this one — a silent misreading, which is the one thing a version field exists to prevent. The reader now carries the stated schema and refuses a journal stating one it does not read. Version 1 was a mutable record rather than this append-only line log, so its lines are another shape entirely, and a later version can change any line the same way; reading either would mean guessing that whatever lines this parser still recognises mean what they used to. Absence is not a disagreement. Every journal written before this reader looked at the field states no schema, and refusing those would drop attribution this reader has always given — "an unknown is never a zero", applied to the reader rather than to a figure. A refusal must not cost the fact that the file is there. Dropped from `list()` alone, a refused journal leaves `check` to fall through to a branch that is false about it: "the recorder is declared nowhere" claims no file exists, and "none carry a readable session_start" blames a torn write for a version disagreement. `RunJournalReader.listForeignSchemas` carries the stated versions across, and `hook-fired` gains `journal-in-another-schema`, read ahead of the anchorless one — a build that cannot read a journal's schema cannot tell a missing `session_start` from one shaped differently. Mutations run, all killed: the reader no longer refusing, absence read as a disagreement, and the new reason ordered after the anchorless one. Bundle budget 596 -> 598 KB, measured 594.3 -> 595.8, recorded beside the three raises before it. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ci: install under pnpm 12, which dropped --frozen-lockfile=false `validate.yml` activates `pnpm@latest`, and pnpm 12.3.4 landed on the runners today. It removed the `=false` value form of `--frozen-lockfile`, so the lefthook job stopped at its own install step: error: unexpected value 'false' for '--frozen-lockfile' found; no more were expected Usage: pnpm install --frozen-lockfile Every pull request fails that check from now on, whatever it changes: the job never reaches the hooks it exists to run. The three merged today passed it hours earlier, under pnpm 11. `--no-frozen-lockfile` is the same instruction in the form both majors accept — pnpm 11 documents the flag as `--[no-]frozen-lockfile`, and pnpm 12's own error says the flag takes no value. Not fixed here, and worth a decision: nine other workflow steps also activate `pnpm@latest`, so the next major arrives the same way, unannounced. Pinning the version is a choice about what the team runs locally, not a CI detail, so it is named rather than made. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp * ci: one pinned pnpm for the whole tree, and the overrides pnpm 12 stopped reading `corepack prepare pnpm@latest --activate` ran in ten workflow steps, so the version CI used was whatever pnpm had published that morning. `cli/` and `kanban/` pinned `pnpm@10.14.0` through `packageManager` and the repository root pinned nothing at all, so one run could resolve the root under 12 and `cli/` under 10. `packageManager` now names `pnpm@12.3.4` in all three, and every workflow step stops at `corepack enable` — corepack reads the pin rather than choosing. One version, written down once, and the next major arrives when somebody edits that line. pnpm 12 stopped reading `package.json`'s own `pnpm` field, which is where `cli/` kept four security floors: [WARN] The "pnpm" field in package.json is no longer read by pnpm. The following keys were ignored: "pnpm.overrides". Ignored there while the lockfile still recorded them, which is the ERR_PNPM_LOCKFILE_CONFIG_MISMATCH every `cli` install answered with under 12. They move to `cli/pnpm-workspace.yaml`, their new home, unchanged: `fast-uri >=3.1.2`, `picomatch >=4.0.4`, `postcss >=8.5.10`, `qs >=6.15.2`. Each lockfile gains 101 lines and loses none — the `packageManagerDependencies` block pnpm 12 records for the pinned version. Measured, all three: no dependency re-resolved, nothing removed. A pin cannot be added without it, since `--frozen-lockfile` refuses to write that block itself. Verified by running the thing rather than reading about it. Every package, `pnpm@12.3.4`, `--frozen-lockfile`: root exit 0, lockfile stable cli exit 0, lockfile stable kanban exit 0, lockfile stable 3,471 CLI tests, 371 repository script tests, biome `ci` clean (2 pre-existing warnings), typecheck clean, bundle 595.7 / 598 KB, 0 broken links in 798 files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp * fix(ci): name the builds pnpm 12 refuses to run unasked Pinning the version was not enough. pnpm turns an unapproved install script into `ERR_PNPM_IGNORED_BUILDS`, and under 12 every `cli` and `kanban` job stopped there — eleven checks red on a change that touches no source at all. The repository root already named `lefthook`. `cli/` and `kanban/` named nothing, because under 10.14.0 the same builds went through. Named from what pnpm itself printed, never guessed: cli Ignored build scripts: esbuild@0.21.5, esbuild@0.27.3, lefthook@2.1.12 kanban Ignored build scripts: esbuild@0.28.1 Verified after, `pnpm@12.3.4` with `--frozen-lockfile` and scripts enabled, each package in its own clean directory: root, `cli/` and `kanban/` all exit 0 with the lockfile unchanged. One measurement to distrust and the reason it is not here: `cli/` first failed with `ERR_PNPM_EXECUTOR_LIFECYCLE_SCRIPT_FAILED` in a bare directory, which is `lefthook install` finding no `.git` — the sandbox's fault, not the tree's. Run again in a directory with one, it passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp * fix(ci): stop discarding the file that allows kanban's builds The lefthook hook installed kanban with `--ignore-workspace`, added so pnpm would not resolve that folder to the repository root, find it lists no members, report "Already up to date" and install nothing. `kanban/pnpm-workspace.yaml` now stops that upward search by itself — the whole reason the file exists, as its own comment says — and the flag had turned harmful: it discards that same file, and with it the `allowBuilds` entry the commit before this one added. So the one job that runs the hooks failed on the build pnpm 12 refuses to run unasked, while the standalone kanban job passed. Measured in the real layout rather than reasoned about: a root workspace above, kanban's own file present, `pnpm@12.3.4`, `--frozen-lockfile`. without --ignore-workspace exit 0 with --ignore-workspace exit 1, ERR_PNPM_IGNORED_BUILDS Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp * fix(scripts): a YAML file may hold more than one document `validate-yaml.mjs` called `load`, which refuses a stream carrying several documents: "expected a single document in the stream, but found more". pnpm 12 writes exactly that shape — a lockfile whose first document carries `packageManagerDependencies` and whose second carries the lockfile itself — so the hook reported all three regenerated lockfiles as broken YAML. They are not: a multi-document stream is valid YAML, and `load` was the wrong function for a check whose whole job is syntax. `loadAll` accepts them and still refuses what is actually malformed — verified both ways: the three lockfiles pass, and `a: [1,` still fails on its own indentation. Found only in CI, and the reason is worth writing down: run with no arguments, this script validates nothing and prints "passed for 0 file(s)". The hook hands it the staged files; a local run without them is green whatever the tree holds, which is how three broken-looking lockfiles reached a pull request after a local check said yes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp --------- Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ed it (#777) * fix(cli): an invoked step does not close the orchestration that invoked it Reading every `step_start` as the end of whatever was open assumes a session only ever runs one skill after another. An orchestrating skill's whole job is to invoke others, so that assumption cuts it short at its first child. Measured on the one orchestrated session captured, 2026-09-04: `aidd-orchestrator:01-sdlc` opened at 05:56:27 and `aidd-pm:04-spec` at 05:59:53, so the orchestration read as 206 seconds against a session that ran until 09:27:21. `by_flow`, reading the same journal under the rule this adopts, named 1,052 records for that skill while `by_step` named 1. `buildStepIntervals` is now two walks over the same lines. The orchestrating half *is* `buildFlowIntervals` — a flow is an orchestrating step, and calling it rather than restating it is what keeps the two axes from drifting apart again. The other half opens on a non-orchestrating `step_start` and keeps the rule it always had: any later `step_start` closes it, because a session that starts orchestrating is no longer running the plain skill it was running before. `attributeMoment` now answers with the innermost interval around a moment rather than the first one in the array. Both claims are true of a record inside an invoked step; the inner one says more, and the outer one goes on answering for every moment the inner one does not cover. Array order decides nothing, which matters now that two separate walks build the list. Which skills orchestrate is `ORCHESTRATING_SKILLS`'s declaration, never inferred: nesting and sequence produce the identical journal, so no rule read off the boundaries alone separates them. The limit is stated in code — a skill that invokes another without being declared an orchestrator is still cut short by its own child. **Measured effect on today's data: none.** `by_step` reads `aidd-dev:01-plan=972, aidd-orchestrator:01-sdlc=1` before and after. The tail of that session is owned by `aidd-dev:01-plan`, an invoked step nothing closed, which runs to the journal's last witnessed moment and wins on having started later. Telling an interval closed by evidence from one merely bounded is the next change, and this two-walk shape is what it needs to fall back to. Mutations run, both killed: the outermost interval winning, and a single walk opening on every `step_start`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp * fix(cli): a step nothing closed yields to the one that encloses it An interval nothing ever closed ends at the journal's own last witnessed moment. That end is a bound, not a measurement, and reading it as an extent is how a step that opened shortly before a long session went on working comes to be credited with all of it — purely for having opened later than the orchestration around it. Measured on the one orchestrated session captured, 2026-09-04: `aidd-dev:01-plan` opened at 06:00:50 inside an orchestration opened at 05:56:27. Neither was ever closed, so both ran to 09:27:21 and the innermost-start rule gave the invoked step every record in between. `buildClosedIntervals` now tells its caller which of the two ended an interval — a moment the journal witnessed, or the cap standing in for one it never did — and `answersFor` reads it: an interval closed by the journal's end yields to one that encloses it and was never closed either. Two values and not three. "Closed by its own `step_end`" and "closed by a later `step_start`" are different strengths of evidence and both were weighed; both are nevertheless a moment the journal witnessed, and no caller separates them, so a third value would be structure nothing reads. The yielding is that narrow and no wider. Where the enclosing interval states its own end, the inner one runs past it, nothing encloses it, and the innermost claim stands — the same answer it gets when both ends are witnessed. An unclosed interval nothing encloses still answers for its own moments: what is refused is preferring a bound over a wider claim covering the same moment, never the bound. No tie between two unclosed sibling steps can arise, and it is not this rule that prevents it: any `step_start` closes whichever plain step was open, so at most one invoked step is ever left unclosed at a time. The test says so by demonstration rather than a comment. Containment between two unclosed intervals is compared on the start alone. They all end at the same cap, so comparing the ends would be a clause no input can make false — the kind of default that hides a wrong number. Measured on the real sink, 30 days, 31,025 requests, every axis reconciling: by_step aidd-orchestrator:01-sdlc journal-interval 1 -> 973 by_step aidd-dev:01-plan journal-interval 972 -> 0 973 is the whole of what the two walks can agree on. 1,052 records fall inside that flow interval; 79 of them carry a step their own tool stated (12 `aidd-orchestrator:01-sdlc`, 61 `aidd-dev:01-plan`, 6 `aidd-pm:04-spec`) and keep that stronger claim on the step axis. 1,052 - 79 = 973, and `by_flow` reads 1,052 for the same interval. This moves attribution within the 1,052 records the journal covers. It does not move the 27,393 unattributed ones: those predate the journal, from one session resumed since 2026-08-13, and no interval rule reaches them. Mutations run, three killed by three distinct guards: never yielding, yielding to any earlier start, and letting a closed interval yield too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp --------- Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) The `by_backlog` axis rests on one file per task folder, `backlog-link.json`, and nothing checked that the file was readable. Of the three this repository held, two carried `writtenAt` and `writtenBy` while `task-backlog-adapter.ts` reads `written_at` and `written_by`, so those two parsed as nothing at all. Measured on the real sink before the fix, 30 days: {"backlog":"#694"} 4 {"declaration":"unreadable"} 130 {"declaration":"none"} 2918 and after it, the same window and the same records: {"backlog":"#746"} 133 {"backlog":"#694"} 4 {"declaration":"none"} 2918 The axis was never broken — it reported `unreadable` truthfully, on its own row, exactly as `TaskBacklogDeclaration` requires of a file it cannot parse. What was missing is anything that reads that row before a person does. Both wrong files were written by `aidd-orchestrator:01-sdlc`, which `01-frame.md` tells to carry the resolved ticket so that "whichever of Spec or Plan first creates the delivery folder can declare it there". It wrote the file itself instead, and took the field names from the TypeScript interface rather than from what either skill teaches. The guard, not a fourth copy of the JSON block, is the durable answer: a third skill inventing a fourth spelling now fails here rather than in a report nobody reads. The guard restates the reader's rule, because the reader is a `cli/` module and this is a repository script test; the second case is what keeps that restatement honest, asserting the same three names are the ones `aidd-pm:04-spec` and `aidd-dev:01-plan` actually teach. Mutations run, both killed: a declaration put back in camelCase, and a skill that stops teaching one of the three fields. 373 repository script tests pass, 0 broken links in 798 files. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cript crossing it (#778) * docs: state the line between a hook and the CLI, and guard the docs that name files The telemetry pivot moved 4,355 lines of skill-owned scripts into the CLI on an argument nobody wrote down. What is left outside is one skill script and the bundled hooks, and the reason the hooks are not next is a measurement, not taste: on one machine, 12 runs each, `hooks/journal.cjs` starts in a median 27 ms and `cli/dist/cli.js` in 180 ms, and `PostToolUse` fires on every tool call a session makes. A thousand tool calls is 153 seconds a person waits through. So the line is not "plugin or CLI". It is what the code answers to: observing runs on a tool event thousands of times a session and must not be felt, so it is plain Node with no install behind it; answering runs once because a person or a skill asked, so latency is nothing and it belongs in `aidd`. The section states both consequences already paid for — a capability that answers moves even when a plugin is what asks for it, and a skill needing an absent CLI must say so out loud — and names the guard that already pins the second. It also states the cost rather than arguing it away: a plugin that promised "no npm install, no CLI, no account" now needs `node` to measure and `aidd` to answer. The doc named `hooks/update_memory.cjs`. That file has always been `.js`, in the tree and in `hooks.json`, so a reader following the architecture doc to the context plugin's session hook found nothing. `comments-name-files-that-exist.test.js` existed to catch exactly that and could not see it: it scanned `cli/src`, `cli/tests`, `plugins` and `scripts` for `.ts`, `.cjs` and `.js`, and no markdown at all. It now scans `docs/` markdown as well — extended rather than paired with a second test, since a durable doc naming a file makes the same promise a comment does. Markdown anywhere else stays out, measured rather than assumed: scanning every `.md` in the tree produced 17 findings and not one was a fault — a skill's own asset and a `cli/tests/fixtures` template name illustrative paths on purpose. Of the three findings in `docs/`, one is this bug and two name `INSTALL.md`, a seam artefact one plugin writes into a reader's project for another to read back, now listed in `NOT_A_REPOSITORY_FILE` for what it is. Red before the fix on the one line it names, green after. 371 repository script tests pass, the same count as before: the guard was extended, not duplicated. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579 * feat(cli): aidd ai rules answers what the last plugin script used to `plugins/aidd-context/skills/11-explore/scripts/list-rules.mjs` was the only script left inside a skill, 198 lines, and its own header said it was "synced from plugins/aidd-dev/scripts/list-rules.mjs. Keep in sync when the source changes" — a directory that no longer exists. A hand-maintained copy whose source is gone, and whose header is false. It had drifted where it mattered. It carried its own table of four tool directories and their extensions and stated "Codex CLI: rules not supported, skipped". `plugin-content-translator.ts` routes a plugin's `rules/` into every tool whose capability accepts them, and all five do, so a Codex project asking what rules it had was answered "none" — silently, and wrongly. `05-rule-generate`'s own `tool-paths.md` said the same of Codex and OpenCode; both rows are corrected here. `RulesCapability.installedLocation()` is what removes the table rather than moving it. It asks `buildInstallPath` with a sentinel name and reads its answer back, so the directory and the extension come from the installer itself: claude .claude/rules/ .md codex .codex/rules/ .md copilot .github/instructions/ .instructions.md cursor .cursor/rules/ .mdc opencode .opencode/rules/ .md Written where the knowledge already is: three of those five are a path template, one goes through `toMdc`, and Copilot's is a delegated handler. A caller splitting a path string apart would have been a second copy, free to disagree the day a tool moves. `registry-conformance` pins all five as a table, so a sixth tool, or a moved directory, is read by whoever changes it. The frontmatter parser goes too: `domain/formats/markdown.ts` already had `parseFrontmatter`, and the script carried a fourth hand-rolled one. `hasRules` was private to `plugin-content-translator.ts` and is now exported from `contracts.ts` beside the type it guards, generic so that translator keeps the narrowing it had. `--json` prints the array the script printed, field for field, so the skill reading it did not change what it reads. The default output is for a person, and a project with no rule says so rather than printing nothing: an empty answer and a command that never ran look identical on a terminal, and only one is a fact. The skill now runs `aidd ai rules --json` and, finding no command, stops and says the CLI is required — never an empty inventory, which is the rule `docs/ARCHITECTURE.md` states for a capability that answers. Bundle 596.6 -> 599.0 KB, budget 598 -> 601, measured and recorded beside the three raises before it. Mutations run, three killed: `installedLocation` answering the source path instead of the installed one (4 red), the scan dropping its extension filter (1), and a name trimmed at the last dot rather than by the whole extension (1). 3,488 CLI tests, 371 repository script tests, typecheck, biome `ci`, knip, jscpd, layering, 0 broken links in 798 files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579 * style(cli): order the imports biome's assist expects Adding the use case to `deps.ts` and the guard to `plugin-content-translator.ts` left both import blocks out of the order `assist/source/organizeImports` fixes to, which `pnpm lint` fails on and `biome ci` alone does not report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579 * fix(scripts): scan docs with its own find, not one joined by a semicolon Extending `comments-name-files-that-exist.test.js` to `docs/` markdown appended a second `find` to the same command string, separated by `;`. `execSync` runs through `cmd.exe` on Windows, where `;` separates nothing: the second `find` and its arguments were handed to the first as arguments, and the walk came back with a list that named files the repository does not hold. Green on macOS, red on the Windows job — which is exactly what that job is there for, and the reason the split is now two `execSync` calls with no shell operator between them rather than a quoting fix that would have worked on one platform. Line endings are split on either form while the calls are being touched: the same output arrives with `\r\n` on Windows, and a trailing carriage return would have made every path miss. 371 repository script tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579 --------- Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`prompt-matched` read one source: a `step_start` the run journal wrote with the prompt's own identifier. A session that ran before the hook was installed has no such line, so 9 records of 31,225 were named that way. The transcript holds the same fact. Every `Skill` call sits in a prompt, and the reader already resolves each record's prompt by walking `parentUuid`. It now also remembers, per prompt, the skill a `Skill` call started there, and stores it as `prompt_skill`. Measured on the real sink, 30 days, before and after: prompt-matched 9 -> 4,322 by_step named 11.5% -> 25.3% An observation, never a judgement. Which step a record belongs to is derived fresh on every report, from this and from the journal together; the journal wins where both name a skill for the same prompt, since it was written by a hook the host fired while this is read back afterwards. It does not duplicate `step`. That one reads `attributionSkill`, which Claude Code writes per message: exact where it appears and sparse where it does not. Measured inside the window `aidd-dev:01-plan` demonstrably ran on 2026-09-04, 142 lines carry counters and 20 carry that field — so its absence is not the tool saying no skill ran, and this contradicts nothing it states. The doc comment claiming otherwise is corrected. Scoped to the transcript the record sits in, which is what the reader accumulates. Claude Code writes a session's subagents to their own files, and 1,038 of 5,564 prompts on this machine appear in more than one. A subagent that invoked its own skill did that work under that skill; merging files first would have to pick one answer for both, and neither is true of both. Verified against an independent recomputation over every transcript, main files and subagent trees alike: of 6,215 stamped records, 6,136 match. The 79 that do not are prompts spanning two files, where the check merged what the reader keeps separate — the looser reading is the check's, not the code's. Four cases written before the code, two red at the start. Mutations run, three killed: the last skill of a prompt winning instead of the first, every `tool_use` counting rather than only `Skill`, and the record outranking the journal. 3,477 CLI tests, 371 repository script tests, typecheck, biome, knip, jscpd, layering, bundle 596.6 / 598 KB, 0 broken links in 798 files. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579 Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) `aidd_docs/product/aidd-kanban.md` was written once and never referenced again: no test asserts anything about it, no source file names it, and no other document links to it. Its two neighbours in that folder are the opposite — `metrics-contract.md` is held by three test files and `cost-report-contract.md` by four, which recompute the figures those documents state and fail when a field is added or removed without being written down. A document nothing checks can drift without anyone finding out, and this one describes a screen that has changed since. Read as current, it misleads; read as history, it is not marked as such. The 101 lines go. Nothing else moves: 373 repository script tests, 0 broken links in 797 files, and the eight cases guarding the two contracts that remain still pass. Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp Co-authored-by: reference-week <reference-week@example.invalid> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
OpenCode loads every function-valued named export of a file in `plugin/` as
a plugin factory of its own. `opencode-plugin.js` exported `journalCallFor`
beside `AiddTelemetry` as a spawn-free test seam; OpenCode called it with one
argument, got `null`, and died reading `.auth` off it before any session
started. Measured against opencode 1.14.20 in a freshly installed project:
export const Stub = async () => ({ event }) -> runs
+ export function helper(){ return null } -> TypeError: null
is not an object (evaluating 'S.auth')
+ export const helper = { a: 1 } -> runs
So installing this framework for OpenCode made the tool it measures
unusable. The seam now rides on the plugin function as a property, leaving
one export, and a test asserts that only `AiddTelemetry` is function-valued.
The same run showed the second half of the fault. OpenCode publishes
`session.created` on its own bus and never delivers it to a plugin's event
hook, and `opencode run` is always such a session, so the journal never
received a `session_start`, never created the run file the rest of the
session appends to, and dropped the `turn-end` and every task declaration
after it. `telemetryLocalRead` declared the tool covered while `aidd
telemetry read`, which reads only sessions the run journal knows, could
never find one. The first call a session produces now opens it, carrying the
directory that call was already going to use — never a new guess. What is
left is only what `session.created` alone could have said, and the README
says it: on a server serving more than one directory, a session it never
announced is journalled under the plugin's own init-time directory.
Verified end to end on a scratch repository, isolated sink: a real
`opencode run` wrote its first journal ever — `session_start`,
`task_declared`, `turn_end` — and `aidd telemetry read` answered
"OpenCode: read (3 new of 3)".
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Co-authored-by: reference-week <reference-week@example.invalid>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two tools declared a limit that was true when it was written and is not any
more. Both are now measured, and the captures that settle them ship as
fixtures.
Copilot. `copilot.ts` said the exclusivity of `input` against `cache_read`
was unconfirmed, because the only capture held `cache_read: 0` — where an
inclusive and an exclusive `input` produce the same number. It named what
would close it: one session with a non-zero `cache_read`. Captured on 1.0.82:
tokenDetails input 9 · cache_read 42038 · cache_write 21404 · output 408
usage.inputTokens 63451
9 + 42038 + 21404 = 63451, and Copilot's own terminal line for that run read
`↑ 63.5k (42.0k cached, 21.4k written)`. An `input` that already counted the
cached prompt would read 63451, not 9. The four counters are disjoint, so the
report is right to add them; it would otherwise have over-counted that
session by 42038 of 63451.
OpenCode. `opencode-export.ts` said no capture ever put a large `cache.read`
beside `input` for a non-Anthropic provider — the one comparison that shows
`input` failing to shrink if it already counted the cached tokens. Captured
from `opencode export --sanitize`, providerID "opencode", modelID
"ling-3.0-flash-fin-free", three billed turns of one session:
input 28242 → 269 → 196
read 640 → 28928 → 29184
total 29089 · 29356 · 29438, each == input + output + reasoning + cache
`input` falls as `cache.read` climbs, and OpenCode's own `total` holds the
identity on all three turns, which it could not if the counters overlapped.
What each tool still cannot say is narrower, and both limitation strings now
say that instead of the old one: for OpenCode, a provider reporting prompt
tokens inclusive of the cached ones has still never been captured here.
Both guards bite. Mutating `copilot-events.ts` to fold `cache_read` into
`input` turns exactly the new case red — `expected 42047 to be 9`.
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Co-authored-by: reference-week <reference-week@example.invalid>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`aidd marketplace list` died with
Error: Cannot read properties of undefined (reading 'map')
on a `~/.config/aidd/marketplaces.json` holding `{"version":1}` — a real file,
found while clearing test residue out of a machine's own configuration. The
reader caught a missing file and nothing else: `JSON.parse` could throw, and
`parsed.marketplaces` could be undefined, so a present-but-malformed registry
reached `.map()` and produced a stack trace naming nothing a person can act
on. Invalid JSON gave the same shape of answer, in the parser's words rather
than the tool's.
Reading such a file as an empty registry would be worse than crashing.
`save()` reads this same list, appends to it and writes the whole file back,
so one silent empty read turns a file with five marketplaces into a file with
one. A missing file is a different answer and keeps its own: no file, no
marketplaces, nothing to lose.
So a file that exists but cannot supply the list is refused by name:
Cannot read the marketplace registry at <path>: it carries no
`marketplaces` list. Repair the file, or delete it to start from an empty
registry.
Verified on the built binary across all four states — no file and an empty
`marketplaces: []` both answer "No marketplaces registered."; a missing list
and invalid JSON each name the file and the reason.
The bundle budget rises 601 -> 603 KB, measured 600.7 -> 601.2 KB. The
guard's own comment asks for exactly that: the budget makes growth visible
rather than walling it off, and a raise is what a reviewer sees. This one is
the smallest so far, and it is the raise that showed the budget had 0.3 KB of
headroom left — less than a correctness fix costs. It keeps the same 2.2 KB
headroom as the four raises before it.
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Co-authored-by: reference-week <reference-week@example.invalid>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ssion telemetry (#795) * refactor(cli): split architecture rules into self-contained invariants `0-hexagonal.md` listed eleven directories, so it had to be rewritten every time one moved. `0-layer-responsibilities.md` covered four topics at once, and its "Shared Use Cases" section explicitly allowed promoting a use case as soon as a second use case called it — the written policy that produced a 14-file `shared/` directory where only two entries have callers in more than one area. Both are replaced by one rule per topic, each scoped to the paths it governs and carrying constraints rather than a map: - 0-dependency-direction, 0-ports-adapters, 0-use-case, 0-domain-model - 0-orchestration: an orchestrator depends on entry points, not on parts - 0-shared-modules: sharing needs callers in two areas, and the test is `grep -rl` Paths now live only in `aidd_docs/memory/codebase-map.md`, which already carried a richer map than the deleted rule. Duplicated bullets are gone: ports and adapters had two homes, and "Methods <= 20 lines" restated 6-method-size.md. The no-re-export constraint joins `1-exports.md`, its own category, next to the existing barrel-file rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): add architecture invariant tests and make guardrails blocking The drift this repo accumulated happened while the rules existed: the `shared/` policy was written down and followed faithfully into a dumping ground. A rule is advice; a test is a barrier. Both detectors that could have caught it ran with `continue-on-error: true`, so they reported and never blocked. Five tests, in a dedicated `architecture` vitest project, 238ms for the set. They read source as text and never import the code under test, so they cannot be broken by wiring and stay cheap enough for pre-commit: - earned-sharing: a shared module needs callers in two areas (7 known) - orchestrator-deps: no use case injects more than four use cases (2 known) - tool-addition-cost: a tool id appears only in its own profile (20 known) - docs-do-not-lie: every command the docs present as available exists - codebase-map: every directory under src/ appears in the map Each carries a frozen baseline that may only shrink: a new violation fails immediately, and fixing one without updating the list fails too. Verified by introducing a deliberate violation and removing it again. Biome gains four native rules — noBarrelFile, noReExportAll, noImportCycles, noUnresolvedImports — plus an override forbidding the domain from importing application or infrastructure, verified in both directions. One sanctioned exception: tests/helpers/** keeps its barrel, imported by 78 test files. Note for the record: noImportCycles does not flag the two cycles found by hand. They close through `import type`, so there is no runtime cycle and Biome is right to stay silent. knip and jscpd now block. jscpd gets an explicit 3.5% threshold against a current 3.43% (71 clones, 772 duplicated lines of 22507), so any increase fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): correct three stale claims and record the file ownership regimes Found by the architecture tests added in the previous commit, which is the point of them. `ARCHITECTURE.md` presented `aidd sync` in its command surface. No such command is declared anywhere, and none ever was — the README documents it as removed. The line is gone. It also described manifest v6 as carrying `marketplaces`, while `manifest.ts:142` states the registry moved to `.aidd/marketplaces.json`. `codebase-map.md` was missing six real directories: display, translator, auth, git, http, and `use-cases/framework/` with its `strategies/` — 1819 lines, the largest use-case directory, absent from the map that is supposed to be the single place describing where things live. `memory/architecture.md` gains a File Ownership section. Two regimes share the disk and confusing them is the main source of accidental complexity: files the CLI generates are gitignored and disposable, so drift is answered by regenerating them; files co-owned with the user (settings.json, .mcp.json, .vscode/) are legitimately edited, so drift is answered by merging and reporting. Hash tracking on the first is over-engineering; blind rewriting of the second destroys work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * refactor(cli): drop the re-export that only served a test `doctor-use-case.ts` re-exported `extractAtReferences` and `extractMarkdownLinkTargets` from `domain/formats/markdown-references.js`. No production code went through it: `doctor-references-use-case.ts` already imports them from the domain directly. The only consumer was the unit test, which reached domain functions through the use case. A test shaping production code, and a use case turned into a hub for a domain module. The test now imports the source; the re-export is gone. Surfaced by biome's noBarrelFile, enabled in the previous commits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): persist the context refactor plan Eight documents from a scoping session backed by measurements on the code. Every figure is reproducible. The CLI's own value is translation: a user on Claude Code can register the marketplace themselves, but cannot convert one content tree into Cursor's .mdc, Codex's TOML and Copilot's .github/instructions. That reorders everything — translate becomes the core, framework one of its clients. Four contexts in a chain: framework -> translate -> tools -> kernel, plus framework -> distribution. Command grammar taken from Claude Code and Codex, where it holds without exception: a bare verb performs an action, a noun then a verb manages a resource. translate absorbs framework build (measured identical: same source, same output dir, same flat/marketplace modes), sync replaces restore, doctor absorbs status, ai and ide become a --tool flag. Acceptance test for the whole refactor: adding a sixth tool must touch one file. It touches eight today, and a test now measures it. The migration plan's rule is that a move and a scope change never share a commit: a neutral batch passes golden and e2e untouched, a scope batch recaptures the snapshot and its diff is the review. Corrections made along the way are kept, because they show where the reasoning slipped: materialization is not the cause of half the CLI (3 tools of 5 already point rather than copy), and cutting test volume is dropped — the suite runs in 25s for 2158 tests, no subject is tested at two levels, and one file of 139 is heavily doubled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): plan phase 0, extending the golden net Reading the golden test changed what this phase is. `captureMatrix` runs commands sequentially in one project directory: it is a scenario where state accumulates, not a list of independent invocations, and `clean --force` is terminal. Extending it is a scenario design, not commands appended to a list. Three things the reading settled. The fixture marketplace offers `aidd-test` from a local path, so plugin commands stay offline and deterministic. `framework build` already has its own golden over the nine target/mode cells, so it is not duplicated here. And the current snapshot never captures a modified tracked file — yet drift detection is what `status` and `doctor` share, and what the refactor touches most. That is the real gap, not the missing command count. The phase is a scope batch by the plan's own rule, since it recaptures the baseline. It should also be the only one whose diff is pure addition: if an existing entry changes, the capture is not deterministic. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): resolve a contradiction about framework build's golden `migration.md` asked phase 0 to add `framework build` to the baseline matrix. `phase-0.md`, written after actually reading the golden test, says the opposite: it already has its own golden over the nine target/mode cells. The second is right; the first predates opening the file. Also states plainly what the plan implied but never wrote: the command is not removed, and not yet renamed. The surface change is phase 12, last, because the e2e net invokes the CLI and renaming breaks it exactly when it is most needed. The behavior is never removed at all — `translate` absorbs it, same engine, new name. So its golden is touched twice, at two moments: phase 1 drops flat mode for the four native tools, taking nine cells to five; phase 12 changes the invocation, keeping the old spelling as an alias until the tests have moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): turn the scoping into an executable plan of 17 phases `migration.md` was a scoping note in thirteen prose phases. This is the plan in the repo's own format: `plan.md` plus one phase file each, with a projection, a user journey, a test scope, tasks and acceptance criteria. Sizing changed the count. `migration.md`'s phase 1 held four deletions of very different risk — dead code touches nothing, dropping the manifest migrations changes what the CLI accepts, and removing four flat build cells rewrites the build golden. They are four phases now, so each ships and is reviewed on its own. Two things the plan makes explicit that the note left implied. Phase 4 opens with a check before it removes anything: it is the only step that can refuse a project that used to load. And phase 16 orders the surface change internally — `sync` first because it replaces nothing, then `doctor` enriched, then `translate` before `framework build` retires. Every phase's acceptance table ends the same way for a neutral batch: golden and e2e pass unmodified. That is the plan's rule, restated where it is enforced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): add three nets and split the oversized phase The plan was reported at 7 of 10 with three named weaknesses. Each is answered by something that fails on its own rather than by a promise to be careful. Eleven relocation phases had no proof that the user-visible surface stayed put: the golden covers a scenario, not the command tree. Phase 1 now freezes a `--help` snapshot of every command and subcommand — no fixture, no network, and a moved flag or a changed description fails immediately, naming the command. Phase 17 was the one phase changing the net and the subject at once: recapturing the golden cannot tell a successful rename from a behavior change, because the command string moved too. Its net is now equivalence, not the snapshot — while both spellings exist, each pair runs on two identical fresh projects and must produce the same exit code, files, manifest and output. The test dies with the aliases, and its passing run is what licenses their removal. Extracting the framework context and redesigning `Manifest` in one pass could not both be reviewed, so they are two phases. The split gains its own net: load every manifest fixture, write it back, assert the bytes are unchanged — before and after. Also promoted the chain to a test rather than a lint pattern (`context-graph`), and moved the manifest-migration removal from second to fourteenth. It is the only step that can refuse a project that used to load, and nothing depends on it, so it now sits where postponing it costs nothing — after the aggregate split, on a smaller file, with the round-trip test available. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): add a smoke phase, after running the suite that nobody runs `scripts/smoke-tools.sh` drives the built binary with real arguments in throwaway projects and reports 100% leaf command coverage, 37 of 37, measured by itself. It has no CI job and no lefthook entry, and its last commit is the repository move. Running it settled three things a reading could not. It is red. 73 pass, 4 fail, 7 min 11 s. The four are one scenario: it sets up with `--plugins recommended`, corrupts the cached catalog, then expects `plugin install aidd-dev` to fail with a message naming `marketplace refresh --force`. It gets "already installed", because `aidd-dev` is in the recommended set. The scenario stopped testing what it claims the day that plugin was promoted, and nobody saw it because nobody ran it. A test defect, not a product one. It needs the network. Seven invocations use `--source remote` and fetch the really published framework — which is why one injected corrupt shape is `{"message":"API rate limit exceeded"}`. A net gated by a rate limit cannot block a build, so the phase splits a hermetic subset from a remote one. And the real gap is options, not commands: 11 of 24 declared flags have never been passed once, including `--flat`, which phase 5 removes for four tools, and `--scope`, which decides where files land. Two of my earlier figures were wrong before execution — 23 of 27 commands and 44 invocations — because the regex missed loop invocations. Corrected in the README, kept visible rather than quietly replaced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): a fourth dormant net, broken by a TypeScript upgrade `stryker.conf.json` mutates exactly one file: `src/domain/models/manifest.ts`, break threshold 50. That is the strongest available evidence that this aggregate's tests would notice a change — and `manifest.ts` is precisely what phase 14 redesigns. It does not run. `stryker run` crashes on `TypeError: ts.parseConfigFileTextToJson is not a function`: Stryker 9.6.1's TSConfig preprocessor calls an API that TypeScript 7.0.2, the native port, no longer exposes. No CI job, no hook, so nobody saw the upgrade break it. It becomes task 0 of phase 14, before the split rather than after: a mutation score taken after a redesign proves nothing about the redesign. If Stryker cannot be made to work with TypeScript 7, the phase records that and names the round-trip test as the weaker stand-in. Also deleted `migration.md`. It kept its own thirteen-phase numbering against the plan's nineteen, and two numbering schemes in one folder are the kind of trap this whole effort exists to remove. Its two sections that lived nowhere else — how tests behave during the migration, and what can run in parallel — moved to the README. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(contributing): name the pnpm 11 first-install stop pnpm 11 will not run a dependency's build script until it is allowed, so the first root install ends on `ERR_PNPM_IGNORED_BUILDS: lefthook` — and lefthook's build script is what installs the git hooks, so nothing works until it clears. Two ways out are documented. `pnpm install --ignore-scripts && pnpm exec lefthook install` leaves nothing behind, both steps verified. `pnpm approve-builds lefthook` writes a `pnpm-workspace.yaml` at the root, which is deliberately not committed: with that file present, `pnpm --version` inside `kanban/` reports 11.20.0 instead of 10.14.0. The effect reproduces; its cause was not found — no `packageManager` field, no `.npmrc`, no `.tool-versions`, and pnpm's workspace-state files record no version. It is stated as observed, not explained. CI is unaffected either way: its only root install, in validate.yml, already passes `--ignore-scripts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): extend the golden net with drift, errors and a help surface Phase 1 of the context refactor. The baseline held five invocations while its own docstring claimed "each public CLI command"; the eighteen phases that move code had almost nothing to check themselves against. The scenario now runs 15 invocations and a second project holds 7 error paths, prefixed so both share one snapshot: 22 entries, 6 of them non-zero exits. It covers doctor, marketplace list, plugin list/install/remove and a second tool, none of which were captured before. A new help-surface golden walks the command tree from the root and freezes `--help` for all 44 nodes. No fixture, no network, 13 seconds. It is the only net that catches a moved flag or a reworded description while files travel between contexts, and it was verified by changing one description and watching it name the invocation. Two invocations I wrote were wrong and the capture said so, which is the point: `plugin remove --yes` recorded `error: unknown option`, not a removal. Worth noting while fixing it — `plugin install` accepts `--yes` silently and `plugin remove` rejects it, though neither declares it and it is not a global option. The docstring now names what it does not cover: the network, the interactive paths, `framework build` and the help shape, each already covered elsewhere or excluded on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * fix(cli): make `aidd restore --force` actually force The flag was inert. `restore.ts` folded it into one boolean — `interactive = !force && isTTY` — and `RestoreAllUseCase.execute` only ever took `interactive`, so `runConfigRestore` passed `force: interactive`. In a non-TTY run that is always `false`, whatever the user typed. A modified tracked file therefore raised `InputRequiredError`, which the caller swallowed into a warning reading "Use --force to overwrite modified files in non-interactive mode" — addressed to someone who had just passed `--force`. With nothing restored, the command then reported "Nothing to restore — all files are unmodified" while `status` reported the same file modified. Two commands disagreeing about one file, and neither wrong from its own point of view. `force` is now threaded from the command to the decision. The error no longer aborts the loop either, so a second tool is reached: the golden goes from "Nothing to restore" to "Checking claude... Checking cursor... Restored 1 file(s)". `ai restore` never had the defect — it passes `force` and `interactive` separately. Only the global command folded them. Found by the golden net extended in the previous commit, which is what it is for. Two regression tests pin it, verified by reinstating the defect and watching the force case fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): recapture the golden after the restore fix, close phase 1 The baseline moves from "Nothing to restore — all files are unmodified" to "Checking claude... Checking cursor... Restored 1 file(s), kept 0 file(s)", and the status that follows returns to "All files are in sync". That diff is the review of the fix in the previous commit. Phase 1 closes with every criterion met. One needed rewording rather than a claim: it demanded a pure-addition diff on the reasoning that any changed entry proves the capture non-deterministic. An entry did change, for a reviewed reason, so the criterion now allows that and requires the reason to be recorded. Determinism is still asserted directly — two captures byte-identical, two verification runs green, and the snapshot carries no absolute path, version string or timestamp. The phase file records what extending the net turned up: the inert `--force`, two invocations this phase wrote wrong that the capture caught, and the fact that every tracked file in this scenario is co-owned — with claude and cursor installed the manifest tracks one `settings.json` each and `plugin install` writes no tracked file at all, since both tools get a registered marketplace rather than copies. Exercising the CLI-owned regeneration regime needs a flat-mode tool. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): record that telemetry lands in the current structure It is being built in parallel with this refactor and will follow today's conventions, so its files move with their layer rather than arriving in the target shape. That keeps one structure at a time, at the cost of one more migration. Three consequences kept where they will be read. The phase projections name files one by one and know nothing of telemetry, so whatever it adds has to be folded into the projection of the phase that moves its layer. "Enable telemetry for a given tool" reads state that `framework` owns, which the "only framework imports another context" invariant does not allow — the fragility flagged when that rule was written, now arriving. And `docs/FAQ.md:44` currently promises the opposite: "there is no AIDD server, account, or telemetry." That is the only place in the repository carrying the promise — the README does not — so touching the README alone would leave it false for a release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): replan phase 2 after measuring what the smoke suite covers The phase was written from reading the script. Running it, and then counting what the token gate hides, changed what the work is. Line 106 falls back to `gh auth token`, and everything substantial sits behind `if [[ -z "$TOKEN" ]]`. Counted statically: 11 invocations are hermetic, 30 are gated — the setup matrix, the global read-only commands, restore, the per-tool AI and IDE commands, the plugin commands, the update conflict guard and the fault injection. On a machine where `gh` is logged in the suite covers 41 invocations and reports 100% leaf coverage; where it is not, it covers 11. Same command, same repository, two different nets. That, not the option gap, is why it cannot gate a build. So the phase's real work is moving those 30 onto the local fixture — including swapping `aidd-dev`, a really published plugin, for the fixture's `aidd-test` and rechecking every assertion that depends on plugin content. A second run also had `plugin update (all)` exceed the script's own 180s ceiling and get killed. Seen once, not diagnosed, and now task 0: reproduce it, keep a ceiling so one hang cannot stall a run, and fix it outside this phase if it is a product defect — a net phase does not change behavior. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): make the smoke suite hermetic and put it in CI It reported 100% leaf command coverage and ran nowhere. Worse, that number depended on the machine: line 106 fell back to `gh auth token`, and 30 of its 41 invocations sat behind `if [[ -z "$TOKEN" ]]`. Where `gh` was logged in it covered everything; where it was not, eleven invocations. Same command, two different nets — which is why it could not gate a build. Every setup now uses the local framework fixture. Coverage is 37/37 with or without a token, the run takes 92s instead of 7 min 11 s, and a new blocking `cli / Smoke` job runs it. One opt-in section (`SMOKE_REMOTE=1`) keeps what a fixture cannot prove: that fetching from a real remote source works. A third token dependency turned up on the way: the coverage threshold itself only fired when a token was present. It is unconditional now. The four failures are fixed. The fault-injection scenario set up with `--plugins recommended` and then tried to install a plugin that was therefore already there, so it never read the corrupt catalog it had just written. It also cannot be hermetic — it corrupts the fetched catalog cache, which only a remote source populates — so it moved into the opt-in section. Once it finally reached its own code path, its expectation proved obsolete: a corrupt fetched catalog no longer blocks the install. Recovering silently may be right, since a fetched catalog is a cache. That question is now reported rather than asserted, and it is this phase's one open decision. All 24 declared options are exercised, up from 13. `--dry-run` is asserted to write nothing by comparing the tree before and after; the two `--scope` values are asserted to write to different registries. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): pin the corrupt-catalog recovery instead of demanding an error Phase 2 left one question: a corrupt fetched catalog no longer blocks `plugin install`. It is the better behavior — a fetched catalog is a cache, and the rule for CLI-owned files is to regenerate rather than error — so the check now pins the recovery rather than reporting the question. Pinning it took two attempts, and the first is worth recording. Asserting that the cache file came back valid failed on one shape of four: three make the CLI re-fetch, `{ truncated` does not. That is an internal difference with no user-visible consequence, and an assertion on it would flap for no reason. The check now asserts what a user sees — the install succeeds and the CLI still works with the corrupt catalog on disk. Both modes now run with no skipped check: 99 hermetic, 108 with SMOKE_REMOTE=1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): re-verify phase 3's targets and size them Every deletion target checked again after phases 1 and 2 moved code around them, and each now carries its evidence and its size rather than an assertion. Roughly 700 lines. The foreign-catalog branch is 4 parsers, `normalized-plugin.ts`, a port method and 5 of the adapter's 123 lines — `NormalizedPlugin` appears in seven source files and no test. `marketplace-entry.ts` is still the only file unreachable from `src/cli.ts`, 103 lines plus a 157-line test, and `knip.json` still names it to stay quiet. And `mcp-exclusion.ts` loses 134 of its 186 lines. The telling part is the middle: `mcp-exclusion`'s four exports and `buildMergeFileEntries` each appear once in `src` — their own definition — and once in tests. Live tests guarding dead behavior. They pass, they prove nothing, and without this phase they would ride through eleven relocation phases. Two test files the phase had missed are now in its projection: the merge-entry unit test and the catalog-repository integration test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * refactor(cli): delete the dead code before moving anything 1871 lines across 23 files, one inserted. The foreign-catalog branch (`loadForeign`, four parsers, `normalized-plugin.ts`, a port method, five adapter methods), `domain/models/marketplace-entry.ts` with the `knip.json` entry that kept it quiet, and six exports that each appeared once in `src` — their own definition — and once in a test. Live tests guarding dead behavior, which would otherwise have ridden through eleven relocation phases. The nets were not touched. Golden, help surface, smoke and e2e all pass on files this commit does not modify, which is what makes a deletion reviewable: unit tests drop from 1522 to 1399 and integration from 510 to 482 because dead tests left with the dead code, while behavior stayed put. Two things the plan had not foreseen. The compiler named four whole test files for the deleted parsers — 595 lines — and a 180-line `loadForeign` block in the adapter's integration test, none of them in the projection: reading a codebase is not compiling it. And deleting exposed more dead code — `ForeignSchemaValidationError` existed only for the path that just went. Each removal uncovers the next, which is the argument for doing this before the moves. `tool-addition-cost` refused to stay silent on six now-obsolete entries and named them; its baseline is 20 to 14. Duplication fell 3.43% to 3.17%, so the jscpd threshold moved 3.5 to 3.2 rather than leaving a third of a percent of slack. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): correct phase 4 before executing it, not during Phase 3 discovered half its own scope while running, because its projection was read rather than compiled. This one was checked first, and it was wrong in four places. It claimed `snapshots/phase0` would lose a help line. That snapshot holds 22 command invocations and captures no help at all. The snapshot that changes is the help-surface golden, built in phase 1 and therefore invisible to a fiche written before it. The smoke suite was ignored entirely — it has a `plugin create` section, an `ALL_COMMANDS` entry, and a coverage report that must go from 37 leaf commands to 36 and stay at 100%. And it listed one test file where there are five. It also missed a cascade: `parsePluginComponentKind` has exactly one production caller, the `--type` option of the removed subcommand, so `plugin-component-kind.ts` and `InvalidPluginComponentKindError` fall with it. That is the pattern phase 3 hit with `ForeignSchemaValidationError` — written into the plan this time rather than discovered mid-flight. Checked and safe: the schema integration test dies with the scaffold, but the two adapters it exercises are covered by eight other tests, so nothing that survives loses coverage. Roughly 750 lines, 520 of them tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * refactor(cli): drop plugin scaffolding 921 lines deleted, 2 inserted, across 15 files. `aidd plugin create` was exposed in `--help` and documented nowhere: `docs/CREATE_PLUGIN.md`, the contribution guide, describes an entirely manual flow. Nobody writes third-party plugins today and the command was never on a contributor's path. Correcting the fiche before executing paid off. The four mistakes it carried — naming the wrong snapshot, ignoring the smoke suite, listing one test file where there were five, and missing a cascade — were fixed as plan edits rather than discovered as surprises. The predicted cascade then happened exactly as written: `plugin-component-kind.ts` and `InvalidPluginComponentKindError` lost their last caller with the `--type` option. It went one level deeper than predicted, and `knip` is what found it: `domain/formats/marketplace-json.ts` was imported only by the deleted use case. Its test followed, 97 lines in all. Third phase running where deletion uncovers deletion, second where tooling saw what reading had not. The nets behaved as intended. The smoke suite reports 36 of 36 leaf commands and still 100%; the help-surface diff removes exactly one entry and one line from `aidd plugin`'s own help; `snapshots/phase0`, the build golden and every surviving e2e file pass unmodified. One flaw surfaced, in a ratchet added two phases ago. Phase 3 tightened jscpd to 3.2% after duplication fell to 3.17%. Deleting 921 lines of NON-duplicated code pushed the ratio to 3.22% — the same 66 clones and 694 lines over a smaller codebase. A percentage ratchet punishes deletion. Threshold moved to 3.3; phase 5 should expect the same, and ratcheting the clone count instead would be the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): explain the coverage exclusion and unblock stryker's first failure Two gaps in the regression net, both measured rather than assumed. `cli.ts` and `commands/**` were excluded from coverage with no reason recorded. Including them drops the total from 91.3% to 82.0% and reports `cli.ts` at 0% and `commands/` at 0.69% — while 126 e2e tests and 98 smoke checks exercise them. Both spawn `dist/cli.js` as a subprocess and v8 coverage does not cross a process boundary, so including them produces a false zero rather than a measurement. The exclusion stays, with that reasoning now in the config where the next reader will find it. Stryker's crash is fixed: `tsconfigFile: ""` avoids its TSConfig preprocessor, which called `ts.parseConfigFileTextToJson` — an API TypeScript 7 no longer exposes. It now reaches its initial test run and fails further along, for a different and named reason: its runner launches vitest, which picks up `vitest.workspace.ts` and therefore runs e2e, and the build golden does not survive Stryker's sandbox where absolute paths differ. `vitest.dir`, `vitest.related` and a dedicated config file were each tried; none narrows the initial run. Unblocking it means stopping Stryker from using the workspace, which is not done here. Phase 14 still wants it, now with an obstacle named instead of "broken". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): cancel phase 5, its premise does not hold It was going to remove flat build mode for claude, cursor, copilot and codex, keeping it only for OpenCode, on the grounds that their flat cells duplicated their native mode. Checking that before executing is what caught it. Two axes were conflated. `PluginsCapability.mode` describes how a plugin is installed into a tool — four of five declare `native`. `FrameworkBuildMode` describes how the framework is built for a target. The first measurement said nothing about the second. The build golden settles it: for claude, marketplace mode produces 198 files under `.claude-plugin/` and `plugins/`, flat mode produces 189 under `.claude/agents/`, `.claude/skills/` and `.claude/hooks/`. One is a distributable marketplace tree, the other puts the framework straight into the tool's config directory. And `--flat` is documented in `cli/README.md` in four places, including the clause that names the second use case: "or when you want files on disk in the project". So the nine build cells stay, the 831 lines of flat-specific code stay, and the plan loses a deletion phase. Nothing downstream depended on it. One finding survives and phase 6 already carries it: the plugin materializer re-derives "flat" from `toolId === "opencode"` instead of reading the profile. That is about plugin materialization — the axis this phase confused with build mode — and it is a real defect either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): record the three ways a marketplace gets registered The same operation is done three different ways. Copilot and codex drive the tool's own CLI through `nativeActivation`. Claude writes `.claude/settings.json` by hand — `extraKnownMarketplaces` and `enabledPlugins` — while `claude plugin marketplace add` exists and is the officially supported path. Cursor needs no registration at all. That file is co-owned: it is the only tracked file in the Claude profile, the one whose drift showed up in phase 1. So the CLI hand-edits another tool's private configuration when that tool publishes a command for it. The Copilot profile documents why it drives the CLI — "Copilot treats enabledPlugins in settings.json as a recommendation, not an auto-install (copilot-cli#2249)" — which means the CLI path was taken because the file was not enough, not on principle. The Claude profile carries no comment at all: nobody questioned it. The tradeoff is real and not mine to settle: writing the file works whether or not the tool is installed, while driving its CLI requires the binary but rests on a public contract instead of a private file format that can change without notice. Two of four tools already accept that dependency. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * refactor(cli): one data-driven plugin CLI adapter instead of three Three subclasses differed by two verbs. `NativePluginCliAdapter` takes the binary and both verbs from the tool profile, so a tool's name no longer appears outside its own profile: the `tool-addition-cost` ratchet drops from 14 entries to 11, and the driven-tools registry in `deps.ts` is now derived from the profiles rather than hand-listed. The change started as an attempt to uniformize Claude onto its own CLI, and that part was reverted after measuring it. `claude plugin marketplace add` exists and takes a local path, but it writes `.claude/settings.json` itself — after this CLI wrote that file and recorded its hash. Two writers, one recorder: the golden showed `status` reporting the file modified forever after. Without `--scope project` it is worse still, since the command defaults to user scope and would register the marketplace globally for every project on the machine. Both the profile and the `NativeActivation` type now carry that reasoning, so the next reader does not retry it. Cursor was checked too: `cursor-agent plugin marketplace add` now exists but takes a git URL and indexes per account, so it cannot register a locally built marketplace. Cursor's plugin-local materialization stays. What this uncovers is bigger than the case: `.claude/settings.json` is co-owned with the *tool*, not the user. Tracking the hash of a file another program legitimately rewrites manufactures false drift — a third regime beside CLI-owned and user-co-owned, and one nothing in the plan decides yet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): phase 5 becomes letting each tool own its configuration The slot held a cancelled phase; it now holds the change that replaces it, with the cancellation kept as its first section so the wrong premise stays visible. The CLI hand-writes `.claude/settings.json` and then records that file's hash in its own manifest. The first attempt at fixing that drove `claude plugin marketplace add` *in addition* to writing the file, and the golden showed the result: two writers, one recorder, so `status` reports the file modified forever. The fix is not to add a second writer, it is to stop being one. Write through the tool's command, verify through `claude plugin marketplace list --json`, track nothing the tool owns. One decision gates it, and the phase says so rather than assuming: setup currently works when Claude Code is not installed, because writing the file leaves a registration that takes effect later. Driving a command cannot. Require the binary, fall back to the file when it is absent, or wait for hosted marketplaces. That last option is why the remote direction matters here. The built marketplaces are local paths, which is the only reason Cursor cannot be driven at all — verified against the installed CLI, `cursor-agent plugin marketplace add` takes a git URL and indexes per account. Host them and claude, codex, copilot and cursor all accept a URL, leaving the four profiles differing by paths and formats only. That is the shape phase 10's acceptance test is asking for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): plan the hosted per-tool marketplaces Written after checking what already exists, and most of it does. `ci.yml` already builds the nine tool/mode cells on every release and attaches `aidd-framework-<tool>-<mode>-<version>.zip` to the GitHub release. The CLI does not consume them: `--release` picks a framework *source* release and then rebuilds locally into `.aidd/cache/built/`. The same build runs twice, once to publish and once per user to install. And `docs/FAQ.md:42` already promises the direction — "public-marketplace publishing is on the way". What is missing is the form. Zips are published; the tools' commands want a git URL. Verified against the installed CLIs: claude takes a URL, path or GitHub repo, codex and copilot take a marketplace snapshot, and cursor takes a git repository URL indexed per account — which is the single reason cursor cannot be driven at all today. Superpowers solves the same problem by rsyncing into a registry repo and opening a PR. Three shapes are laid out to choose between: a repo per tool, branches of one repo, or keeping zips and not driving the commands. The last one unblocks nothing. What it would unblock is named: phase 5's open decision disappears, since with a URL there is no local path to point at; cursor becomes drivable; the double build goes away; and phase 10's acceptance test becomes real. What it costs is named too: no offline install, content public by construction so a private framework needs a second path, revocation moving into the tool's config, and an unanswered question about who publishes what and when. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): park phase 5 until the hosting shape is decided It waits on a product decision that is not being taken now: neither the offline behaviour nor the hosting form. Marked blocked rather than pending so the plan's state stays true. It blocks nothing. No other phase depends on it, and the refactor continues at phase 6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): record the per-tool scopes and what the gitignore exposes Checked against the four installed CLIs: only Claude offers scopes at all — `user` (its default), `project` and `local`. Codex is user-global by design, copilot has no `--scope`, cursor indexes per account. So "allow every scope where available" means AIDD's model moves from one fixed scope per tool to the list a tool supports plus a default, and exposes `--scope` only where there is a choice. The gitignore settles which default. AIDD adds one line, `.aidd/cache/`, leaving `.aidd/manifest.json`, `.aidd/marketplaces.json` and `.claude/settings.json` committed. But that settings file holds the marketplace registration, and the path it registers is absolute and points into `.aidd/cache/built/` — the ignored directory. Verified on a fresh project: a teammate cloning the repo gets a pointer to something that cannot exist until they run setup themselves. That is a latent defect in today's design, independent of the rest, and it decides the split: AIDD's runtime config is genuinely shareable and belongs in `.claude/settings.json` at project scope, while the registration can only ever be machine-local and belongs at `local` scope — which writes a separate file, `.claude/settings.local.json`, removing the hash collision that sank the first attempt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * refactor(cli): import every symbol from where it is defined Four untanglings, no file moved and no behaviour changed, so the golden snapshots and the 126 e2e cases pass exactly as they were. The two design cycles are gone. `UserFileSection` and `UserFileSectionKey` lived in `tools/contracts.ts` while `formats/command.ts` was the only place building them, so they move to `formats/`, and the three `AI_TOOL_IDS` imports in `capabilities/` now point at `models/tool-ids.ts` rather than travelling through the tool registry. Neither cycle was a runtime cycle — both closed through `import type`, which is why `noImportCycles` never said anything about them. Six re-exports are gone with them: the registry stopped re-publishing eight identifiers it had imported from `models/tool-ids.ts`, and `setup-use-case.ts` and `update-all-use-case.ts` stopped standing in for the modules that define `SetupToolsResult`, `ToolInstallResult` and `GlobalExecutionError`. The criterion for that last part named Biome as the judge, but Biome cannot deliver that verdict: `noBarrelFile` only sees files that do nothing but re-export, and `noReExportAll` only sees `export *`, while the form that had actually accumulated here is narrower than both — a module importing a symbol and exporting it again. A criterion checked by a tool blind to what it checks is the failure this refactor exists to correct, so it becomes a ratchet with an empty baseline, proven by injection: putting a re-export back makes it fail. `MarketplaceSettings` and its entry types leave `plugins-capability.ts` for their own file. They are read by marketplace settings synchronisation alone, while `PluginsCapability` is read by every tool profile, and none of the five profiles pulls them in any more. Finally, the framework build mode is read off the tool's profile instead of being re-derived from its name. `frameworkBuildModeFor` sits beside `nativeActivationOf`, which already reads the profile the same way, and the translator that used to ask whether the tool was called "opencode" no longer names a tool at all — one entry off the tool-addition ratchet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): refresh the index's net table after phase 6 The smoke row still described the suite as running nowhere and red, which phase 2 fixed: it is hermetic, wired into CI, and up from 77 checks to 98. And the non-re-export ratchet phase 6 added is a net in its own right, worth listing next to the others precisely because Biome cannot see the form it catches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * docs(cli): settle phase 5 — the offline question was not the blocker Measured, and two of the beliefs the phase rested on were wrong. The offline precedent already exists and costs less than the phase assumed. `aidd setup --ai codex` lays down exactly one file, `.codex/config.toml`, holding `model` and `approval_policy` and no registration at all: codex's marketplace exists only through its command, so codex without its binary is already a setup that succeeds and registers nothing. Claude lays down exactly one file too. Driving the command would not introduce a new silent failure — it is already in place for one tool in two. But what AIDD registers for itself cannot be shared, under either source mode. With `--source remote` it writes an absolute path into `.aidd/cache/built/`, the directory the gitignore excludes; with `--source local` it writes the absolute path of the source repo. What gets registered is never the upstream repo, it is the built tree — the Claude-shaped marketplace — so the path is machine-local by construction until those trees are hosted. And the same file carries entries of opposite natures: a third-party marketplace declared as a GitHub repo writes `{source:"github", repo:"…"}`, which is perfectly shareable and correct to commit. So a committed file mixes what can only belong to one machine with what should belong to the team. That contrast, not the path on its own, is what decides the phase. So the phase splits. 5a separates the two natures — AIDD's own registration moves to `.claude/settings.local.json`, gitignored, while third-party entries stay in the committed file — which needs no hosting, no CLI driving, keeps AIDD the sole writer of both files so no hash collides, and fixes a real defect: today a teammate cloning inherits a pointer to a directory that cannot exist for them. 5b, driving the tool's own command, stays blocked and is attached to the hosting note, because at project scope it recreates the collision that sank the first attempt, at local scope it only matches what writing the file already achieves, and cursor cannot be driven at all since its command takes a git URL. Two of the phase's own criteria were false and are corrected in place: `claude plugin marketplace list` has no `--json`, and `configOutputPaths` keeps AIDD writing `.claude/settings.json` for `respectGitignore` and `permissions`, so "no file under `.claude/` in the manifest" was never reachable — only the machine-local entry leaves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): keep the smoke suite out of the real home directory The suite already isolated `AIDD_USER_CONFIG_DIR`, which covers what this CLI writes, but not what it makes the tools write. `marketplace add --scope user` lands in the tool's home settings, and native activation shells out to `codex` and `copilot`, which register marketplaces in their own home store. Pointed at the real home, every run left a registration behind naming a temp directory the script then deleted. That is not theoretical: `~/.claude/settings.json` and copilot's global store each carried an `aidd-framework` entry pointing into a deleted `aidd-smoke-tools-*` directory on a developer machine. It also shadowed a real project's registration by name, which is how it was found. Moving HOME into the run's own temp root fixes both. The token is read first, since `gh` looks for its credentials under the real home. Verified by counting the leftover entries in both tools around a full run: unchanged, where before each run added one. The suite still passes 98 checks across 36 of 36 leaf commands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * feat(cli): keep the machine-local registration out of the committed settings `.claude/settings.json` is committed, and it carried the marketplace this CLI registers for itself. That registration names the tree built under `.aidd/cache/` by absolute path — and `.aidd/cache/` is the one line the CLI adds to `.gitignore`. So a teammate cloning the repository inherited a pointer to a directory that could not exist for them, and would not exist until they ran setup themselves. It is not a matter of which source was chosen. `builtSourcesForTool` replaces every successfully built marketplace with the built directory, and `resolveSourceForSettings` makes that absolute, so the declared source is never what lands in the file. Both `--source remote` and `--source local` produce a path belonging to one machine. The sibling key is the opposite. `enabledPlugins` holds `plugin@marketplace` names, no paths at all, and is worth sharing. So the cut is between the two keys, not between tools or between entries: `MarketplaceSettings` gains `marketplacesSettingsPath`, mirroring the `enabledPluginsSettingsPath` it already had, and the Claude profile sends its registrations to `.claude/settings.local.json` — the file Claude itself writes when told `--scope local`, and which the CLI now gitignores, since Claude does not. That file is written and never hashed: recording an absolute path in the manifest would make every other machine read as drift. Two consequences had to be handled. `status` began reporting the new file as *added*, which is the same lie in a different shape. `detectAddedFiles` already excluded `.backup` files for exactly this reason, and files a profile declares as machine-local now follow that precedent. And an untracked file announces nothing when it breaks — deleted by hand, `doctor` still called the installation healthy. `DoctorRegistrationUseCase` closes that blind spot by comparing the registry against what the file declares, and the command it suggests does fix it. Projects installed before this keep the key in their committed file, where it holds a path that is wrong for everyone but its author, so the sync takes it out and re-hashes. The golden diff shows a file appearing and one hash changing, and no command output moving at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): pin the two things the split could quietly get wrong Both were reachable, and neither was covered by what shipped with the split. The shared settings file is written twice in one sync: once to take out the registration an older install left there, once to merge the enabled plugins. Each write loads the file itself, so a wrong order would let the second resurrect the key the first removed. It does not — the eviction writes before the plugins branch reads — but the tests proved it by accident: the split case had a plugin and no stale key, the migration case had a stale key and no plugin, so the two writes never met. Asserting the plugin landed in the migration case is what makes them meet. The second is why `status` skips these files. It compares the path the profile declares against the one it rebuilds from the tool's directory, so a profile declaring `settings.local.json` rather than `.claude/settings.local.json` would silently stop being skipped and the false "added" would come back. A test around `status` cannot catch that: the file would land outside the scanned directory, drift nothing, and pass for the wrong reason — confirmed by injecting exactly that path. So the convention becomes an invariant checked across every profile in the conformance suite, where injecting it does fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * feat(cli): give each tool the answer its own architecture allows Claude offers a machine-local project file, so the registration moved there. Copilot does not, and treating the two the same would have been worse than doing nothing. VS Code reads `.github/copilot/settings.json`, not the `copilot` CLI — measured: `copilot plugin marketplace add` writes `~/.copilot/settings.json` and leaves the project file untouched. And the documentation VS Code publishes says what that file is for: "Projects can recommend plugins for team members by configuring plugin settings in the workspace settings". It is a committed, shared recommendation. A marketplace entry naming an absolute path on whoever ran the install is not a recommendation to anyone, and copilot has no local sibling to move it to — `chat.plugins.marketplaces` cannot stand in either, having application scope, which VS Code refuses in workspace settings. So the capability gained a third answer rather than a second: registrations go in the shared file, in a machine-local file, or nowhere. Copilot writes `enabledPlugins` and nothing else, and learns its marketplaces through its own CLI, which it was already being driven to do. Two defects found while checking the rest. `FileAdapter.listDirectory` returned whatever `relative()` gave it, so backslashes on Windows — while every caller compares those paths against ones written with `/` in profiles and manifests. No test could catch it: the in-memory adapter has always produced `/`, so the two implementations diverged exactly where nobody was looking. The port now states its shape and the real adapter holds to it. And `loadSettings` parsed without a guard. That file is genuinely co-owned now — Claude writes it too, and it is untracked and gitignored, which is precisely the kind of file people hand-edit. A trailing comma took the whole sync down; it now warns and lets the merge restore the keys this CLI owns. The smoke suite earned its keep: `null` slipped past a guard that only rejected `undefined` and reached `join(root, null)`, which threw and took `plugin doctor` with it. Pinned by a test that fails when the guard is loosened again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * feat(cli): let the tool write its own configuration where it offers a command A tool that publishes a command for writing its configuration writes it better than we do: in its own format, at its own scope, and it will keep doing so when that format changes. So this CLI now writes only what no command covers. Claude registers its own marketplaces. An earlier attempt drove the command at project scope, where it rewrites `.claude/settings.json` after this CLI hashed it — two writers, one recorder, drift reported forever. `--scope local` writes `.claude/settings.local.json` instead, which this CLI neither writes nor tracks, so nothing collides. Verified end to end, including that the file appears only when the binary is reachable. What resisted the rule is `enabledPlugins`. Measured rather than assumed: `claude plugin install --scope project` writes exactly `{"<plugin>@<marketplace>": true}` into the shared settings file, character for character what this CLI already writes there. Driving it would be a second way of doing the same thing, and would hand a hash-tracked file a second writer. It stays ours. The capability now separates the two axes it had been conflating: `marketplaceSettings` says *where* the file is, which the gitignore and `status` still need, and `nativeActivation` says *who* writes it. Offline follows from that: with the registration driven, an unreachable binary means no registration, exactly as for codex and copilot. That is the literal reading of the rule — the file when the tool offers no command, not when the binary is missing. Driving a command also makes the observable output depend on what the machine has installed, which turned the golden green here and red without the binary — useless as a gate. Sandboxed runs now strip from PATH every directory holding a drivable CLI and reach node through `process.execPath`; filtering by directory rather than naming directories to keep is what holds where `node` and `copilot` share `/opt/homebrew/bin`. Under that filter the golden caught two real regressions. The built tree vanished, because building had been a side effect of registering — while building is this CLI's job whoever registers, and the tree is what any registration points at. And `doctor` exited 1 to report that an uninstalled tool had not declared its marketplace, which is reporting that absent software is misconfigured. Four dead imports also go, left behind by the re-export removal two commits ago. They survived because the lint output reaching this session was being filtered; the checks here now run the binaries directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * feat(cli): carry the scope through to the tool, and take a name back only when nobody holds it A marketplace AIDD remembers at user scope was being declared in the project's tool settings, which is neither what the user asked for nor where it belongs. The scope now travels: one per-scope argument mapping on the profile, used by `add` and `remove` alike so the two cannot drift — a remove that omitted the scope its add used would, for Claude, delete the declaration from every scope at once. A project-scoped marketplace maps to Claude's *local* scope, not its project one. That looks like a mismatch and is not: the registration names the built tree by absolute path, so it belongs to one machine, while Claude's project scope writes the shared, committed file where such a path is wrong for everyone else. Local scope is project-bound and machine-bound at once, which is what the content actually is. Verified with an isolated home: project registrations land beside the project, user ones in `~/.claude/settings.json`, neither in the other. The copilot collision is fixed the only way that does not make it worse. Its registry is global and keyed by name, so a name can be held by a project that no longer exists — and it then breaks every other project's plugin installs, measured. Removing and re-adding unconditionally would have had two live projects steal the name from each other on every sync, uninstalling each other's plugins with `--force`. So the name is taken back only when the tool reports its source is gone: copilot's `plugin marketplace update` exits 1 on a missing local path and 0 otherwise. Codex's `upgrade` refuses every local marketplace alike and Claude's reports success on a path that does not exist, so neither declares the probe, and "cannot tell" reads as "leave it alone". Verified against a real stale entry on a developer machine, which now points at a live project again. The smoke suite is the only net that runs real tools — the e2e ones strip the binaries from PATH on purpose — so it grew the assertions that matter: where each scope's declaration actually reached the tool. Its own scope checks had been reading AIDD's registry, which a wrong `--scope` argument would have sailed straight past. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * fix(cli): build a user-scope marketplace outside the project that registered it A marketplace added at user scope is declared once and meant for every project, but its built tree was written under whichever project happened to register it, and that is the path the tool's global declaration named. Delete that project and the declaration points at nothing — the same disease as a global registry holding a project-local path, one level down. It now builds under this CLI's own user directory. That directory is already the user's `.aidd`, so the layout below it repeats the project one without repeating the `.aidd` segment, and the shape lives beside its project-scoped sibling rather than being spelled out at the call site. The user directory itself was being computed from scratch in three places with the same expression, and this made a fourth. One function now answers it, which also keeps the `AIDD_USER_CONFIG_DIR` override — the thing that keeps the test suites out of a real home — in a single place. Verified on a real project with an isolated home: the user marketplace builds under the config directory and `~/.claude/settings.json` points there, while the project one builds in the project and is declared beside it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * fix(cli): rebuild when the source can have changed, and tell the tools after an update Two of the three limits noted while handling scopes; the third is a deliberate non-fix. A built tree was considered fresh when the CLI version and the marketplace's catalog version both matched. For a published source that holds: different content carries a different version. For a directory on this machine it does not — someone edits a file and the version stays put, which is the whole of framework development. So `refresh` re-read the source, reported ok, and left the old build in place; only deleting `.aidd/cache/built/<name>` by hand got the change through. A local source's version is no longer believed, nor is any cached answer when a refresh was explicitly asked for. A real build is 434 files in 0.4s including node startup, so the safe answer is also the cheap one. That made the build's own diagnostics surface on every command, since almost all of them bring the cache up to date. Those lines belong to `aidd framework build`, where a build is what was asked for; the cache rebuild now traces them at debug, so `--verbose` still shows them. The golden diff is exactly that: two commands stop repeating what they skip. And `update` refreshed the marketplace cache without ever telling the tools about it, so the command a user reaches for to put a project back in order left a drifted registration exactly as it found it — `doctor` reported it, `update` did not fix it, only `marketplace refresh` did. The two now travel together there as they already do in that command. The third limit stays open by decision. Two marketplaces pointing at one source produce built trees declaring the same name, and tools key their registry by that name rather than by ours, so the second silently overwrites the first — measured, the declaration ends up naming one marketplace and pointing at the other's content. A warning for it was written and then removed: it reads the catalog name from the marketplace cache, which only exists for remote sources, so it would have stayed silent precisely in the local case where the collision happens. A rule that lies by omission is worse than no rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb * test(cli): stop the golden from depending on what has been released The golden failed on a warning nobody wrote into it: "CLI update available: v5.2.1 → v5.2.2". The update check asks GitH…
…ocess environment (#802) `plugin install --token` and `marketplace add --token` hand the flag to `createDeps`, which composes it into the auth reader every fetcher consults, ahead of `AIDD_TOKEN` and the stored credentials. Nothing writes the environment any more: the install use case loses its `Environment` and the port loses `set`. Two reads of the user's own `AIDD_TOKEN` remain, both in `runtime/auth`, both reads. Red first: the auth reader's explicit-token test failed with `expected 'env-token' to be 'flag-token'`, the marketplace wiring test with `expected "spy" to be called with arguments`. Closes #797 Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
… Claude Code does not warn (#803) `claude plugin validate` warned `Unknown field 'recommended'` on our source catalog and on every claude build, and CI now runs that validation on each pull request. Measured against Claude Code's validator: a plugin entry's `metadata` object is the one place a catalog may carry its own field without a warning, while `category`, `tags` and `keywords` are official and mean something else. The field moves there: the source catalog, the fixtures, the schema, the repository validator, the parser (which reads no other spelling) and the claude-style catalog every build writes. The nine-cell golden is re-baselined for claude and cursor, whose catalogs now carry `metadata.recommended`. Red first: the parser test failed with `expected false to be true`, the catalog builder test with `expected undefined to strictly equal { recommended: false }`. Closes #800 Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* test(cli): kill the surviving mutants of the tool capabilities, formats and registry Tests written for the survivors stryker named in the tools scope's domain layer: the mcp json-to-toml transform and user-prime merge, the command frontmatter and path builders, the agents, rules, skills, commands, settings, hooks and plugins capabilities, the registry's signal scan and activation queries, the flat hooks merges, the marketplace catalog builders, the host registration answers and the marketplace source conflict. Every test was watched red under a hand-applied mutation before the source was restored. Scope measured 80.2 before this pass (1358 killed, 67 timed out, 232 survived, 120 uncovered of 1777) and 96.8 after this lot and the infrastructure lot (1687 killed, 34 timed out, 50 survived, 6 uncovered). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the tool host readers and plugin CLI adapter Tests written for the survivors stryker named in the tools scope's infrastructure: the plugin CLI adapter's marketplace removal, source check, declared verbs, failure messages and batch-shim spawn; the PATH lookup's empty segments, quoting and host description; Claude's, Codex's and Copilot's plugin registries at the edges of their shapes; and Claude's marketplace registry on documents that are not objects, entries without an install location and a dead path. Every test was watched red under a hand-applied mutation before the source was restored. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): raise the tools mutation floor to 94 Measured 96.8 under run-mutation.mjs tools --force after the two test lots; the floor sits two under the measurement, as the other scopes' floors do. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): build the expected signal paths with the platform separator hasToolSignals joins its answers with node's own path.join, so on Windows the registry test compared a backslash path against a slash literal and failed there alone. The expected list is now built the same way the source builds it; the intention of the test is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…#808) The coverage job's `apt-get update` fails a few times a day on the Google Chrome source the runner image ships, whose mirror serves a stale index ("Hash Sum mismatch"), and the whole job goes red before a test runs; it hit #804 and #807 within an hour. `expect` comes from ubuntu's own archive, so the chrome source is removed before the update. Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* test(cli): add the test doubles the framework survivors need Recording, choosing and checkbox prompters, a faulting file adapter, a stub asset provider and a stub AI tool, plus a save counter on the in-memory manifest repository and select and input recorders on the scripted prompter. Every double is consumed by the tests in the following commits. Framework mutation score before this series: 72.2. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the framework domain Manifest, tool entries, tracked files, native registrations, mcp exclusions, serialization, setup flow, config capability, project context, tool recommendations, installed plugins and rules, markdown references, install scope, marketplace source drift and the plugin source resolver: 91 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the framework adapters Plugin distribution reader, user source references, manifest and user manifest repositories, manifest file io and the environment adapter: 34 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the install and setup use cases Runtime and IDE config install, AI and IDE tool install, config install, content sections, gitignore, init, setup and its tools, machine scope, marketplace source and project context steps, update decisions and one-tool update: 63 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the plugin use cases Plugin add, install, install from marketplace, remove, update, search, target resolution and helpers: 68 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the clean use cases Clean and clean user scope, including the shared reference guard: 61 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the status and doctor use cases Status, status all, doctor, doctor all, registration, references, tracked files, merge files, layout and plugin checks: 65 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the restore and uninstall use cases Restore, restore all, restore all plugins, tool files, merge and regular files, restore decisions, tool distribution generation, uninstall, uninstall tools, ide, plugin and mcp exclusion: 76 tests, each shown red first against the mutant it names. The mcp exclusion use case had no test before. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the marketplace flows Marketplace sync settings, remove and check: 60 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): kill the surviving mutants of the translators and shared steps Built tree, flat and marketplace translators, project hooks materializer, ensure built marketplace, catalog identity, marketplace registration, project hooks removal, user scope files, cache purges, shared source references, plugin drift, plugin files and native calls: 66 tests, each shown red first against the mutant it names. Framework mutation score: 72.2 before the series, 95.4 after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): raise the framework mutation floor to 93 Measured 95.4 after the survivor series (5388 killed, 225 survived, 36 uncovered of 5649), up from 72.2 (4029 killed, 50 timed out, 1186 survived, 384 uncovered). The floor is the measured score minus two. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 * test(cli): make two framework survivor tests separator-agnostic The in-memory file adapter normalises every key to forward slashes, so a listing is compared to the normalised form rather than to a platform join. The user source references adapter joins its own path, so the expected error messages are built with the same join and the unparsable-JSON reason is matched as a substring rather than a slash-specific regex. Both failed only on Windows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011x4ms5qcGuZgYhCxfdHMUb AIDD-Session-Id: 4acc9a1c-19bc-4468-b8b6-e86644bcba60 --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
blafourcade
approved these changes
Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated promotion of
nexttomain, from a snapshot of next taken at run 34395640885. Merged as a merge commit somainkeeps every conventional commit for release-please, and so both branches keep a shared merge base for the back-merge. Do not squash, do not rebase.