Skip to content

ci: promote next to main - #1038

Merged
blafourcade merged 66 commits into
mainfrom
promote/next-to-main-38873662585
Oct 16, 2026
Merged

blafourcade merged 66 commits into
mainfrom
promote/next-to-main-38873662585

Conversation

@aidd-bot

@aidd-bot aidd-bot Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Automated promotion of next to main, from a snapshot of next taken at run 38873662585. Merged as a merge commit so main keeps 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.

alexsoyes and others added 30 commits September 15, 2026 12:25
Keep generated project instructions concise and context-aware. Prefer high-signal bullets, remove test-only greeting and full-memory fallback, and batch work only when it saves time or context.
aidd-qa-v1.0.0 shipped; the pin would make every later release PR
propose 1.0.0 again.


Claude-Session: https://claude.ai/code/session_01WuxKN5bm96LFhU97rsgBUW
AIDD-Session-Id: 6d6ccc35-f6c1-417d-84a6-9a0b70474e6b

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…933)

Bumps the npm-dev-dependencies group with 2 updates: [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) and [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional).


Updates `@commitlint/cli` from 21.2.2 to 21.2.3
- [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.3/@commitlint/cli)

Updates `@commitlint/config-conventional` from 21.2.2 to 21.2.3
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.2.3/@commitlint/config-conventional)

---
updated-dependencies:
- dependency-name: "@commitlint/cli"
  dependency-version: 21.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 21.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the github-actions group with 3 updates: [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `github/codeql-action/init` from 4.38.1 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@1c5b675...2892aa5)

Updates `github/codeql-action/autobuild` from 4.38.1 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@1c5b675...2892aa5)

Updates `github/codeql-action/analyze` from 4.38.1 to 4.38.2
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@1c5b675...2892aa5)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.8.0 to 1.9.0.
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](squirrelchat/smol-toml@v1.8.0...v1.9.0)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.9.0
  dependency-type: direct:production
  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>
fix(aidd-dev): validate all CI failures before repushing
docs(aidd-context): shorten repeated file citations
Prioritize recommendations and surface only what the reader needs to understand or act. Keep instructions concise and offer compaction only after addressing frustration.
Bumps [simple-git](https://github.com/steveukx/git-js/tree/HEAD/simple-git) from 3.36.0 to 4.0.2.
- [Release notes](https://github.com/steveukx/git-js/releases)
- [Changelog](https://github.com/steveukx/git-js/blob/main/simple-git/CHANGELOG.md)
- [Commits](https://github.com/steveukx/git-js/commits/simple-git@4.0.2/simple-git)

---
updated-dependencies:
- dependency-name: simple-git
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
* fix(cli): render setup constructor validation errors

Keep unsupported user-scope combinations inside the command error boundary so intentional refusals print remedies without a stack trace. Document current registration and installation scope limits.

Refs #891

AIDD-Session-Id: 01a111bd-4959-7be1-9ad4-093a46ef8295

* docs(cli): record independent setup validation review

Record the approved review and outcome challenge for #891, mark the plan reviewed, and clarify that dependency construction assertions apply to the setup action.

AIDD-Session-Id: 01a111bd-4959-7be1-9ad4-093a46ef8295

* docs(cli): keep the user-scope table to what setup refuses

The table carried a second subject — plugin install file scope — and a closing
paragraph that restated it, then recited what the change does not do. Both are
out of what #891 asks, which is the refusal and the state already shipped.

It also stated the support list by hand next to a --help list generated from
supportsUserScopeActivation. Two sources for one fact, and only one of them can
drift. The table now says the minimum and points at the generated one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* fix(framework): a path the reader creates is not a dead reference

`referenced-paths` anchors on whatever top-level entry is on disk, so `.aidd/`
becomes an anchor the moment a contributor runs the CLI in this checkout. From
then on the two pages documenting `.aidd/config.json` — the telemetry opt-in
switch a repository commits to turn measurement on — were refused for naming a
file this repository deliberately does not hold. Its absence is the state those
pages describe.

The checker now skips that path where it skips a command or a placeholder. The
test states the case with the anchor present, so it is red on a clean clone too,
where the old gate passed by accident.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* docs(framework): deleting a remote branch is the one push to skip the gates on

`git push --delete` runs the pre-push gates over a push that carries no commit:
41 seconds of the cli suite to remove a branch. Three measurements say no fix is
reachable from inside the hook — lefthook forwards git pre-push stdin to no
command, the hook environment is byte-identical to a normal push, and a glob
filter still matches. Owning the git hook file to read that stdin is a
structural change for a rare operation, so the limit is written down instead,
inside the paragraph that already rules on --no-verify rather than beside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…irectory (#968)

The sandbox PATH drops every directory holding a drivable tool binary, which is
right for the tool and wrong for node: npm installs `codex` into the bin
directory of the node that ran npm, so the one directory the sandbox cannot do
without is also one it would drop. On a machine with codex installed that way
the sandbox reached no node at all, and the pre-push suite failed on the guard
that exists to catch exactly that — a sandbox reaching neither node nor git
would make the tool guard pass by breaking everything it exercises.

The needed binaries are now linked into one directory placed first on that PATH,
so the filter costs a tool its reachability and never costs the sandbox node,
git or gh. POSIX only: linking node.exe needs a privilege a Windows runner may
not have, and the npm layout behind this puts a .cmd shim beside node there.

The new guard builds the crowded directory itself, since no runner can be relied
on to have one. It is red on the old code for the reason it names.


AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(orchestrator): the sdlc flow pauses when asked

It ran autonomously or not at all. A refactor kept the autonomy and
dropped the rest, so a person who wanted to see the contract, the plan
and the verdict before anything shipped had no way to ask.

`interactive` beside the source now stops the run at each artifact that
can still be corrected, and a refusal there is a finding routed back to
the zone that owns it. `auto` stays the default and is unchanged.

The pauses are the orchestrator's own behaviour, written once in its
router: no called skill changes. The planning step already carried both
branches and switches on what the router declares.

Check gains one rule, because the flow could not reach a person at all
when a strict checker never cleared: a finding that recurs after a pass
acted on it stops being routed and becomes the user's to decide.

The ship recipe committed and then asked for a request on a branch
nothing had pushed. It passes the commit skill its push option now.

Verified by eighty-five headless runs over sixty-four decision edges,
then forty-four more measuring dispersion rather than presence. Twelve
defects in the router's own paragraph were found and closed, five of
them only by running it. What the runs could not settle is recorded
beside the plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* fix(orchestrator): each zone commits what it wrote, and the request gate becomes reachable

Four defects the case battery surfaced in zones the interactive mode did
not own. Each left the mode promising something the flow could not keep.

Frame's spec and Check's review had no committer, so a request could open
without the contract it was built from or the review that cleared it. Each
zone now commits what it wrote before handing over.

Whether an end to end journey was required rested on a file no zone
reference points at, so a run that obeyed the reading rule skipped it. The
project's testing memory decides, and nothing else does.

`planning-ready` decided whether a spec is written, and therefore how often
a person is consulted, with no test behind it. Three tests now hold it: the
source names the artifact to change, states the observable outcome, and
leaves open no decision that changes what is built.

The request gate read `no actionable finding`, and a strict checker always
had one, so two earlier runs looped four times each and never reached it.
Once the review has cleared, only a finding that contradicts the contract
reopens a zone; any other is carried onto the request as a note. A pass
budget was rejected: the number would be arbitrary and indefensible.

Eleven headless runs, one per claim and one per branch of each claim. The
direct branch held on the same request that twenty earlier runs had settled
there, so the third test is not stricter than the flow it describes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* refactor(orchestrator): the sdlc router says the same in fewer words

The mode sentence names one condition instead of four, the stop rule moves
next to the zone reading it, and the diagram carries each zone's pause so the
flow and the prose cannot drift apart.

The onboard tier note no longer names a skill from another plugin; it states
the rule any autonomous skill follows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* fix(orchestrator): the journey and the planning-ready test are read, not judged

The planning-ready test becomes its own sentence, so a zone can answer it
instead of inferring it from a clause. The journey runs when the plan calls
for one and never otherwise, which moves the decision to the artifact that
can state it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* fix(orchestrator): a carried note never trips the recurrence stop

The two sentences governing a recurring finding both claimed it: a cosmetic
finding a pass had already acted on was a non-blocking note under one and a
blocking stop under the other. Three independent readers answered that the
text did not decide.

The reopening rule now runs first, and the recurrence rule speaks of a finding
that would be routed a second time, so a note never reaches it. Four readers
re-traced both paths on the corrected text and none answered undecidable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* fix(orchestrator): an approval carries its own corrections

A Codex run of the interactive flow answered two of its three gates with
"approved, with corrections". The text had no reading for that: a correction is a
finding against the artifact, so the letter of it sent a one-line fix back
through the zone and presented the artifact again. The run advanced instead,
which is the useful behaviour and not what the text said.

Three readers traced the three answers a pause can get — approved, approved with
corrections, refused — and none answered undecidable. The run itself, including
the three gates exercised with a person at them, is recorded beside the batch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* docs(orchestrator): record the codex runs of both modes

The interactive gates had never been executed, only read: a headless run cannot
answer them without inventing the approval the gate exists to collect. Two Codex
runs close that, one per mode, on the same issue from the same commit.

Both reached the same fix. The gates added no quality on it, and the person at
the first one made it worse, so the comparison is recorded as measured rather
than as the result the design expected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* docs(orchestrator): the user-facing pages name both modes again

#910 asks that documentation explain both modes and their default, and the
review had stripped every mention. The description, the plugin table and the
README row say autonomous or supervised, and the catalogs follow from the
description.

The word a request carries stays `interactive`, which the argument hint already
names. The README row names it too, so the word a reader meets is the word they
can type.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* refactor(orchestrator): the mode rules live in their own reference

`## Behavior` had grown from 84 words to 313, almost all of it the mode: nine
sentences, each one a run that failed without it. Compressing them would have
traded a proven case for a shorter page — an earlier pass already lost the plural
in "every wait a step takes" that way and let brainstorm skip its own pause.

So they move whole into `references/mode.md`, a section per mode, and the router
keeps the dispatch alone at 77 words. One redundancy went with the structure:
inside a section named Interactive, "dispatch in interactive" says nothing.

Three readers starting from SKILL.md alone found the reference and applied it:
the five mode cases, the three pauses in order, the zero pauses of auto with its
three exceptions, and where a refused outcome routes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* refactor(orchestrator): the mode reference states one rule per line

Four paragraphs of run-earned sentences read as prose when every one of them is a
constraint to check. They become bulleted rules under Behavior, Auto, Interactive
and Refusal, so a reader meets one rule at a time.

Two rules went in the process. "Dispatch only the steps … and run the rest as
`## Auto` says" pointed at a section of its own file, which reads as nothing: it
now says that everything else runs without pausing. And the opening pair, naming
the three pauses then saying to present and wait, was one rule written twice.

Five readers confirm the format did not turn rules into a procedure: the five mode
cases, the three pauses in order, no bullet contradicting another, an approval
carrying a correction, and — step by step — that clarifying and formalizing are
the contract pause rather than two more, while implementing and repairing pause
for nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* fix(orchestrator): the contract pauses once, on the finished artifact

A supervised run whose source is not planning-ready waited four times, not three.
Nothing said the contract pauses once: "only the steps that produce those three
pause, clarifying and formalizing included" counts per step, "that artifact's
pause" counts per artifact, and three readers walking that path answered four.
Adding the spec sentence back only moved one of three — it was never what held
the count.

Two rules replace four. The count is now an invariant: three pauses, one per
artifact, the producing steps sharing it. And the pause is on the finished
artifact, so clarification questions are part of producing the contract rather
than pauses of their own, and the formalized spec is what gets presented.

The exceptions move above both modes, where they always belonged: as a rule of
`auto` alone they contradicted "everything else runs without pausing" the moment
Check withheld a twice-routed finding in a supervised run.

Thirteen readers across the two paths: three on the clarify-then-spec walk, three
on the contradiction, three confirming it closed, and the last three answering
that nothing is left undecided — the first of the series to say so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

* refactor(orchestrator): the check zone states one rule per line

Eight sentences in one paragraph, three of them added by this branch, and two
routings living in two files with nothing saying they were different things: a
checker finding routes by what it is about, a refusal the user gives at a pause
routes by artifact. A reader had to infer that from the word "finding" meaning
two things.

The zone now reads as rules under Behavior, Findings and Handoff, and Findings
opens by saying which of the two it governs and where the other one lives.

Three readers crossed the two on four cases — an implementation finding before
the review clears, a contract refusal at the outcome pause, a contract
contradiction after it cleared, and a plan refusal. All four landed in the same
zone for all three readers, each through the right file, and none found the two
routings in conflict.

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

* docs(orchestrator): record the supervised flow run with someone answering it

Every earlier run was headless, so the three gates were only read: a headless run
cannot answer a pause without inventing the approval the pause exists to collect.
This one stopped at each pause, reported what it presented, and continued from a
real answer — approve, approve with a correction, refuse, refuse again.

The source was deliberately not planning-ready, so the clarify then formalize
path ran. That path had never been counted: every headless interactive run used a
planning-ready source and went through Frame directly, which is why the pause
counting defect survived 38 of them and surfaced only when asked about.

Five pauses on three artifacts, two step waits correctly held not to be pauses,
ten commits, seventeen tests green, no pull request. The run also volunteered two
faults of its own conduct — probe files outside the sandbox, and a mutation
measured against a reverted tree — neither of which the reviewer had caught.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…lsewhere (#969)

* fix(aidd-pm): give spec gaps one home under open questions

The TBD marker had a form but no place, so two runs on the same feature
could put the same gap in different sections. Gaps are now listed once under
a required Open Questions section, and a residual question makes the spec
invalid. The new criterion weighs nothing, so the pass threshold is unchanged.

Refs #626

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

* docs(aidd-pm): record the plan behind the spec open questions fix

The Deliver zone wrote this plan and its phase, so it is committed with the
work it describes. It carries the backlog link to #626
and the five decisions the implementation was not free to re-make, including
the two the ticket got wrong: SKILL.md needs no change, and the validator
gains no weighted criterion.

Refs #626

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

* fix(aidd-pm): scope the spec marker rule to every other section and let refine clear entries

Review of a4d185a found two gaps.

First, the rule and both test rows said "no required section carries a
marker". Context and Stakeholders are optional, so a gap listed under Open
Questions and copied into Context passed every check while reproducing the
second run measured in #626. The rule now reads "every other section", in
tbd-marker.md and in both test rows.

Second, refine only listed what was still unanswered. With the marker no
longer inside its field, rewriting a field no longer deleted it, so one stale
entry could invalidate a spec whose content was complete. Step 4 now removes
answered entries first and writes None when none remain, with a test row.

Also drops the placeholder bullet from the spec template and the
criterion's self-description from the validator. Weights and threshold are
unchanged.

Refs #626

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

* docs(aidd-pm): name the gap once in the spec open questions plan

A checker found the plan carrying a false reason: check-doc-duplication.js
scans only docs, the two memory banks and the READMEs, so it could never have
failed a sentence shared inside a skill. The decision stands on one fact, one
home; the reason did not.

The same round found one noun written four ways across the skill, the broadest
of which now blocks validation. The plan fixes the unit: a gap is an unresolved
decision a required section depends on, and what no required section depends on
is not a gap.

Refs #626

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

* fix(aidd-pm): define a spec gap once and make the validator criterion spec-wide

Finding 1: the spec skill named one unit four ways (unresolved decision,
gap, missing required field, field still unanswered). tbd-marker.md now
defines a gap once, as an unresolved decision a required section depends
on, excluding what no required section depends on and implementation
detail. 01-build, 02-refine, the template and the validator point at it.

Finding 2: the open_questions criterion now states that no TBD marker
appears outside Open Questions anywhere in the spec, Context included, so
an evaluator reading the yml alone has one reading. Weights and
pass_threshold are unchanged.

Also: 02-refine removes an entry only once its decision is fully
answered, and rewrites it to the remaining question otherwise.

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

* docs(aidd-pm): record the spec open questions journey and review

The validator now refuses both cases phase-1 named and never ran: a residual
entry, and a None section with a marker under Context. Placement and the
measured case hold.

The thin-request runs failed their criterion. Two of three still ask which
fields the CSV must carry, which no required section depends on. The unit
bounds where a gap is written, not which questions are raised.

Also repairs a sentence in tbd-marker.md that read "nowhere else, anywhere in
the spec".

Refs #626

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

* fix(aidd-pm): cut the spec gap rules to the words that change the draft

The heading was hardcoded in five files. The template owns it now, and nothing
else names it.

Cutting the text also closed the over-ask the previous evidence caught: on the
same thin request, drafts went from 5, 4 and 6 entries with CSV field lists and
refusal wording to 2, 4 and 2 with none. The rule did not change, only its
length.

Refs #626

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

* fix(aidd-pm): say the refine gap step once

It said answered twice and rewrite twice. Three clauses, three behaviours, no
word spent on a second pass.

Refs #626

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

* refactor(aidd-pm): cut the spec actions to the words that change the draft

The gap rules were lean and the text around them was not, so the skill still
read as mostly prose. 01-build loses a quarter of its words, 02-refine and the
template guidance the rest: the restated sub-bullet, the parenthetical listing
the optional sections the template already marks, the three example verbs, and
the two paragraphs explaining the backlog declaration.

Every rule is kept. A fresh context given the shortened step 6 still writes
backlog-link.json for a named ticket, writes none for no ticket, and leaves an
existing file untouched for a different one. A fourth draft of the thin request
listed three gaps, none of them presentation detail.

This goes past the issue, on the user asking for the existing text to be cut
too.

Refs #626

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

* fix(aidd-pm): put back the three clauses the cut should have kept

A checker measured what the last cut cost, and two of the three were real.

Refine lost "None when none remains", so resolving the last gap left an empty
section 3 times out of 3, and the validator refuses that. The validator lost
the definition of a gap, so an evaluator reading the yml alone supplied its own
and refused a spec whose content was complete. Refine never linked the
template, so on a spec carrying no such section it invented the heading in
lower case 3 times out of 3. All three now pass 3 of 3.

01-build gets back what written_by holds, which its own test row requires.

The journey no longer claims the cut caused the shorter drafts: that cut
changed several clauses at once, so length was never isolated. It reports what
is measured instead, including 0 markers outside the section across 22 drafts
and None on a fully answered request 3 times out of 3.

Refs #626

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

* docs(aidd-pm): record what the installed tree does, and what it refuses

Eight sessions reading SKILL.md from a translated sandbox and following it.
Placement holds: the section exists 8 of 8, no marker outside it 8 of 8. Zero
does not: a request answering every required field still lists entries 3 of 3,
and the new hard threshold then refuses that spec.

The pasted-text runs were optimistic. They measured None 3 of 3 where the
router measures 0 of 3, because the router reads more files and asks more.

Also records why the first installed-skill attempt measured nothing: the Skill
tool resolves to the user-scope install whatever a project declares, and a
project marketplace does not register in a headless session.

Refs #626

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

* fix(aidd-pm): a gap written elsewhere refuses the spec, a gap still open does not

Measured through the installed tree: a request answering every required field
still lists 3, 2 and 2 entries, always the column set and when the seven days
start. Neither is a decision a required section depends on. So the threshold
added here refused specs where nothing was missing, and before this branch an
extra question cost nothing.

open_questions_unresolved becomes open_questions_outside_their_section. A
listed gap is valid, a gap written in any other section is not, which is the
defect the issue measured. Driving to zero moves to the template as the
intention it is, and to #625 as the only thing that will enforce it.

Also closes four record findings: the plan claimed one home for a definition
the validator must repeat, phase-1 still framed the rule on required sections,
01-build had lost what a backlog item is and the one-support rule, and two
files named two authorities for one check.

Measured, three runs each: a listed gap valid 3/3, a gap under Context invalid
3/3, None valid 3/3.

Refs #626

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

* refactor(aidd-pm): the spec skill keeps only the words that change a draft

The backlog declaration was a contract inside a step, so it moves to its own
reference and the step becomes one line. Four duplications go with it: the
template already gives headers, bullets and a non-goals section, so the
transversal rule saying to was redundant; the drop-implementation-detail clause
is already transversal; the validator repeated the plan boundary its own hard
threshold carries; and two files named two authorities for one section check.

The assets lose their padding. The template keeps one placeholder bullet per
list instead of two and states each rule once. The validator reads as a
checklist again, every criterion one line, arithmetic untouched: required 90,
total 100, pass_threshold 90, five hard thresholds.

1379 words to 1050, with a section and a reference added since.

Measured on the rebuilt tree: every required section present 4 of 4, the
section exists 4 of 4, no marker outside it 4 of 4, and the three validator
cases unchanged at 2 of 2 each.

Refs #626

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

* refactor(aidd-pm): the backlog declaration shape becomes an asset

The JSON was a scaffold inside a reference, so it moves to
assets/backlog-link-template.json beside the spec template, and the reference
keeps only the rules. The guard that pins the taught field names follows it.

Tested through the tree, which the declaration never had been and refine never
had at all: a named ticket writes the file with all three fields, no ticket
writes none, an existing file for another item is left untouched, and refine
drops a resolved entry then reads None on the last.

Refs #626

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

* fix(aidd-pm): a section says it is optional in its guidance, not in its heading

The template shipped `## Stakeholders (optional)`, and a drafted spec carried
the parenthesis into its own heading. Optionality belongs to the template that
teaches it, not to the artefact.

Tested through the tree: no drafted spec carries it, 6 runs out of 6.

Two paths nothing had exercised, both green. From a PRD, 3 runs out of 3 carry
every required section and lift the PRD own open question as the one gap. On a
second model, 3 runs out of 3 place every gap in the section, which settles
that the over-asking is not one model habit: 6, 7 and 7 entries against 6 to 8.

Refs #626

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

* test(cli): the taught backlog example can be an asset, not only a fenced block

CI caught what no local gate could: the spec skill ships its backlog-link shape
as an asset now, and this guard extracted the first fenced json block out of
the action, which no longer has one. It read null and failed, exactly as a
guard should.

The loader accepts both forms, the asset bytes or the fenced block, so each
skill still feeds its own example through the real adapter.

Worth noting: the pre-push gate globs cli/**, so a plugins-only change runs no
cli test locally, while cli-ci.yml runs them. That is the gap this slipped
through.

Refs #626

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

* test(cli): drop the comments the code already says

The loader docblock restated its own body, and the header docblock explained
which skill ships which form, which the two paths say. One line stays: why the
real adapter and not a stand-in parser, the only thing the code cannot show.

Refs #626

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…974)

## 🎯 What & why

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

## 🛠️ How it works

[`a-backlog-link-the-reader-can-read.test.js`](scripts/__tests__/a-backlog-link-the-reader-can-read.test.js) checked that the three field names appeared as substrings. It parses the taught example now, and requires each field as a non-empty string — the same predicate the reader applies at [`task-backlog-adapter.ts:12`](cli/src/contexts/telemetry/infrastructure/task-backlog-adapter.ts#L12). A taught value that is not a plain object is refused by the predicate [`json-file.ts:5`](cli/src/kernel/reading/json-file.ts#L5) uses, so nothing the reader accepts is rejected here.

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

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

## 🧪 How to verify

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

## ⚠️ Heads-up

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

## 🔗 Linked issue

Closes #972

## ✅ I certify

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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
* fix(opencode): load hooks and telemetry in both plugin runtimes

OpenCode V2 requires a default definition and emits a different event lifecycle. Keep the V1 server entrypoint while adapting measured V2 tool and execution events.

Closes #953

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): record completed OpenCode compatibility validation

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* fix(opencode): share runtime adapter across plugin delivery paths

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): record shared runtime delivery and final validation

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): record independent OpenCode review and challenge

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* fix(cli): verify Kilo hooks and repair mutation asset loading

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* test(cli): cover Kilo parser and session lifecycle mutations

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* test(cli): assert final Kilo hook counts after shutdown

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): record independent Kilo verification review

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* test(cli): refresh Kilo bridge golden after lifecycle support

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): verify corrected Kilo golden baseline

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* fix(cli): keep embedded configuration bytes stable on Windows

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): record Windows checkout verification

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* test(cli): remove code interpolation from Kilo probes

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* refactor(cli): remove obsolete Kilo parser and stale hook comments

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): shorten documentation and add concise Claude rules

Preserve operational contracts, verification commands and Kanban metadata while removing repetition and transient snapshots.

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): keep architecture focused on boundaries

Link implementation protocols to canonical sources instead of duplicating them in the framework overview.

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): clarify architecture and telemetry for all readers

Explain core concepts visually and keep operational safeguards visible while linking detailed implementation contracts.

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): organize documentation by section purpose

Keep responsibilities, usage, interpretation and safeguards in their own sections while preserving linked anchors and contracts.

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): consolidate concise documentation rules

Keep the same documentation safeguards in fewer instructions.

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): explain architecture from marketplace to components

Give each plugin component a clear role, keep adapter details with the CLI, and correct conflicting public documentation.

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* docs(framework): prefer architecture diagrams and tables

Keep the architecture focused on stable relationships and responsibilities, with concise prose and preserved safeguards.

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3

* fix(cli): repair runtime files for current plugins

Refs #953.

AIDD-Session-Id: 01a11696-08a4-78b1-b05c-c456b9fbceb3
docs(aidd-context): reduce cognitive load in agents instructions
One phase of a 7-phase plan, one diff, three models: `100% (6/6)` on 7 blocks,
`100% (6/6)` on 1 block, `53% (16/34)` where 16/34 is 47%, and boxes copied from
the previous run. The skill never said what a phase outside the diff becomes, how
the percent is computed, or where a past round lives.

A review now appends one section per round and is never rewritten. A round carries
its date, its author from `git config user.name`, its diff, the axes that ran, a
verdict and a score; its `Criteria` list holds the plan's criteria and its
`Findings` list the defects no criterion covers. The score counts over the plan's
whole criteria total, in three numbers that sum to it, so a plan covered at a
third can no longer read as finished.

The skill follows `skill-generate`'s authoring contract: the router holds the
flow, the five actions and two transversal rules, and nothing else. `01-prepare`
resolves the diff once for the three axes and opens the round; `05-finalize`
judges it and checks its shape. `references/report-contract.md` holds where the
report lives, what diff is reviewed and how a round joins it. An action says what
to decide; the template owns the names and the form, the validator the field set.
The skill reads 1583 words, against 1785 on `next`.

Severity reads `critical`, `major`, `minor`: one scale, where `warning` put a log
level between two gravities. Three files outside this skill still say `warning`.
A critical is the harm a merge would do to production, its users, their data or
what depends on it, never how strictly a rule is worded. Before, a broken
declared rule read critical in 7 of 12 runs and a crash in production blocked in
3 of 5; now 0 of 10 and 5 of 5, over 50 runs on ten defect fixtures with every
verdict unanimous.

Measured, each with the mutation that proves it: 31 assertions, 39 mutations, each
reddening one assertion alone. The assertions spell a path the way the documents
spell it, so Windows and macOS read the same tree. 71 real runs on the final
text: 50 of 50 functional runs over 10 fixtures scored right, including 10 phases
and 34 criteria reading `3/34 met, 5 unmet, 26 out of the diff`; 3 of 3 runs
without a plan read `not scored`; 33 of 33 planted defects found across the three
axes; no run patched a file. Against `next` on the same fixtures, counting checked
boxes against the truth: 6 wrong runs of 18.

Four rules come from those campaigns, each turning a split between runs into
unanimity: a context line of the diff is evidence, a criterion whose subject is
absent is a gap rather than `not-applicable`, a phase projecting no file is traced
rather than out of the diff, and a label echoing the criterion's words is not
evidence.

Refs #858


Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z
AIDD-Session-Id: bf713ca4-3c1b-491d-ac0a-4d4533330b5c

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#995)

On `next`, a criterion only a run could show was checked from the code alone:
"submitting the form displays a success banner" came out `[x]` citing
`src/signup.js:3` in 9 runs of 9, including one where a recorded run had seen
that banner fail.

A review stays static. A criterion whose subject is in the diff but whose outcome
only a run could show, what a screen displays, a timing, an outside system's
answer, is now unverified at runtime: named in the round with what a run would
show, never checked from the code, and never blocking the verdict. A run recorded
beside the plan on the reviewed head decides it instead: passing checks it,
citing the record; failing leaves it unchecked; a record from another head
leaves it unverified. The score gains a fourth number, so met, unmet, unverified
and out of the diff sum to the plan's whole criteria total.

This replaces #977's "a not-applicable criterion counts as met": such a
criterion is now unverified, and `not-applicable` is gone from the skill.

Measured with `claude -p` against the plugin from this tree, 103 runs:
- 85 functional runs: every fixture scored as expected, including six traps:
  a record naming a branch, a working-tree edit after the run, a stale record
  covering a criterion the code proves, a blocked run, a mixed criterion, and a
  backend criterion that must stay checked.
- 18 all-axes runs: verdicts unchanged (approve, blocked, then
  changes-requested), no unverified line on criteria the code proves.

A round whose every criterion is unverified approves with `0/N met`; its score
says so.

Refs #976


Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
blafourcade and others added 26 commits October 12, 2026 07:00
…i/next/simple-git-4.0.2

chore(deps): bump simple-git from 3.36.0 to 4.0.2 in /cli
…er (#912)

chore(deps-dev): bump the npm-dev-dependencies group across 1 directory with 11 updates

Bumps the npm-dev-dependencies group with 11 updates in the /cli directory:

| Package | From | To |
| --- | --- | --- |
| [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.5.11` | `2.5.15` |
| [@commitlint/cli](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/cli) | `21.2.2` | `21.2.3` |
| [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/HEAD/@commitlint/config-conventional) | `21.2.2` | `21.2.3` |
| [@stryker-mutator/core](https://github.com/stryker-mutator/stryker-js/tree/HEAD/packages/core) | `9.6.1` | `10.0.0` |
| [@stryker-mutator/vitest-runner](https://github.com/stryker-mutator/stryker-js/tree/HEAD/packages/vitest-runner) | `9.6.1` | `10.0.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.4.0` | `26.6.4` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `3.2.6` | `5.0.3` |
| [fast-check](https://github.com/dubzzz/fast-check/tree/HEAD/packages/fast-check) | `4.9.0` | `4.10.2` |
| [jscpd](https://github.com/kucherenko/jscpd/tree/HEAD/rust/jscpd) | `5.0.16` | `5.4.0` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.33.0` | `6.39.0` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `3.2.6` | `5.0.3` |



Updates `@biomejs/biome` from 2.5.11 to 2.5.15
- [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.15/packages/@biomejs/biome)

Updates `@commitlint/cli` from 21.2.2 to 21.2.3
- [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.3/@commitlint/cli)

Updates `@commitlint/config-conventional` from 21.2.2 to 21.2.3
- [Release notes](https://github.com/conventional-changelog/commitlint/releases)
- [Changelog](https://github.com/conventional-changelog/commitlint/blob/master/@commitlint/config-conventional/CHANGELOG.md)
- [Commits](https://github.com/conventional-changelog/commitlint/commits/v21.2.3/@commitlint/config-conventional)

Updates `@stryker-mutator/core` from 9.6.1 to 10.0.0
- [Release notes](https://github.com/stryker-mutator/stryker-js/releases)
- [Changelog](https://github.com/stryker-mutator/stryker-js/blob/master/packages/core/CHANGELOG.md)
- [Commits](https://github.com/stryker-mutator/stryker-js/commits/v10.0.0/packages/core)

Updates `@stryker-mutator/vitest-runner` from 9.6.1 to 10.0.0
- [Release notes](https://github.com/stryker-mutator/stryker-js/releases)
- [Changelog](https://github.com/stryker-mutator/stryker-js/blob/master/packages/vitest-runner/CHANGELOG.md)
- [Commits](https://github.com/stryker-mutator/stryker-js/commits/v10.0.0/packages/vitest-runner)

Updates `@types/node` from 26.4.0 to 26.6.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@vitest/coverage-v8` from 3.2.6 to 5.0.3
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.3/packages/coverage-v8)

Updates `fast-check` from 4.9.0 to 4.10.2
- [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.10.2/packages/fast-check)

Updates `jscpd` from 5.0.16 to 5.4.0
- [Release notes](https://github.com/kucherenko/jscpd/releases)
- [Commits](https://github.com/kucherenko/jscpd/commits/v5.4.0/rust/jscpd)

Updates `knip` from 6.33.0 to 6.39.0
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.39.0/packages/knip)

Updates `vitest` from 3.2.6 to 5.0.3
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.3/packages/vitest)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: "@commitlint/cli"
  dependency-version: 21.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: "@commitlint/config-conventional"
  dependency-version: 21.2.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: "@stryker-mutator/core"
  dependency-version: 10.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dev-dependencies
- dependency-name: "@stryker-mutator/vitest-runner"
  dependency-version: 10.0.0
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dev-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.6.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dev-dependencies
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dev-dependencies
- dependency-name: fast-check
  dependency-version: 4.10.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dev-dependencies
- dependency-name: jscpd
  dependency-version: 5.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dev-dependencies
- dependency-name: knip
  dependency-version: 6.37.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dev-dependencies
- dependency-name: vitest
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: npm-dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Baptiste LAFOURCADE <baptiste.lafourcade@gmail.com>
…1000)

* test(cli): run the Biome guards on Windows

`biome-guards-bite.arch.test.ts` spawns `node_modules/.bin/biome` directly.
Windows reaches Biome through a `.cmd` shim, so the launch reportedly fails with
ENOENT (#881), and the Windows job never runs the architecture suite, so
nothing here has shown it. The Windows job now runs that one file.

Refs #881

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* fix(cli): launch Biome through node in the architecture guards

On the Windows runner the Biome guards failed with `expected [] to include
'src/kernel/exit.ts:plugin'` and two more like it: spawning
`node_modules/.bin/biome` directly found no executable there, and the empty
output read as a tree with no findings.

The guards now run the package's own launcher, `@biomejs/biome/bin/biome`,
through `process.execPath`, which works on every platform. A run that is
neither clean (exit 0) nor diagnosed (exit 1 with a finding) throws instead of
returning nothing, so a guard asserting that no finding exists can no longer
pass when Biome never ran. A test with a missing launcher was red before this.

Closes #881

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* fix(cli): read Biome's Windows paths in the architecture guards

With Biome launched, the Windows guards still failed with `expected [ …(5) ]
to include 'src/kernel/exit.ts:plugin'`. Biome found the five planted findings
it finds elsewhere, but names a file with backslashes there, so no key
matched. The parser now turns the separators into forward slashes, under a
test fed a Windows line.

Refs #881

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* chore(ci): name the Windows hook steps by what they do

A CI step name, an error message and a test comment carried the number of the
issue that led to them. Each now says what it checks, and the issue numbers
stay in the commits and pull requests that fixed them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* docs(framework): keep tracker IDs out of every file, not only docs

The documentation rule already dropped ticket and run IDs, but only for the
Markdown its paths name, so code, comments, test names and CI steps carried
them. A rule with no paths now covers every file: tracker IDs live in commits,
pull requests and issues, and the thing is named by what it does. The
documentation rule no longer repeats the clause.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* chore(ci): run the whole architecture suite on Windows

The Windows job ran one file of the architecture suite, the Biome guards.
It now runs `pnpm test:arch`, the same suite the Linux architecture job runs,
so every architecture guard is exercised on Windows, the Biome guards among
them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* docs(framework): reduce the tracker-ID rule to one bullet

The rule now reads as a single bullet, and the documentation rule keeps its own
clause unchanged: its paths only reach the documentation, so the two do not
overlap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* chore(framework): check out text files with LF on every platform

Run on Windows, the architecture suite failed in three files that read text by
its lines: `codebase-map.md has no fenced block`, `expected [] to include
'knip'`, and a SyntaxError loading `mutation-covers-source`. A Windows checkout
turns LF into CRLF, which the repository's scripts and tests do not expect.

`.gitattributes` now asks for LF on every platform, `text=auto` leaving binary
files alone. No committed file changes: the only CRLF files are archived
evidence whose own `.gitattributes` already mark them `-text`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* fix(cli): resolve both sides of a Biome finding's path in the guards

After the merge with #912, the Windows guards still failed with
`expected [ …(5) ] to include 'src/kernel/exit.ts:plugin'`: Biome found the
five planted findings, and none of their paths matched. Only the planted tree
went through realpathSync, so a file Biome named through another spelling of
the same directory could not be made relative to it. Each file now resolves
through realpathSync too, and a failed match prints the findings it saw.

Refs #881

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* fix(cli): decode the file the Biome GitHub reporter escapes

With the findings now printed, the Windows guards failed on
`ENOENT ... lstat '...\aidd-biome-bite-BHHWDT\C%3A'`. On Windows Biome reports
an absolute file, and the GitHub reporter escapes its drive colon as `%3A`,
so the path resolved under the planted tree instead of to the file. The guard
decodes the reporter's `%3A`, `%2C` and `%25` before resolving the path, under
a test that was red first.

Refs #881

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Normalize the memory directory prefix before building README links on Windows.

Add regression coverage and isolate hook tests from inherited CLAUDE_PROJECT_DIR.

Closes #986
* docs(aidd-context): refresh cook recipes

Keep recipe steps actionable, enforce valid heading nesting, and preserve verified token and MCP guidance.

* docs(aidd-context): streamline token optimization recipe

Separate durable agent instructions from scoped rules and keep each optimization technique directly actionable.

* feat(aidd-context): validate technical recipes

Keep bundled guidance concise, reproducible, and mechanically verifiable.

* chore(cli): migrate Biome configuration

Keep lint settings compatible with the installed Biome schema.
* fix(cli): preserve exact mutation filtering with vitest 5

* test(cli): assert recipe and plugin removal contracts

* test(cli): preserve tracked files and unconfirmed native refs
…#1004)

fix(cli): validate github token responses without leaking credentials

Cover the HTTP authentication boundary and normalize failures without preserving request credentials.

Refs #467
…1005)

A run of table rows with no separator under the Actions table was read as the
table resumed after a blank line, so a glossary there could donate a citation
and hide a deleted row. A blank line ends a table, as every renderer reads it:
such a run now draws one refusal at its first line, and cites nothing.

Orthogonality keeps reading through fences while router coherence does not,
on purpose and now said in the code: a fenced command is still one the model
runs. A test pins it.

R8 says that two actions sharing a slug keep their number, the form the guard
already cites.

The split-table unit test and the property test's split shapes now expect that
single refusal. Every router in the tree still passes.

Closes #884


Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs(aidd-context): let rule generation cover OpenCode V1

The rule contract excluded OpenCode V1, yet the contribution it writes for
OpenCode is the root `AGENTS.md`, and OpenCode V1's rules documentation reads
that file: "Place an AGENTS.md in your project root for project-specific
rules", with config `instructions` only combined with it. The OpenCode row now
covers V1 and V2, and the V1 documentation joins the checked contracts.

This rests on the documentation: no OpenCode 1.x session has been run against
a generated rule.

Closes #913

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* docs(aidd-context): name OpenCode once, with no version and no date

Both OpenCode versions read the root `AGENTS.md`, so the row and the sentence
under the table name OpenCode alone, and the checked-contracts line loses its
date. The tracker-ID rule now also keeps dates out, and covers docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

* docs(aidd-context): state the OpenCode target as one instruction

The sentence listing what OpenCode does not read named three formats a
generator could pick back up. It now says only where to write and the one
wrong target the contract used to name, `.opencode/rules`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VE16AxC1V9wAMGmGgefe4z

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
framework, tools and kernel run in 4 legs, telemetry in 3, tools-codex
and translate in 2, cut deterministically by file weight. A scoring job
merges the legs' reports and applies the same score and floor as one
run, and fails on a missing, drifted or empty leg. CLI CI: 27.5 to
about 14 min warm, 45.3 to 27.7 cold.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
Remove the twelve high-severity advisory paths with compatible patch floors while keeping excluded dependencies and quality thresholds unchanged.

Refs #1008
Validate the qs and esbuild parent-range exceptions without migrating their consumers. Account for the patched compiler emitting TypeScript fields correctly in the measured bundle budget.

Refs #469
…1019)

Bumps the npm-dev-dependencies group with 2 updates: [js-yaml](https://github.com/nodeca/js-yaml) and [lefthook](https://github.com/evilmartians/lefthook).


Updates `js-yaml` from 5.4.2 to 5.4.3
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@5.4.2...5.4.3)

Updates `lefthook` from 2.1.14 to 2.2.1
- [Release notes](https://github.com/evilmartians/lefthook/releases)
- [Changelog](https://github.com/evilmartians/lefthook/blob/master/CHANGELOG.md)
- [Commits](evilmartians/lefthook@v2.1.14...v2.2.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.4.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: lefthook
  dependency-version: 2.2.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…984)

Make parallel task execution discoverable and retain blocked or failed agents in the final recap.
…3 updates (#1024)

chore(deps-dev): bump the npm-dev-dependencies group

Bumps the npm-dev-dependencies group in /kanban with 3 updates: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest).


Updates `@biomejs/biome` from 2.5.14 to 2.5.15
- [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.15/packages/@biomejs/biome)

Updates `@types/node` from 26.6.2 to 26.6.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `vitest` from 5.0.2 to 5.0.3
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.3/packages/vitest)

---
updated-dependencies:
- dependency-name: "@biomejs/biome"
  dependency-version: 2.5.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.6.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: vitest
  dependency-version: 5.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…pdates (#1020)

chore(deps-dev): bump the npm-dev-dependencies group

Bumps the npm-dev-dependencies group in /cli with 3 updates: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [jscpd](https://github.com/kucherenko/jscpd/tree/HEAD/rust/jscpd) and [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip).


Updates `@types/node` from 26.6.4 to 26.6.5
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `jscpd` from 5.4.0 to 5.4.1
- [Release notes](https://github.com/kucherenko/jscpd/releases)
- [Commits](https://github.com/kucherenko/jscpd/commits/v5.4.1/rust/jscpd)

Updates `knip` from 6.39.0 to 6.41.0
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.41.0/packages/knip)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.6.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: jscpd
  dependency-version: 5.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dev-dependencies
- dependency-name: knip
  dependency-version: 6.41.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dev-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the github-actions group with 4 updates: [actions/setup-node](https://github.com/actions/setup-node), [github/codeql-action/init](https://github.com/github/codeql-action), [github/codeql-action/autobuild](https://github.com/github/codeql-action) and [github/codeql-action/analyze](https://github.com/github/codeql-action).


Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@8207627...949feb2)

Updates `github/codeql-action/init` from 4.38.2 to 4.38.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@2892aa5...24c5418)

Updates `github/codeql-action/autobuild` from 4.38.2 to 4.38.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@2892aa5...24c5418)

Updates `github/codeql-action/analyze` from 4.38.2 to 4.38.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@2892aa5...24c5418)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js) from 8.7.2 to 8.7.3.
- [Release notes](https://github.com/SBoudrias/Inquirer.js/releases)
- [Commits](https://github.com/SBoudrias/Inquirer.js/compare/@inquirer/prompts@8.7.2...@inquirer/prompts@8.7.3)

---
updated-dependencies:
- dependency-name: "@inquirer/prompts"
  dependency-version: 8.7.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 [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](squirrelchat/smol-toml@v1.9.0...v1.9.1)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.9.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>
… 1 (#993)

* docs(telemetry): frame deterministic token measurement, release 1

The contract for replacing the previous telemetry: every billed Claude Code
call counted once, broken down by person, session, model, period,
repository, task and ticket, the task coming only from a declaration.
Carries the measurements, the common usage record and the prototype
readers with their mutation-proved fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579

* docs(telemetry): plan deterministic token measurement in nine phases

Removal of the previous telemetry first, then count, store, declare,
ask, attribute and report, opt-in and cleanup, and the end-to-end
proof. Pins the consent key, the binding snapshots, the hook guards
and a mutation per rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
AIDD-Session-Id: 2c21d903-3a7e-47ac-83f8-d8b7ae3aa579

* refactor(telemetry)!: remove the previous telemetry

The previous measurement is replaced by a deterministic, declaration-attributed
one in the following commits of this branch. This commit removes the old one so
nothing half-describes the new one.

cli: delete the telemetry context, its wiring, command, displays and tests, and
everything that existed only to serve it: the measurement vocabulary, the
per-tool telemetry declarations and transcript locations, the git adapter that
installed the commit-trailer hook, the identity and sink resolvers, the
FileWriter chmodExecutable method and the run-journal gitignore entry. The
OpenCode hooks bridge no longer names the deleted plugin module in its
generated comments; the framework build golden is re-baselined for that one
line. Architecture guards, biome overrides, mutation scope, smoke steps and the
help golden describe the CLI that remains.

plugins: delete the aidd-telemetry hooks and skills (README reduced to a stub,
manifest description updated). Remove the backlog-link.json step, reference and
template from aidd-pm:04-spec and aidd-dev:01-plan, the backlog-link paragraph
from the SDLC frame, and every aidd:step-end echo with the SDLC, async-dev and
backlog "Say when this orchestration is over" sections.

scripts and CI: delete the telemetry script tests, their fixtures, the two
telemetry scripts, the generated prompts documentation and its lefthook job,
the prepare-commit-msg trailer job, the journal replay, telemetry check and
telemetry e2e exclusion in cli-ci.yml, and the identifier-join probe. Tests
that used the telemetry plugin as a byte fixture read aidd-context instead.

docs: delete the cost-report and metrics contracts and aidd_docs/runs/, rewrite
every memory, README and docs line that claimed the previous telemetry, and mark
the pending telemetry task folders of 2026_09 as superseded.

The previous version's on-disk locations are recorded in
aidd_docs/tasks/2026_10/2026_10_09_deterministic-tokens/context/previous-version-leftovers.md
for the opt-in and forget phases.

BREAKING CHANGE: the aidd telemetry commands, the aidd-telemetry hooks and skills, backlog-link.json and the AIDD-Session-Id commit trailer are removed; measurement returns, for Claude Code first, later on this branch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): count each billed Claude Code call once

Read Claude Code transcripts into one usage record per billed model call.

- key message.id:requestId, or message.id:sessionId:timestamp without a request id
- drop the synthetic model; isSidechain or agentId makes the call a subagent
- cache_write and cache_write_1h are summed over the message iterations; the
  one-hour part is unknown when any iteration lacks its split
- each advisor iteration is its own record, keyed <key>#advisor<n>
- an unrecognised shape is reported and never guessed; an unknown counter is
  null, never 0
- global fold across every file: largest total, then earliest time, then
  lowest session id, independent of input order
- source: recursive walk of the projects root including subagents and
  *.jsonl.superseded-* set-asides, byte-offset reads, an unterminated last
  line is left unconsumed, a shrunk or replaced file is re-read whole

Re-adds the telemetry context to the architecture guards (public modules,
biome layer overrides, codebase map, mutation scope, tool-name baseline).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): store usage in a local ledger, one record per call

Add `aidd telemetry ingest`: read what every Claude Code transcript gained
and store each billed call of the projects that opted in.

- ledger: <telemetry dir>/ledger/<YYYY-MM>.jsonl by the record's own UTC
  time; a changed partition is rewritten by temp file and rename, so a
  better snapshot of a call replaces the held one even across months and no
  reader meets two live records or a torn line
- idempotent: held records fold first, so an equal record changes nothing;
  the phase 3 fold decides every replacement, in any order
- lock: ledger/.lock holds pid and creation time, created exclusively; a
  dead pid or a lock older than ten minutes is cleared, a live recent one
  is waited for and released only by its owner
- repository: git rev-parse per working directory, id = sha256 of the
  normalised host/owner/repo of origin (https, ssh, scp-like, credentials,
  port and .git dropped), else the lowest root commit; a linked worktree
  resolves to its main repository
- consent: .aidd/config.json telemetry.enabled with version 2; the previous
  bare enabled is not consent; an unparseable file is counted; the main
  working tree answers for a worktree without its own file;
  AIDD_TELEMETRY=0 refuses before anything is read or written
- roots.json remembers each directory seen alive, so lines from a deleted
  one still resolve; one never seen alive is counted, not stored
- bindings/branches.jsonl snapshots branch.<name>.aiddTask|Ticket|DeclaredAt
  with the branch's creation time from its reflog, only when it changed
- write order under the lock: ledger, roots, snapshots, offsets last
- owner-only storage: 0700/0600 on POSIX, icacls on Windows
- coverage counters in the ingest result, by reason not stored

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): declare the task a branch or session works on

aidd telemetry task <name> [--ticket <ref>] | --none binds the running Claude
session from now on and, on a working branch, the branch itself through its
git config, then snapshots the declaration at once. The default branch and a
detached head are left untouched. A project that has not opted in to version 2,
or AIDD_TELEMETRY=0, is refused. With no argument the command shows what the
work is bound to and where that comes from.

The format the CLI writes and the plugin hooks read is pinned by one shared
fixture under scripts/__tests__/fixtures/telemetry-bindings/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(aidd-telemetry): ask for the task once per branch, before any token

Three Claude-only hooks, inert unless the project opted in: a prompt gate that
answers a typed declaration without the model and asks once on an unbound
working branch when a person is present, a session-start that keeps the task
across /clear and /branch, and an async catch-up that runs the ingest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): report usage on eight axes, attributed by declaration

aidd telemetry report reads new transcripts, then splits what the stored
calls consumed along total, person, session, model, day (UTC), repository,
task or ticket, four counters apart, every axis summing to the same
totals. An unknown counter is left out of its sum and counted, never added
as zero.

Attribution is one pure function: the session's declaration at the call's
time, else its carry, else the branch's declaration from the snapshots
(from the branch's creation, so declaring late moves earlier work), else
unattributed with the reason. The first declaration in a carried session
applies to the whole session. Sub-agent and advisor calls follow their
session.

aidd telemetry identity <id> writes {person_id} to identity.json in the
telemetry directory, owner-only; --off removes it. Nothing is carried over
from the previous version's file.

Ingest also reports when the oldest transcript still on disk was last
written, for the coverage line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): keep a reused branch name's earlier work on its task

Attribution read only the latest snapshot of a repository and branch name,
so a branch deleted and created again under the same name took the older
generation's calls to the new task, or to none. Snapshots are now read as
generations told apart by branch_created_at: a call belongs to the youngest
generation created strictly before it, and within that generation to the
latest snapshot, so declaring late still moves its earlier work. A snapshot
with no creation time speaks only when no dated generation matches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): opt in, opt out and forget, leaving no trace of the old

`on` writes consent at version 2, removes the previous version's commit
hook line with its script, run journal and ignore entry, and resets the
ledger offsets and remembered consent so earlier history is stored once.
`off` switches off and keeps the version. `forget` previews, and with
--yes erases both versions' files and declaration keys under the lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): add init and usage skills and the usage contract

Two thin skills over the CLI: 00-init opts in after consent, sets or
removes the identity and forgets with a preview; 01-usage maps a question
to report flags and reads the JSON without computing a figure. A test
checks every command they name against the help golden.

The usage contract, the plugin README and the CLI memory describe the
record, the version 1 envelope, the stores and the ask and carry rules.
Stale mentions of the replaced measurement go, the smoke harness covers
the telemetry commands, and the bundle budget is set to the measured
build plus 2 %.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): drive the whole journey on the built binary

Opt in, get asked on an unbound branch, declare by typing it, clear and
keep the task, run a headless session, then check that every report axis
adds up to the total and lands on the declared task and ticket. The real
hooks run through an aidd shim, a .cmd on Windows and a node script
elsewhere, with no AI tool binary on PATH.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record the real-history oracle, equal on every day

The built CLI and the prototype's independent reader counted the same
projected history: 2,746 files, 262,328 records, 68 days, no day
differing. The usage record's branch comment now says what attribution
does with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): resolve the release 1 review findings

C1: partitioning is linear, an ingest rewrites only the months that
changed, declarations and snapshots take a bindings lock and never wait
on the ledger lock.
C2: on lifts a remembered refusal for every directory of the repository.
W1: ordinary prompts that look like a declaration pass the gate.
W2: a branch redeclaration applies from its own time.
W3: consent is the clone's git config aidd.telemetry, never committed.
W4: cmd.exe /s command line, and the hook home matches the CLI's.
W5: mutation proofs recorded.
Minor: stale context docs, superseded old plan, report wording, dead
attribution reasons, V1 config block removal, forget wording, next step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record the mutation proofs of every phase

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): consent is read live from the clone, never lifted by id

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): say so when a typed declaration is not understood

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): ingest repairs a month that holds a torn line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): read the branch config inside the bindings lock

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): each lock names itself when it times out

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): show the total axis row once

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): word consent as the clone's git config everywhere

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): drop the run journal words and record fix round 2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): pin the clone consent rules against surviving mutants

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): off withdraws what the clone's deleted directories were remembered as

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): re-run the round 2 mutations at the final head

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): store a call only if its clone consented at that time

A call is stored only if its directory was seen alive in clone X, X is
the clone answering now (or the remembered X once it is gone), and X's
consent covered the call's time. A clone is its git common dir's
dev, ino, birth time and path; consent is a history of intervals kept
beside the ledger, opened by on, closed by off or by an ingest that
finds the key changed or the clone gone.

roots.json becomes format 2: one entry per directory and clone, no
consented flag. forget names only gone clones that had consented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): say how to send a mistyped declaration as a prompt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record fix round 3 and reword a stale target line

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): source the per-OS identity notes and correct round 3

The identity comment now cites the Node Stats documentation and the
libuv sources it was read from, and drops a claim about FAT and network
shares that no source gave. The proofs file states what was observed:
the damaged-month mutation at its stated scope and without the new
assertion, and row 12 at the old code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(aidd-telemetry): say that forget also ends measurement

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(aidd-telemetry): ask only where an interval is open for the clone

A clone consents for the hooks only when its key is 2:<token>, the
consent log holds an open interval with that token, and that interval
was recorded for the clone's real path. A key set by hand, a cp -R copy,
a clone made again or a moved one asks nothing and blocks nothing.

The hooks also close: when a hook passes the Claude-only guard in a
clone whose key no longer names an open interval of its path, it appends
{token, close} to consents.jsonl, whoever is present. The shared fixture
pins the log format and both decisions for the CLI and the hooks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(cli): store a call only if its clone consented at that time

Measurement starts at on and reads nothing back. Consent is a key
2:<token> in the clone's git config and an interval in
ledger/consents.jsonl ({token, clone, open} and {token, close}). A call
is stored when its directory was seen alive in a clone that had an open
interval at the call's time. on mints the token and opens the interval,
off and an ingest that sees the key stop naming it close it, and a
damaged consent log refuses every clone. on no longer resets offsets.

task and show apply the same test as the hooks. The bundle budget goes
to 667 KB (653.3 KB measured).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the consent rule, and fix round 4

The usage contract states the three-clause rule first: measurement
starts at on, a call is stored only inside an open interval of its
clone, and nothing else decides. plan.md keeps the previous decision,
marked superseded, and adds the user's. mutation-proofs.md records the
scenario table before and after, and the mutations with their red
counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record the round 4 mutation score and survivors

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(cli): a damaged log, a copy, a clone out of reach stay safe

On and off refuse over a damaged consent log, and the advice is forget then
on. The hook compares the full clone identity. An unstattable clone is
unreadable, a clone out of reach keeps its interval, a broken git config is
unreadable, and a closed consent is told apart from a never-opted clone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(cli): let the journey prove a call right after on is stored

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record the fix round 5 mutations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): mark the plan implemented, the presence probe open

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(cli): smoke off before forget, which now unsets the key

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(cli): word no-consent as no consent at that time, fix the contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): a torn last line of the consent log is not damage

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): bound the bindings lock wait for the hook's declaration

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): date the oracle method, record the fix round 6 mutations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): pass windows, codeql and the runtime mutation floor

The first CI run of this branch found three gates red.

- Windows: the posix cases of the shared fixture were joined with the
  host's separator, and one legacy-locations case compared a literal
  path with a joined one. Both now say which separator they mean.
- CodeQL: four stat-then-open races. The hook's appender opens the
  file first and measures the descriptor; the test helpers read entry
  types from readdir instead of stat-ing each path.
- Mutation (runtime) was 88.2 against a floor of 90: removing the git
  adapter took its killed mutants with it, and the Windows path of the
  private storage adapter had no coverage on a posix runner. The
  adapter takes its host (platform, environment, runner) so that path
  runs everywhere, and the case-insensitive file system check is pure.
  Local score 91.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): tell a birth from the change time to the nanosecond

On Linux a birth time equal to the change time was taken as no birth,
since libuv copies the change time when statx is refused. Compared in
milliseconds, a real birth fell in the change time's millisecond for 1
git common dir in 80 freshly cloned or copied on ext4 (probed in a
node:22 container); none was equal to the nanosecond. With the birth
dropped, a copy moved onto a deleted clone's path was known only from
ingest's first sighting, and a call made before that went to the old
clone's open interval: the CI's "copy at the path" row stored it.

The CLI and the hook now compare birthtimeNs with ctimeNs. The shared
fixture carries both, plus a case of a birth in the change time's
millisecond, which the millisecond rule fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): run the integration tier on windows

- The sandbox starts from the native real path, so a call's directory
  and the clone git reports are spelt alike (no 8.3 short name).
- Tests that lock a directory with chmod or stand in a shell-script
  git are posix-only; the mode check skips Windows, which has no
  execute bit.
- Expected paths are joined, and the icacls target is the namespaced
  path mkdir returns on Windows.
- A transcript path that is a directory is refused: Windows opens one
  and reports it empty.
- The husky caller is named with / on every platform, as the
  repository names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): root the erasure sandbox at the native real path

The temporary-directory guard now compares against the long real path,
so the erasure test, still rooted at the 8.3 short one on Windows, was
refused before it ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): keep a birth equal to the change time as a birth

On Linux the file clock is coarse: a git dir made and filled within one
tick has its birth time equal to its change time, to the nanosecond.
Taking that as no birth made a copy answer only from ingest's first
sighting, so the row "copy at the path" stored a call it should not
have (81 runs in 200 on ext4, 0 in 400 with this rule).

Any positive birth time is now kept, by the CLI and the hook alike.
Where Linux refuses statx, the birth time is the change time and the
identity moves; the usage contract states what that leaves open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): off closes the interval its key names at its path

Where Linux refuses statx, the birth time is the change time, so a
clone's identity moves with each entry added to its git dir, the
key write of `on` included. `off` closed only the interval of the
identity it read, and a call made after it could be stored.

It now also closes the open interval its key names when that interval
was opened at the same path. A copy elsewhere carries the token, not
the path, and is left alone. With statx refused, 0 of 36 consent rows
store more, 3 runs; with statx, 36 of 36 pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* ci(telemetry): probe the session id join against the real tool

The identifier-join job lost its probe with the previous telemetry.
This one runs claude -p against a dead API and judges, through the
shipped hook predicates, that the hook's payload, its environment and
every transcript line name one session, that CLAUDE_PID is the hook's
parent, and that a headless run never reads as a person present.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): supersede the old plans, record the latest proofs

The pending telemetry plans of August are superseded by this one.
The mutation proofs record the off fix and the probe, and the oracle
note names the one guard added to the transcript reader since.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* ci(telemetry): judge CLAUDE_PID as the hook uses it, isolate the probe

The hook takes CLAUDE_PID and falls back to its parent pid only when
it is unset, so the probe now fails only when CLAUDE_PID is gone, not
when a shell stands between Claude and the hook. Its git init no longer
inherits GIT_* from a git hook, its temp dirs are removed, and the job
runs when the probe or the hook predicates it uses change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the hook's parent is not Claude on linux

The identifier-join probe's first CI run showed a shell between Claude
and its hooks on Ubuntu: CLAUDE_PID 2565, hook parent 2588. The parent
pid only stands in when CLAUDE_PID is unset; the comment now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(cli): a flat cursor agent keeps a dot inside its name

Removing the previous telemetry's fields from the cursor profile took
killed mutants with them, and the tools-cursor score fell to 96.8,
under its 97 floor. The surviving mutant on the agent name's .md is
real: an agent named read.md-first.md lost its inner .md. Five of the
others are equivalent. Score now 97.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): ask only where a person sees the hook's message

Observed with the maintainer: the terminal and the desktop app set
ATTENDED=1 and show a block's reason; the VS Code extension sets no
ATTENDED and shows neither a reason nor a systemMessage, so a block
there would eat the prompt silently. The presence rule now names the
two surfaces seen to show the reason, cli and claude-desktop, instead
of any entrypoint not starting with sdk, which would block a surface
never seen. Each condition has a mutation that turns its row red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record the vs code deviation and the presence rule

The spec asked every surface where a person can be present, the IDE
extension included. The extension shows no hook message, so it is not
asked: recorded as a deviation in the spec and the plan, and the README
tells a VS Code user to declare from a terminal, which binds the
working branch. Three documents still stated the sdk rule. The presence
matrix now judges every row before failing, so each mutation names all
the rows it turns red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): say where it asks in one table

IDE extensions in general, not VS Code alone: the rule asks only the
terminal and the desktop app, and VS Code is the extension observed.
The README section becomes a table and two bullets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* refactor(telemetry): ingest reads every tool through one reader each

Ingest held Claude's reader directly. A reader is now a tool's
transcripts and its line format, and ingest reads a list of them: a
second tool is its format, its source and one wiring line. Each reader
hands back its own files' positions, laid over the ones held, so no
tool undoes another's and a file no longer listed keeps its place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): status shows the clone's state, task only declares

`aidd telemetry status` says whether this clone is measured, since
when, and the task its work is bound to; not measured is an answer,
exit 0. `task` with no name used to show that and now refuses,
pointing at status, so each command does one thing. Bare
`aidd telemetry` already prints the group's help.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): a typed declaration needs a name or --none

With `task` declaring only, a bare `aidd telemetry task` typed as a
prompt would have run the CLI to be refused. The hook now treats it as
a declaration it does not understand and shows the usage, without
running anything. The end-to-end checks of what work is bound to now
go through `status`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): name status where commands are listed, say how to add a tool

The privacy contract and the CLI memory listed the commands without
status. The memory now says what adding a tool takes: its format, its
source, one reader in the wiring and its name in USAGE_TOOLS. The
two-reader ingest test gives the second reader another tool's name,
and the not-measured lines all start the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(cli): smoke the status command

The smoke script lists every command the binary has and fails on a
drift; status was missing, and is now run after a declaration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): ask and confirm in one line, one way

Tried in a real session: the ask was five lines, and the confirmation
of a declaration showed the CLI's whole output inside Claude's
"blocked by hook" frame, which read as still blocked. The ask is now
one line naming the one way that always works, the declaration typed as
the next prompt: a terminal's `!` runs whatever `aidd` its PATH holds,
not the one the hook found. A declaration is confirmed in one line that
says to send the prompt again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* perf(telemetry): do not read transcripts no consent can cover

A first ingest after `on` read the whole Claude history and asked git
the repository of every old call, though none could be stored: over 22
minutes on a 6.2 GB history, never finished. A transcript last written
more than a day before the first consent interval opened is now not
read, and its position is set to its last complete line, so what it
gains later is read. The day absorbs a file clock that lags the call
clock. Same history: 246 s for the first ingest, 1.1 s for the next.

What is stored does not change: a test ingests the same transcripts
with and without the skip and stores the same records, and on a real
session the stored calls match an independent count (5 calls, 217,337
tokens). Pre-consent calls are no longer counted under a reason, and
forget may leave a hand-set key in a clone that never ran `on`; the
contract says both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record the proofs of the unread transcripts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): nothing is read while no clone opted in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): pin what decides which transcripts are read

The earliest interval of any clone decides, a damaged consent log
reads everything so each call keeps its reason, and a read that was
not skipped says so. The first interval is now Math.min of every
start, infinity when there is none, with no separate empty case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(cli): pin the telemetry help to its golden, and its failures

The presentation mutation score fell to 94.0 once next's recipe
validation joined this branch's telemetry commands, whose descriptions
no test of that scope read. Each subcommand's help is now compared with
the help golden, and four commands are shown to report a failure and
exit 1. Score 95.1 against a floor of 95.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): say why nothing is read, and pin reading after a late on

With no clone opted in, ingest reads no transcript, so every count is
zero and the usage skill could not say why: it now names that case. A
test chains no consent, `on`, then a grown transcript, and only the
new line is read. The contract states a network file clock lagging by
over a day and transcripts copied from another machine, and the hook's
refusal of unsafe characters says ASCII.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): report by the agent that ran

Every stored call already says whether the main session, a sub-agent
or the advisor made it. `--axis agent` splits a period that way, and
adds up to the same totals as every other axis. The envelope stays at
version 1: an axis value is added, nothing renamed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(telemetry): a README to follow, in four steps and two tables

Start in four steps, every command in one table, when it asks in
another, then what it measures, privacy, skills and hooks in short
bullets. Nothing it said before is lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(telemetry): one owner per fact, diagrams and tables over prose

The README shows what happens at a high level, the contract keeps every
rule in tables and diagrams (4,600 to 3,000 words, each of 274 rules
checked against the original), and the code map points to both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(telemetry): the real claude, on three systems, billed by a fake API

The report must equal what the API billed, call for call and counter for
counter, on the declared task and ticket. The API is a local server whose
usage is the expected answer, so no key and no quota are spent, and the job
runs on Linux, Windows and macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): a person at a terminal is asked, answered and keeps the task

The real claude runs in a pseudo-terminal against the fake API: the first
prompt is blocked before any call, the declaration typed as a prompt is
answered by the hook, and /clear carries the declared session. Each step
waits on what it causes on disk or at the API, not on the screen. Linux and
macOS; Windows has no POSIX pty and keeps the headless check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): the person's session on Windows too, over ConPTY

The same relay through pywinpty, with an aidd.cmd the gate can find, so
Windows is asked, answered and keeps the task across /clear like the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): answer ConPTY's cursor query so Claude renders on Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): answer the terminal identity query on Windows as well

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): keep the person's session to POSIX, Windows stays headless

Under ConPTY on the runner Claude never got past its terminal setup, even
with the cursor and identity queries answered. Windows keeps the headless
check, which passes there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t adds (#1031)

* docs(telemetry): frame the ledger by day, with the window measured

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): plan the ledger by day in five phases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* refactor(telemetry): a record belongs to a utc day, an ingest to a window

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* perf(telemetry): the ledger is kept by day, and an ingest reads its window

One ingested call now loads, folds and writes only its calls' days and
the day on each side, plus any monthly partition those days fall in,
which it splits into days. Damage and duplicates in the window are
rewritten with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): a report leaves the ledger settled, by day

The report rewrites, folded, every partition that is damaged, monthly,
or holds a call another one holds, so the bytes on disk are those of
one ingest of the same calls whatever order the ingests ran in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the contract and the code map say the ledger is by day

Pins that forget removes daily and monthly partitions as the ledger,
never as an earlier day file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): a day holding one call twice is unsettled too

Kills the two mutants the telemetry run left in the ledger adapter: an
unanchored monthly name, and a duplicate inside one day going unseen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the ledger by day is implemented

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): a call's day never depends on the machine's time zone

dayOf now gives no day for a time without an explicit zone (Z or
+-hh:mm), or whose UTC day falls outside four-digit years. Such a call
is counted undated at ingest, and such a ledger line is counted and
dropped as not a record, instead of landing in a file DAILY never reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): the report says a damaged ledger line is dropped

The report settles the ledger, so a line that is not a record is gone
after it, as after an ingest. Both now say so in the same words.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* refactor(telemetry): a split month is found without a filter that changed nothing

A day's key never equals a month's, so filtering names to monthly files
before matching them against the touched months was redundant: the
equivalent mutant the review saw survive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the ledger's invariants say when they hold

Once on disk and order-independent bytes hold after a report settles the
ledger; an ingest reads its window plus a month it splits; save removes a
named day it holds no records for rather than writing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the contract records the re-measured before and after

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* test(telemetry): the settled-ledger property covers damage and re-ingest

Adds torn lines in a monthly file and in a stray day, calls already held
in a monthly file read again, 200 runs, an assertion that the bytes are
the canonical partitionByDay(foldUsage(calls)) with each call once, and
that a second settle finds nothing to do and changes nothing. Red when
settle ignores damaged partitions or save keeps an emptied day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* refactor(telemetry): a day's year bound has no check a test cannot break

The four-digit check on the computed day was a ten-character regex on a
ten-character slice, so its anchors were equivalent mutants; it is now
a sign check. Reading a stored time goes through dayOf alone. Tests pin
the leading anchor and a zoned time naming no real day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): a stored time that is not text is refused again

7ff1dee read a stored time through dayOf(String(at)), so a time held
in a one-element array coerced to a zoned string and was accepted with a
non-text at. The text check is restored, superseding that commit's
"goes through dayOf alone"; the new refusal case also kills its mutant,
so the check is no longer equivalent. The zone pattern's closing anchor
is kept and says why no test can observe it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): say when a time is usable, and keep the review rounds

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
)

* docs(telemetry): a report reads its period, the contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* perf(telemetry): a report reads its period, not the whole history

A report with --days, --from or --to now loads only the days of its period
and the day on each side, plus any monthly file one of them falls in, and
settles only what it read. A report with no period reads and settles the
whole ledger, as before.

The copies of one call are less than a day apart (0.3 h at most over 262,631
real keys), so a call with a copy in the period has every copy in the window
and folds to the same record: a fast-check property checks the bounded
envelope equals the full report filtered, byte for byte.

UsageLedger.load takes a test on the day instead of a set of days, so a
window costs the partitions on disk, not the length of the period.

One year at 3,400 calls a day: --days 7 goes from about 4.2 s to 95 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* refactor(telemetry): one day arithmetic, one failing storage, one autumn

Review round 1: shiftDay in the stored usage domain replaces three copies of
the day length and its arithmetic; the failing storage and the September to
November days are shared test helpers. The usage skill's envelope reference,
the usage contract and the task contract say what a bounded report reads,
and evidence.md records the mutation run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the window's bound is observed, not guaranteed

Review round 2: say outside the observed bound only an unbounded report folds
every copy, and that skipped_ledger_lines covers a month kept in one file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…1033)

* docs(telemetry): contract for filtering the report

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): plan for filtering the report

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): a filter keeps the calls of the row it names

A filter compares what axisValueOf gives a call with the value a flag
names, so it selects exactly the calls a row of that axis holds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): the report narrows to what its filters name

The use case refuses first, locates `.` before reading anything, then
resolves a session or repository typed by its start among the calls of
the period. The envelope names every filter, as resolved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): the report takes a filter per axis it can select on

Nine flags, one value each, trimmed and checked before anything is
read. The text names what was selected, and says when nothing matches.
The end-to-end check also runs one filtered report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): say what a report filter selects, and record the gates

The usage contract, the telemetry memory and the usage skill name the
nine filters and the envelope's `filters`. One display assertion covers
the ambiguity of a repository start, which mutation found uncovered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): state only what the gate runs showed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): the report refuses filters on one axis itself

The use case refuses two flags that select on one axis, before the
environment's refusal, and takes `.` as its own field, with no cast.
A repeated bare flag is named, not shown as `null`; the selection line
reads the selectors the filter matches with. The end-to-end judge takes
a root-commit id, the usage skill says when a filter matched nothing,
and the contract's rule 2 names a value with `--absent` on its axis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): one refusal for filters on one axis, and `.` alone

The command leaves conflicting filters to the use case, whose result
already exits 1. A request names a repository or `.`, never both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): record the approved review of the report filters

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* docs(telemetry): contract for choosing who is measured at opt-in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): order the opt-in question before the use case writes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): plan for choosing who is measured at opt-in

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): on writes the identity chosen, and only once it is on

The on use case takes who the clone is measured as: the identity already
set, none, or one chosen. It refuses an identifier the identity rules
refuse, and staying anonymous while an identity is set, before writing
anything, and writes the identity only after consent is granted. A
proposal is read from git's user.name, else user.email, never under
AIDD_TELEMETRY=0. The command still passes keep: nothing changes yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* feat(telemetry): on asks who is measured, and status names them

`aidd telemetry on` takes --as <id> or --anonymous. Interactively, with
no identity set, it asks once the opt-in is confirmed: the git name when
there is one, another identifier, or anonymous. --yes with no identity
and neither flag is an error before anything is written, so scripts and
tests now pass --anonymous. `on` and `status` say who the measurement
names, and the text report labels nobody `anonymous`; the envelope is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): say what on asks, and how to answer it without a terminal

The usage contract, the contributor map and the plugin README describe
who is measured at `on`. The 00-init start action reads the identity,
asks how the person appears when none is set, offering the git name, and
runs `on --yes --as <id>` or `--anonymous`. Gate evidence recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): ask again for an identifier on cannot keep

A typed identifier the identity rules refuse is asked again instead of
ending `on` after the opt-in was confirmed. The refusal and the prompt
say "one non-empty line of at most 128 characters", and the command
prints the refusal through the display instead of a copy of its text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): the start action reads no git name, and probes on --help

The 00-init skill asks the person to type an identifier or stay
anonymous, runs `on --yes --as '<id>'` quoted or `--anonymous`, and says
that `aidd telemetry on` in a terminal offers the git name: no name
reaches the session before the person chooses. Its probe now requires
`--anonymous` in `aidd telemetry on --help`. The start test table loses
the row its two new rows cover. Contract rules 5 and 6 say what ships.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* fix(telemetry): say why a typed identifier is asked for again

A refused typed identifier now prints the identity rule before the
prompt repeats. The start action says how to keep a quote inside the
single-quoted --as value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

* docs(telemetry): close the identity-at-opt-in task with its review

The review's two rounds, the gates rerun on the tree merged with the
report filters, and the contract and plan marked done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@aidd-bot
aidd-bot Bot requested a review from a team as a code owner October 16, 2026 08:26
@aidd-bot
aidd-bot Bot enabled auto-merge October 16, 2026 08:26
Comment on lines +78 to +86
run = spawnSync(env.ComSpec || "cmd.exe", ["/d", "/s", "/c", cmdLine(resolved.shim, argv)], {
cwd,
env: cleanEnv(env),
encoding: "utf8",
windowsHide: true,
windowsVerbatimArguments: true,
timeout,
stdio: ["ignore", "pipe", "pipe"],
});
@blafourcade
blafourcade merged commit 499b738 into main Oct 16, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants