@erclx/aitk 3.0.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "3.0.0",
4
+ "version": "3.2.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -35,7 +35,7 @@ Read these on both paths, skipping any that do not exist:
35
35
  - `${CLAUDE_SKILL_DIR}/../../standards/markdown.md`: word, punctuation, and formatting constraints
36
36
  - The `write-human` skill: tone and sentence construction constraints
37
37
 
38
- On the source path, also read the UI surfaces matched in Step 1 plus `docs/agents/output-shape.md` and `docs/index.md` for output shape or framing rules already documented.
38
+ On the source path, also read the UI surfaces matched in Step 1 plus any `docs/agents/output-shape.md` and `docs/index.md` the project itself carries, for output shape or framing rules already documented. Those two are the toolkit's own spelling, so a project keeping its framing rules elsewhere is read there instead.
39
39
 
40
40
  On the greenfield path, also read `.claude/ARCHITECTURE.md` for platform, tech stack, and surface type. Do not scan `src/`, stylesheets, or UI modules. Step 1 already established they hold nothing.
41
41
 
@@ -43,6 +43,6 @@ The batching fails a fifth way. A folder holding thirty unread items put as thir
43
43
 
44
44
  - Filing a dump and writing the items, which is `claude-intake` and owns every other write into the folder
45
45
  - Promoting an answered item onto the board, which is `claude-tasks` and runs after the answers land
46
- - The item format, the answer contract, and retrieval, which `standards/intake.md` owns and this skill cites
46
+ - The item format, the answer contract, and retrieval, which the toolkit's `standards/intake.md` owns and this skill cites
47
47
  - The comparable answer slots in groundwork and feature plans, which carry their own contracts and are a separate measurement
48
48
  - Deciding when to fire. The skill is user-invoked through `disable-model-invocation`, so answering is the operator's call rather than a description match.
@@ -15,7 +15,7 @@ A body written around a named tool fails a third way. A session on a surface car
15
15
 
16
16
  The opposite failure is the one that ships silently. A session that takes a preference decision itself produces work the operator did not ask for and never learns a choice was made, because nothing in the output says a fork was passed.
17
17
 
18
- `CLAUDE.md` states both branches of the rule, sending an ordinary judgment call to a pick with the tradeoff in one sentence and a preference-deciding call to the operator. The first branch has `snippets/decision-help.md` behind it and the second had no surface at all.
18
+ `CLAUDE.md` states both branches of the rule, sending an ordinary judgment call to a pick with the tradeoff in one sentence and a preference-deciding call to the operator. The first branch has the toolkit's `snippets/decision-help.md` behind it, which installs into a project as `.claude/snippets/decision-help.md`, and the second had no surface at all.
19
19
 
20
20
  ## Must
21
21
 
@@ -40,6 +40,6 @@ The opposite failure is the one that ships silently. A session that takes a pref
40
40
  ## Out of scope
41
41
 
42
42
  - Making the ordinary judgment call, which is a pick plus a one-sentence tradeoff and needs no surface
43
- - The chat-side pick with no repository behind it, which `snippets/decision-help.md` covers and reaches a different reader
43
+ - The chat-side pick with no repository behind it, which the toolkit's `snippets/decision-help.md` covers and reaches a different reader
44
44
  - Writing the decision into a plan, task, or architecture record, which each owning standard governs and this skill only routes to
45
45
  - Deciding when to fire. The skill is user-invoked through `disable-model-invocation`, so escalating is the operator's call rather than a description match.
@@ -84,9 +84,23 @@ A detached HEAD gives `git branch --show-current` an empty result, which would r
84
84
 
85
85
  ### Labels
86
86
 
87
- Read `.claude/aitk/pr-labels.toml` from the project root. A project that has not declared a map gets no labels and no warning, since a label set this skill supplied would be a guess about that project's surfaces.
87
+ Ask the CLI first:
88
88
 
89
- When the file resolves, match it against the name-only diff per `${CLAUDE_SKILL_DIR}/references/labels.md` and write the comma-separated result into `pr_labels` below. Leave `pr_labels` empty when no map resolves or no prefix matches, which skips the labelling command rather than running it against nothing.
89
+ ```bash
90
+ aitk labels audit --base <base> --json
91
+ ```
92
+
93
+ The record carries `labels`, the set this branch earns, and `uncovered`, the changed paths no row of the map reaches. Join `labels` with commas into `pr_labels` below. Report each `uncovered` path beside the result line, naming the map so the reader knows where a row would go, since a surface nobody covered merges bare and nothing else says so.
94
+
95
+ Branch on the record rather than on the exit. An operator's shell profile may wrap `aitk` in a function whose status comes from a trailing command, and the binary exits 1 for an unknown subcommand and 1 for an ordinary refusal alike.
96
+
97
+ A `reason` of `no-map` is the answer that the project declared no map, which earns no labels and no warning: a label set this skill supplied would be a guess about that project's surfaces. Stop there and label nothing.
98
+
99
+ Every other `reason` is a map or a range the verb could not read, which is `unreadable-map`, `no-domains`, `no-base`, and `unreadable-changes`, plus `bad-base` for a ref this skill resolved wrongly. Take the fallback below and warn beside the result line, naming the reason. A map with a typo in it still has rows a prefix match can reach, and reading the refusal as an absence would open the pull request with no labels and nothing said, which is the surface merging bare that the verb exists to name.
100
+
101
+ The fallback is reading `.claude/aitk/pr-labels.toml` and matching it against the name-only diff per `${CLAUDE_SKILL_DIR}/references/labels.md`. It also covers no record coming back at all, which is an installed `aitk` predating the verb, since a skill reaches a target the moment it merges while the CLI reaches one only when a release publishes. The fallback labels correctly and reports no uncovered path, which is the half only the verb carries.
102
+
103
+ Leave `pr_labels` empty when no map resolves or no prefix matches, which skips the labelling command rather than running it against nothing.
90
104
 
91
105
  ### Final command
92
106
 
@@ -29,11 +29,35 @@ A label takes more than one prefix when two folders read as one surface. Two lab
29
29
 
30
30
  Matching is prefix-anchored, so a row written for an authoring root reaches nothing under the copy a project consumes. A surface living only under a dotted folder carries its own prefix on the row that owns its subject, and a folder holding several subjects rather than one splits across the rows that own them.
31
31
 
32
- The map is authored by hand and nothing detects a directory it fails to cover, so a surface added after the map was written labels nothing until someone adds a row. A map that has been censused against its own history says in its comment where that check is owned, so a reader meeting the gap is sent somewhere rather than left with the prediction.
32
+ The map is authored by hand, so a surface added after it was written labels nothing until someone adds a row. `aitk labels audit` is what names that surface before the branch merges.
33
33
 
34
34
  ## Paths a map declines to label
35
35
 
36
- A path that moves only when a release or a generator rewrites it earns no row. Release automation applies its own label, and a domain label on a generated file gives a mechanical edit a subject it does not have. Neither is covered, so both are recorded in the map's comment with the reason, which is what separates a path nobody has gotten to from one somebody decided against.
36
+ A path that moves only when a release or a generator rewrites it earns no row. Release automation applies its own label, and a domain label on a generated file gives a mechanical edit a subject it does not have.
37
+
38
+ Both go under a `[declined]` table keyed by the reason, in the same file and matched by the same prefix rule:
39
+
40
+ ```toml
41
+ [declined]
42
+ release-managed = ["CHANGELOG.md", "package.json"]
43
+ generated = ["build/manifest.json"]
44
+ ```
45
+
46
+ A table rather than a comment because the check reads it. A path here is a decision already taken and a path under neither table is a gap nobody has gotten to, and a report that could not tell those apart would be useful about neither.
47
+
48
+ A path claimed by a `[domains]` row and a `[declined]` row takes the label. It already has a subject, so reporting it as deliberately unlabelled would contradict the label the same run applies.
49
+
50
+ ## Reporting a surface no row reaches
51
+
52
+ ```bash
53
+ aitk labels audit --base <base> --json
54
+ ```
55
+
56
+ The record carries `labels`, `declined`, and `uncovered`. It exits 2 when `uncovered` has anything in it and 1 when it refuses, with `reason` naming the cause: `no-map` for a project that declared none, and a parse or range failure otherwise.
57
+
58
+ It reports and never gates. Whether an uncovered surface deserves a label is a judgment only the person who owns that surface can make, and a push failing on one teaches a contributor to route around the check while nothing about the surface has changed.
59
+
60
+ What it leaves unmeasured is a prefix reaching no path, so a row left behind by a deleted folder stays in the map. That is the map going stale from the other side and a second measure rather than this one.
37
61
 
38
62
  ## Matching
39
63
 
@@ -111,7 +111,7 @@ grep -l '^## Indexes' CLAUDE.md 2>/dev/null
111
111
 
112
112
  If the section is present, skip the seed silently.
113
113
 
114
- If `CLAUDE.md` exists but has no `## Indexes` section, offer to install the canonical convention block. The text below is the source of truth and is mirrored in `tooling/claude/seeds/CLAUDE.md`. Paste it verbatim. Do not rewrite, paraphrase, condense, or add punctuation.
114
+ If `CLAUDE.md` exists but has no `## Indexes` section, offer to install the canonical convention block. The text below is the source of truth and is mirrored in the toolkit's `tooling/claude/seeds/CLAUDE.md`. Paste it verbatim. Do not rewrite, paraphrase, condense, or add punctuation.
115
115
 
116
116
  ```markdown
117
117
  - When a folder has an `index.md`, check it before reading individual files in that folder.
@@ -147,5 +147,7 @@ Replace bracketed values with the values from this run. Drop the "Folders bootst
147
147
 
148
148
  ## Reference
149
149
 
150
- - `.claude/context/indexes.md`: system rationale, frontmatter contract, when to adopt
151
- - `docs/agents/indexes.md`: `aitk indexes regen` flags, exit codes, JSON shape
150
+ Both pages sit in the toolkit and install nowhere, so a target reads them there rather than in its own tree.
151
+
152
+ - The toolkit's `.claude/context/indexes.md`: system rationale, frontmatter contract, when to adopt
153
+ - The toolkit's `docs/agents/indexes.md`: `aitk indexes regen` flags, exit codes, JSON shape
@@ -5,7 +5,7 @@ description: Fetches a YouTube video transcript with metadata frontmatter into t
5
5
 
6
6
  # YouTube transcripts
7
7
 
8
- Turn a pasted YouTube URL into a markdown file with YAML frontmatter and a cleaned prose body. The `aitk transcripts` command owns the fetch, VTT cleanup, and frontmatter. Do not reimplement that logic. See `.claude/context/transcripts.md` for the output format and field list.
8
+ Turn a pasted YouTube URL into a markdown file with YAML frontmatter and a cleaned prose body. The `aitk transcripts` command owns the fetch, VTT cleanup, and frontmatter. Do not reimplement that logic. The output format and field list live in the toolkit's `.claude/context/transcripts.md`, which a target does not receive.
9
9
 
10
10
  ## Guards
11
11
 
@@ -22,11 +22,11 @@ aitk audits list --json
22
22
 
23
23
  ## What it runs
24
24
 
25
- Fourteen verbs, listed by `aitk audits list`. Each runs once in its fullest form, and the aggregate reads that verb's own record rather than imposing a shared envelope on it. Every one of those records already has consumers naming its keys, so a common shape would be a breaking change bought for tidiness.
25
+ Sixteen verbs, listed by `aitk audits list`. Each runs once in its fullest form, and the aggregate reads that verb's own record rather than imposing a shared envelope on it. Every one of those records already has consumers naming its keys, so a common shape would be a breaking change bought for tidiness.
26
26
 
27
27
  The verbs walk separate trees and share no state, so they run together. Measured on the authoring machine at twelve verbs, a run finished in 0.8 seconds of wall clock against 4.4 seconds of processor, which is under every other stage in `bun run check`. `aitk deps audit` is the one that changes that reading, since it reaches a network rather than a tree and its latency is the index's rather than this machine's.
28
28
 
29
- Twelve of the fourteen read a tree on this disk. The two added by `state-scoped-risk.md` read committed state rather than an arriving change, which is the gap every review surface here leaves by construction.
29
+ Thirteen of the sixteen read a tree on this disk. The two added by `state-scoped-risk.md` read committed state rather than an arriving change, which is the gap every review surface here leaves by construction, and the one added by `label-coverage.md` reads a branch range against a map the project declares.
30
30
 
31
31
  Each is invoked as the CLI the caller is running rather than as a global `aitk`. A globally installed binary resolves to the main checkout no matter which worktree is executing, so the aggregate would measure a tree the branch never touched and report a pass over it.
32
32
 
@@ -46,9 +46,11 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
46
46
  | `aitk markdown audit` | Fail any markdown path on a banned character, word, or spelling, and report the structural checkpoints |
47
47
  | `aitk claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
48
48
  | `aitk claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD`, and the installed version against the newest published (`--json`) |
49
+ | `aitk claude skills reach` | Report the shipped bodies citing a toolkit path no target project receives, exiting 2 on an unqualified one |
49
50
  | `aitk gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`) |
50
51
  | `aitk secrets scan` | Report credential-shaped values in the tree the package ships, keyed on issued values rather than on words (`--json`) |
51
52
  | `aitk deps audit` | Report published advisories against the resolved dependency set, refusing rather than reporting clean when the index is unreachable (`--json`) |
53
+ | `aitk labels audit` | Report the labels a changed set earns from the pull request label map and the paths no row reaches (`--json`) |
52
54
  | `aitk audits run` | Run every audit as one set, report per check under one verdict, and compare each count to the recorded baseline (`--json`, `--record`) |
53
55
  | `aitk audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
54
56
  | `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
@@ -58,25 +60,26 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
58
60
 
59
61
  Each domain exposes a consistent shape where applicable: `list`, `install`, `sync`, `create`.
60
62
 
61
- | Domain | Subcommands |
62
- | ----------- | ------------------------------------------------------------------------------------------- |
63
- | `tooling` | `list`, `sync`, `ref`, `create`, `verify`, `inject`, `prune-gitignore` |
64
- | `snippets` | `list`, `install`, `sync`, `create` |
65
- | `standards` | `list`, `<name>` |
66
- | `gov` | `list`, `install`, `sync`, `build`, `regen`, `test-order` |
67
- | `claude` | `init`, `sync`, `seeds list`, `skills list`, `skills audit`, `skills drift`, `setup [dest]` |
68
- | `wiki` | `init` |
69
- | `design` | `render` |
70
- | `slides` | `render`, `list` |
71
- | `tasks` | `archive`, `validate` |
72
- | `intake` | `list`, `answer` |
73
- | `teach` | `list`, `open`, `resource`, `glossary` |
74
- | `comments` | `scan` |
75
- | `context` | `audit` |
76
- | `markdown` | `audit` |
77
- | `secrets` | `scan` |
78
- | `deps` | `audit` |
79
- | `audits` | `run`, `list` |
63
+ | Domain | Subcommands |
64
+ | ----------- | ----------------------------------------------------------------------------------------------------------- |
65
+ | `tooling` | `list`, `sync`, `ref`, `create`, `verify`, `inject`, `prune-gitignore` |
66
+ | `snippets` | `list`, `install`, `sync`, `create` |
67
+ | `standards` | `list`, `<name>` |
68
+ | `gov` | `list`, `install`, `sync`, `build`, `regen`, `test-order` |
69
+ | `claude` | `init`, `sync`, `seeds list`, `skills list`, `skills audit`, `skills drift`, `skills reach`, `setup [dest]` |
70
+ | `wiki` | `init` |
71
+ | `design` | `render` |
72
+ | `slides` | `render`, `list` |
73
+ | `tasks` | `archive`, `validate` |
74
+ | `intake` | `list`, `answer` |
75
+ | `teach` | `list`, `open`, `resource`, `glossary` |
76
+ | `comments` | `scan` |
77
+ | `context` | `audit` |
78
+ | `markdown` | `audit` |
79
+ | `secrets` | `scan` |
80
+ | `deps` | `audit` |
81
+ | `labels` | `audit` |
82
+ | `audits` | `run`, `list` |
80
83
 
81
84
  Common patterns:
82
85
 
@@ -18,6 +18,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
18
18
  - [Indexes](indexes.md): Flags, exit codes, and JSON shape for aitk indexes regen, plus when it auto-stages what it rewrote
19
19
  - [Install and sync](install-and-sync.md): What each install and sync verb writes, refuses, or leaves alone, and how drift is attributed in a target project
20
20
  - [Intake](intake.md): Reading intake folder counts and items, the three read states an item can be in, landing a batch of selections in one cluster, the refusal reasons, and why a call is scoped to one file
21
+ - [Label coverage](label-coverage.md): Reading a changed set against the pull request label map, the two tables it matches, how a gap is separated from a decision, and why an absent map is an answer
21
22
  - [Markdown audit](markdown-audit.md): Running the audit over any markdown path, where its bans and checkpoints are read from, what each check reports, and why the ban half gates while the structural half reports
22
23
  - [Output shape](output-shape.md): Two framed shapes every command renders into, how JSON and --names modes keep stdout clean, and the exit discipline that lets piped output drain
23
24
  - [Overview](overview.md): What this folder covers, the invocation rules every command inherits, and where domain behavior is documented instead
@@ -26,6 +27,7 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
26
27
  - [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
27
28
  - [Sessions](sessions.md): Resolving live peer sessions to the worktree and branch each holds, the liveness confidence field, the unresolved reasons, and what the read depends on
28
29
  - [Skill audit](skills-audit.md): Measuring both skill corpora against standards/skill.md, the checks it reads, the requirement gate that is the only failing one, and the drift verb that names bodies rewritten since a ref
30
+ - [Citation reach](skills-reach.md): Reporting the shipped skill bodies that cite a path no target project receives, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
29
31
  - [State-scoped risk](state-scoped-risk.md): Reading committed state rather than an arriving change, the shipped-tree corpus the secret scan reads, what it keys on and how a deliberate value is exempted, the advisory check and its network failure mode, and why one gates while the other reports
30
32
  - [Tasks](tasks.md): Selecting a shipped task by stem or pull request, recording a number and closing an outcome, the refusal reasons, the board and backlog checks validate runs, and why the board root defaults to the main worktree
31
33
  - [Teach](teach.md): Listing learning workspaces and the ordinal a new one takes, opening one with its required files, recording sources and glossary terms, resolving what the next lesson needs before it is written, the refusal reasons, and why every write here runs through a verb
@@ -138,11 +138,14 @@ edit and a job counting those stays red with no remedy.
138
138
 
139
139
  ### Surfaces reported beside the domains
140
140
 
141
- Four sections sit outside the per-domain scan, because each names something
142
- that walk cannot see. None of them produces a change, and no sync command
143
- applies any of them.
144
-
145
- All four report only against a toolkit-managed target, which is one carrying a
141
+ Six sections sit outside the per-domain scan, because each names something
142
+ that walk cannot see: `seeds`, `superseded`, `unmigrated`, `newSkills`,
143
+ `newRules`, and `reverse`. None of them produces a change, and no sync command
144
+ applies any of them. The headings below group the first three together and give
145
+ `newSkills` its description under `newRules`, which answers the same question
146
+ about a different corpus.
147
+
148
+ All six report only against a toolkit-managed target, which is one carrying a
146
149
  `.claude/` directory, a `CLAUDE.md`, or a domain still at the root layout. The
147
150
  report says so through `managed` in the JSON and routes an unmanaged directory to
148
151
  `aitk init`. Seeds are why the gate exists, since they enumerate from the toolkit
@@ -198,6 +201,66 @@ the toolkit ships, so a root folder holding the project's own documents beside
198
201
  the installed ones reports the installed subset, where a listing reports every
199
202
  file and proposes relocating the lot.
200
203
 
204
+ #### Rules the target never received
205
+
206
+ `newRules` names a rule the toolkit authored after this target last synced
207
+ governance. A sync refreshes the files a target already holds and adds none, so
208
+ without this section a project's rule set freezes at its install date while every
209
+ file it does hold reports as current. That is the report's most confident wrong
210
+ answer, since a clean result reads as a target holding everything the toolkit
211
+ publishes.
212
+
213
+ The list rides beside `newSkills`, which asks the same question about the plugin
214
+ catalog. Both are names rather than paths, and neither queues a change, because
215
+ the two remedies differ: a skill loads live from the plugin directory and needs
216
+ nothing run, while a rule reaches a target only when someone runs
217
+ `aitk gov install`.
218
+
219
+ Read a clean section as reporting rather than as delivering. Nothing here
220
+ installs, nothing counts toward `--exit-code`, and a target can read the list and
221
+ act on none of it. The value stops at an operator reading it, which is the same
222
+ contract the skills list already sets.
223
+
224
+ The measurement anchors on governance's own stamp rather than on the oldest
225
+ anchor across domains, since rules are domain-scoped and a shared anchor would
226
+ let a snippets sync move the revision rules are measured from. A target carrying
227
+ no governance anchor reports nothing at all: it has no date to measure against,
228
+ and diffing from the start of history would read the whole catalog as new.
229
+
230
+ An anchor this toolkit cannot resolve reports nothing by the same route, and that
231
+ one is not visible. A stamp naming a revision the running clone has never seen,
232
+ which is what a registry install without history or a shallow clone produces,
233
+ fails the read and yields an empty list rather than a stated absence. It looks
234
+ identical to a target holding every rule the toolkit publishes. `newSkills`
235
+ behaves the same way, and neither carries the `historyUnavailable` flag the
236
+ per-domain scan uses to separate the two. Treat an empty section on a toolkit
237
+ that is not a full clone as unmeasured rather than clean.
238
+
239
+ Entitlement is filtered, because a stack does not receive every rule. The base
240
+ stack takes the `core` and `claude` folders whole and every other stack extends
241
+ it with individually named rules, so an unfiltered list would tell a base
242
+ consumer about rules it can never receive and train the reader to skip the
243
+ section.
244
+
245
+ The filter accepts a band on either of two grounds. A folder the base stack takes
246
+ whole is entitled to every target, read from the stack file so a folder added to
247
+ base later needs no code change. Every other band is read off the folders the
248
+ target already carries, since `aitk gov install` records file hashes and never
249
+ the stack it resolved, which leaves the installed tree as the only evidence of
250
+ what a chain reached. One band can be reached by more than one stack, so the test
251
+ over-reports inside a folder the target holds, which costs a line where
252
+ under-reporting would cost the section its point.
253
+
254
+ A rule the target already holds is dropped by name. That is what keeps a rule the
255
+ toolkit moved between band folders out of the list, since a rename reaches this
256
+ read as an addition and only the name tells the two apart.
257
+
258
+ This closes the dangling-citation case as a side effect rather than checking for
259
+ one. The live instance is a rule citing a sibling authored after the target's
260
+ install, and the section names the sibling as new rather than naming the citation
261
+ as broken. That is enough for an operator to act on, and it is not a citation
262
+ check.
263
+
201
264
  #### The reverse walk
202
265
 
203
266
  `reverse` is the one section built by walking the target rather than the
@@ -249,6 +312,11 @@ documented and unreachable from any report.
249
312
  is: only the user can move content they wrote, so failing a job on it leaves the
250
313
  job red with no mechanical remedy.
251
314
 
315
+ `newRules` is excluded on a different ground, since a command does close it. What
316
+ excludes it is that installing a rule changes what a project is governed by, so
317
+ gating on the count would pressure a target into adopting rules nobody chose.
318
+ `newSkills` is excluded because it needs no command at all.
319
+
252
320
  The whole `reverse` section is excluded on the same grounds, and more strongly.
253
321
  Every entry in it is a judgment about a file the project may own, and one of its
254
322
  three verdicts is a labelled unknown by design. The unmigrated detection shipped
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Label coverage
3
+ description: Reading a changed set against the pull request label map, the two tables it matches, how a gap is separated from a decision, and why an absent map is an answer
4
+ ---
5
+
6
+ # Label coverage
7
+
8
+ `aitk labels audit` resolves the paths a branch changed against the label map a project declares, reports the labels the set earns, and names every path no row reaches. It closes a gap the map's own comment had predicted since the map shipped: nothing detected a surface added after the rows were written, so such a branch merged carrying no label and nobody heard about it.
9
+
10
+ ```bash
11
+ aitk labels audit
12
+ aitk labels audit --json
13
+ aitk labels audit --base origin/main
14
+ aitk labels audit src/cli.ts docs/index.md --json
15
+ ```
16
+
17
+ | Option | Behavior |
18
+ | --------------- | ----------------------------------------------------------------- |
19
+ | `--base <ref>` | Far side of the range, defaulting to the merge base against trunk |
20
+ | `--root <path>` | Repository to read, defaulting to the current directory |
21
+ | `--json` | Add a machine-readable record on stdout, keeping the frame |
22
+
23
+ Positional paths replace the range entirely. A caller holding a changed set already passes it rather than paying for a second git read, and the record then omits `base` to say the range was never resolved.
24
+
25
+ Under `--json` the record holds stdout alone and the frame still renders on stderr, refusals included, which is the split `output-shape.md` fixes for every mode.
26
+
27
+ ## The map it reads
28
+
29
+ The map sits at `.claude/aitk/pr-labels.toml` and carries two tables. `[domains]` keys a label name to the path prefixes that earn it, and `[declined]` keys a reason to the prefixes that earn no label on purpose.
30
+
31
+ ```toml
32
+ [domains]
33
+ api = ["services/api/"]
34
+ web = ["apps/web/", "packages/ui/"]
35
+
36
+ [declined]
37
+ release-managed = ["CHANGELOG.md", "package.json"]
38
+ generated = ["build/manifest.json"]
39
+ ```
40
+
41
+ Matching is prefix-anchored, so a row written for an authoring root reaches nothing under the copy a project consumes. A path claimed by rows in both tables takes the label, since it already has a subject and reporting it as deliberately unlabelled would contradict the label the same run applies.
42
+
43
+ ## A gap and a decision are different findings
44
+
45
+ The verb sorts every changed path into three buckets and keeps two of them apart:
46
+
47
+ - **Labelled.** At least one `[domains]` row reaches the path. It contributes its label and nothing else.
48
+ - **Declined.** A `[declined]` row reaches it. Somebody already decided the path earns no label, so the run names it and moves no exit code.
49
+ - **Uncovered.** Neither table reaches it. This is the finding, and it wants either a prefix on the row that owns its subject or a `[declined]` row carrying the reason.
50
+
51
+ Folding the last two together was the shape this replaced. A report that cannot tell a surface nobody has gotten to from one somebody refused is useful about neither, which is why the declined half moved out of the map's trailing comment and became data the command reads.
52
+
53
+ ## The range it reads
54
+
55
+ The default range is the branch against the trunk, resolved as the merge base against `origin/main` and then local `main`. The changed set is that base diffed against the working tree, plus untracked files git does not ignore, rather than against `HEAD`.
56
+
57
+ Reading the working tree is what lets the check run before the branch commits, which is the moment a session adds the surface nobody covered. The set is a superset of `base..HEAD`, so a caller running after the commits still sees the whole branch.
58
+
59
+ ## Exit codes and refusals
60
+
61
+ | Code | Meaning |
62
+ | ---- | ---------------------------------------------- |
63
+ | `0` | every changed path is labelled or declined |
64
+ | `1` | refused, with `reason` naming the cause |
65
+ | `2` | at least one changed path is reached by no row |
66
+
67
+ It reports and never gates. Whether an uncovered surface deserves a label is a judgment only whoever owns that surface can make, and a push failing on one teaches a contributor to route around the check while nothing about the surface has changed.
68
+
69
+ A project declaring no map refuses with `no-map`, which is an answer rather than a fault. Such a project is labelled silently by design, and treating the absence as a break would make the map mandatory for every target. `aitk audits run` reads that one reason as an expected absence and every other refusal as a measure that did not run.
70
+
71
+ ## What it does not measure
72
+
73
+ A prefix reaching no path is invisible here. A row left behind by a deleted folder stays in the map forever, and this verb has nothing to say about it. That is the map going stale from the other side and a second measure rather than this one.
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: Citation reach
3
+ description: Reporting the shipped skill bodies that cite a path no target project receives, the ownership key that decides what counts, the one-word qualifier that marks a citation as decided, and why the verb reports instead of gating
4
+ ---
5
+
6
+ # Citation reach
7
+
8
+ `aitk claude skills reach [path]` reports every shipped skill body citing a path that exists in the toolkit and reaches no target project. It reads and reports. Repairing what it finds is separate work.
9
+
10
+ ```bash
11
+ aitk claude skills reach
12
+ aitk claude skills reach --json
13
+ ```
14
+
15
+ | Option | Behavior |
16
+ | -------- | ---------------------------------------------------------- |
17
+ | `--json` | Add a machine-readable record on stdout, keeping the frame |
18
+
19
+ ## The defect it reads for
20
+
21
+ A plugin skill installs into a project and the toolkit's own tree is not there. A body naming `.claude/context/transcripts.md` resolves for a session running in this repository and sends every other reader to nothing, and no stage asked the question until this one. The shape is wider than one folder: a seed naming a standard with no route and a machine-readable field naming a toolkit-only path are the same claim, true here and false in a target.
22
+
23
+ ## What counts as a citation
24
+
25
+ A backticked token carrying a separator and an extension, which is how every body spells a path it means a reader to open. Three forms are skipped by construction.
26
+
27
+ - A placeholder such as `.claude/context/<domain>.md`, which names a shape rather than a file
28
+ - A path resolved through `${CLAUDE_SKILL_DIR}`, which is self-contained wherever the plugin loads
29
+ - A sibling named relatively, such as `references/labels.md`, which matches no authoring root and travels with the body
30
+
31
+ The same sibling named from the repository root as `claude/skills/<name>/references/<file>.md` is reported rather than skipped. The plugin loads from a cache rather than from the project tree, so that spelling resolves for nobody and the report is correct.
32
+
33
+ A path the toolkit does not hold is dropped rather than reported. The measure asks whether a claim true here is false in a target, and a path true in neither is a different defect that `aitk context audit` already reports against its own corpus.
34
+
35
+ ## The ownership key
36
+
37
+ A cited path counts when it sits under an authoring root no install channel delivers. Standards install nowhere and are read through the plugin corpus, snippets land under `.claude/snippets/`, governance rules under `.claude/rules/`, and the rest is this repository's own source, catalogs, and contract pages.
38
+
39
+ `src/`, `scripts/`, and bare `docs/` are deliberately outside the list. A body naming one of those is describing the reader's own tree, so listing them reports a correct citation on every run and buries the finding under the pass.
40
+
41
+ A path a seed installs is disowned twice, under its own name and under the folder spelling it takes once a project splits the entry. A domain that outgrows one file becomes `<domain>/`, which is still the entry the seed delivered, so reporting the split form would fail a project for growing.
42
+
43
+ ## The qualifier
44
+
45
+ A correct citation and a defective one are the same string, and the sentence around it is the difference. A citation counts as decided when its line names the toolkit as the owner, matching the bodies that already spell it that way. The repair for a finding is to say whose copy the path is, never to delete the citation, since the paths name real documents a reader wants.
46
+
47
+ ```markdown
48
+ Read `.claude/context/indexes.md` from the toolkit if context on the system is needed.
49
+ ```
50
+
51
+ A line mentioning the toolkit for an unrelated reason exempts a citation on it. That is the accepted cost of a word over a notation every future body would have to learn.
52
+
53
+ ## Exit codes
54
+
55
+ Exit codes are `0` when every citation names its owner, `1` for a refusal, and `2` when at least one is unqualified. The refusal is a tree carrying no `claude/skills/`, which ships no plugin body to measure, and it reports the reason rather than a clean count over nothing.
56
+
57
+ The verb reports rather than gates. A toolkit-scoped instruction is sometimes meant for a session in this repository, so failing a push on one would make the check something to route around. `aitk audits run` registers it with no gating exit for the same reason, and carries `unqualifiedCitations` as its retained count.
@@ -130,13 +130,25 @@ Each domain holds its own toolkit commit, so syncing governance today does not m
130
130
 
131
131
  A project that has never synced under a toolkit new enough to write a stamp falls back to the toolkit's own git history. Installed content matching any version that history published proves the file untouched, so it reports `stale` naming the commit it came from, and content matching no published version stays `drifted`. That fallback needs the toolkit as a git checkout. Installed from the registry it ships source without history, and the report says attribution was unavailable rather than reading every file as a local edit.
132
132
 
133
- Four further causes sit outside the per-domain scan, each naming something that walk cannot see. A seed the project edited is reported under `seeds` and reconciled with `aitk:claude-seed-sync`, since no sync command touches a seed. A file a newer seed folder replaced is reported under `superseded`, such as `.claude/TASKS.md` against the `.claude/tasks/` that now ships, and nothing moves it because the content is the project's own. A domain sitting at the root layout with nothing under `.claude/` is reported under `unmigrated` and handed to `aitk:migration-standards`.
133
+ Further causes sit outside the per-domain scan, each naming something that walk cannot see. A seed the project edited is reported under `seeds` and reconciled with `aitk:claude-seed-sync`, since no sync command touches a seed. A file a newer seed folder replaced is reported under `superseded`, such as `.claude/TASKS.md` against the `.claude/tasks/` that now ships, and nothing moves it because the content is the project's own. A domain sitting at the root layout with nothing under `.claude/` is reported under `unmigrated` and handed to `aitk:migration-standards`.
134
134
 
135
135
  That third one matters most on an older project. Before it existed, a target holding `snippets/` at its root reported zero entries for that domain, so a project that had never migrated was indistinguishable from one that was fully current. It covers snippets alone, since a root `standards/` folder in a target is the project's own authoring surface and nothing proposes moving it.
136
136
 
137
+ #### Rules you never received
138
+
139
+ A sync refreshes the files you already hold and adds none, so your rule set is frozen at the date you installed governance while every file in it reports as current. `newRules` names the rules the toolkit has authored since then, which is the one section that reads your absence rather than your contents.
140
+
141
+ Take a clean section as reporting rather than as delivering. Nothing here installs, and it counts toward nothing, so pick the rules up with `aitk gov install <stack>` or take one with `--add <rule>`. That separation is deliberate: adopting a rule changes what your project is governed by, which is a choice a sync has no business making for you.
142
+
143
+ The list is filtered to what your stack can receive, read off the rule folders you already carry plus the folders the base stack takes whole. A rule under `lang/` or `ui/` belongs to some stacks and not others, so an unfiltered list would name rules you can never install. A target that has never stamped governance reports nothing at all, since there is no date to measure against.
144
+
145
+ An empty section is not proof either way when the toolkit running the check is not a full git clone. The read needs history to reach your anchor, and an install from the registry ships none, so the section goes quiet rather than saying it could not measure. Run the check from a clone before reading a clean result as a complete one.
146
+
147
+ This also closes the case where a rule arrives citing a sibling you do not have. A sync refreshing a rule can land a version pointing at a file it never installs, and the section names the missing sibling rather than the broken citation, which is enough to act on.
148
+
137
149
  #### What the toolkit stopped shipping
138
150
 
139
- The fourth cause runs backwards. Every one above starts from what the toolkit ships and asks whether the target matches, so a folder the toolkit dropped appears in none of them. `reverse` walks the target instead and reports a folder sitting at a top-level path the toolkit once shipped and has since deleted.
151
+ The last cause runs backwards. Every one above starts from what the toolkit ships and asks whether the target matches, so a folder the toolkit dropped appears in none of them. `reverse` walks the target instead and reports a folder sitting at a top-level path the toolkit once shipped and has since deleted.
140
152
 
141
153
  Each entry carries a verdict, since a dropped folder and one the project wrote are the same bytes at the same path.
142
154
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.0.0",
4
+ "version": "3.2.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {