@erclx/aitk 0.108.0 → 0.110.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.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/claude-orchestrate/REQUIREMENT.md +1 -3
- package/claude/skills/claude-orchestrate/SKILL.md +10 -18
- package/claude/skills/claude-pr-review/REQUIREMENT.md +1 -1
- package/claude/skills/claude-pr-review/SKILL.md +6 -6
- package/claude/skills/migration-superseded/REQUIREMENT.md +1 -1
- package/claude/skills/migration-superseded/SKILL.md +1 -1
- package/docs/agents/audits.md +88 -0
- package/docs/agents/commands.md +3 -0
- package/docs/agents/index.md +1 -0
- package/docs/agents/scripting.md +13 -9
- package/docs/ai-workflow.md +2 -3
- package/docs/operating-model.md +18 -19
- package/package.json +1 -1
- package/scripts/core/verify.sh +74 -6
- package/src/audits/baseline.ts +194 -0
- package/src/audits/catalog.ts +464 -0
- package/src/audits/run.ts +202 -0
- package/src/cli.ts +4 -0
- package/src/commands/audits.ts +342 -0
- package/standards/requirements.md +2 -2
- package/standards/tasks.md +4 -3
- package/claude/skills/claude-roadmap/REQUIREMENT.md +0 -41
- package/claude/skills/claude-roadmap/SKILL.md +0 -61
- package/claude/skills/claude-roadmap/references/roadmap.md +0 -62
- package/standards/bundled/roadmap.md +0 -62
|
@@ -45,7 +45,6 @@ The session also records nothing of what it learns. Both other callers of memory
|
|
|
45
45
|
- List a candidate's file set against every track in flight, and serialize when the sets are not disjoint
|
|
46
46
|
- Place every finding a merged pull request produced against the surface that owns it
|
|
47
47
|
- Place what the return leg carries by what it changes, sending an answer that settles a finding onto the pull request and one that corrects this session's model of the world to the task owning that surface, since neither becomes a record by being read
|
|
48
|
-
- Date the roadmap line from that file's last commit, so an old sequence reads as old
|
|
49
48
|
- Keep one planned, non-conflicting task in reserve beyond what is running
|
|
50
49
|
- Refill the queue while a wave is still building, rather than on a merge and a thinning list alone, since neither of those fires across the window planning costs nothing
|
|
51
50
|
- Carry a constraint naming each in-flight track's file set in every plan written from here, stating per set which of the two acts it forbids, since a bare path list leaves a broken citation in place
|
|
@@ -86,8 +85,7 @@ The session also records nothing of what it learns. Both other callers of memory
|
|
|
86
85
|
## Guards
|
|
87
86
|
|
|
88
87
|
- Priority file absent: report the queue and say the order is unrecorded rather than inferring it from the index
|
|
89
|
-
-
|
|
90
|
-
- Roadmap present but never committed: report the date as uncommitted, since a blank reads as a formatting slip
|
|
88
|
+
- Cross-version sequencing asked for: say no surface carries it, rather than asserting an active version the tree does not state
|
|
91
89
|
- This body dropped from a long session approaching a compaction: name the re-invocation and the runbook paths, since the routing lives in the body and a user-invoked skill routes nothing once it is gone
|
|
92
90
|
- Blocker only an operator can clear: record the row as untestable this pass and name the action owed, rather than re-measuring what no session can move
|
|
93
91
|
|
|
@@ -28,7 +28,6 @@ Read the board in parallel, resolving the paths at the main worktree root per Wo
|
|
|
28
28
|
- `.claude/tasks/backlog.md`: what is not being scheduled, when the file exists
|
|
29
29
|
- `.claude/tasks/index.md`: what is queued
|
|
30
30
|
- `.claude/plans/*.md`: features already planned and ready to hand off
|
|
31
|
-
- `.claude/ROADMAP.md`: sequencing rationale, when the file exists
|
|
32
31
|
- open PRs via `gh pr list --json number,title,headRefName,isDraft`
|
|
33
32
|
|
|
34
33
|
Then output the state of play so the human knows what to launch, review, and merge.
|
|
@@ -37,7 +36,7 @@ Then output the state of play so the human knows what to launch, review, and mer
|
|
|
37
36
|
|
|
38
37
|
Row position under `## Needs a plan` is what gets planned next, top first, per `.claude/standards/tasks.md`. `backlog.md` carries what nobody is scheduling and is explicitly unordered, so read it for what exists and never as a queue. Report a backlog count rather than its rows, since listing them puts the length back in front of the reader the cut took it away from.
|
|
39
38
|
|
|
40
|
-
|
|
39
|
+
No surface carries cross-version sequencing, so report none. A row's `Waiting on` cell states why that row sits where it does, and reasoning spanning several rows reaches this session only through whoever remembers it. Say nothing about an active version, since nothing in the tree states one and a version asserted from the board is the unsourced claim this omission exists against.
|
|
41
40
|
|
|
42
41
|
A compaction is a moment this skill cannot detect, so the human asks for each side of it and this skill reads the matching runbook when they do.
|
|
43
42
|
|
|
@@ -56,8 +55,6 @@ That routing lives in this body and this skill is user-invoked, so a session tha
|
|
|
56
55
|
```plaintext
|
|
57
56
|
Orchestrator ready.
|
|
58
57
|
|
|
59
|
-
Roadmap: vX.Y, <the Now row's outcome>, as of <date>.
|
|
60
|
-
|
|
61
58
|
Ready to build (hand each to its own worker):
|
|
62
59
|
|
|
63
60
|
<feature>
|
|
@@ -80,9 +77,7 @@ Next: <the single most useful action>
|
|
|
80
77
|
|
|
81
78
|
Omit any section with nothing in it. Recommend a handoff only for a plan whose file set is disjoint from every track already in flight, per Parallelism below.
|
|
82
79
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
That command returns nothing for a roadmap that exists but has never been committed, which is the state `claude-roadmap` leaves behind when it writes the file and declines to stage it. Write `uncommitted` as the date in that case. A blank there would read as a formatting slip rather than as the newest possible sequence.
|
|
80
|
+
The block opens on the board rather than on a version, because no committed file states one. Adding a version line here would restate what a reader can already see on the rows, dated by nothing, which is how the retired sequencing surface produced an unsourced claim on every run.
|
|
86
81
|
|
|
87
82
|
### Every later turn
|
|
88
83
|
|
|
@@ -103,18 +98,15 @@ Write no shape for a correction. A correction is a sentence, and a format for ad
|
|
|
103
98
|
|
|
104
99
|
## The loop
|
|
105
100
|
|
|
106
|
-
1.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
4. Verify the plan against the tree. Reading it is not enough, since a plan goes stale from whatever merged after it was written. Grep for each construct it names and count the sites against the count it claims. Check that every phase label it cites is still open. Open each file it describes rather than trusting its account of the contents. Correct the plan before handing it over.
|
|
112
|
-
5. Hand off. The human opens a worker worktree with `claude-worktree` and runs `claude-autoship` against the plan. The orchestrator does not spawn workers.
|
|
113
|
-
6. Review the PR. When a worker opens a PR, run `claude-pr-review` to post findings to it. This is the deep, independent pass. The worker's autoship self-review was only the green gate.
|
|
101
|
+
1. Plan the next feature. Run `claude-feature` here, with the cross-feature context, to write a plan to `.claude/plans/`. Planning stays in this warm session so the plan front-loads reasoning a cold worker would otherwise re-derive. Every plan written from here also carries a constraint per track in flight, which the paragraph below this list states.
|
|
102
|
+
2. Decide parallelism and merge order. Note which plans touch a shared wiring seam so their PRs merge in sequence, not at once.
|
|
103
|
+
3. Verify the plan against the tree. Reading it is not enough, since a plan goes stale from whatever merged after it was written. Grep for each construct it names and count the sites against the count it claims. Check that every phase label it cites is still open. Open each file it describes rather than trusting its account of the contents. Correct the plan before handing it over.
|
|
104
|
+
4. Hand off. The human opens a worker worktree with `claude-worktree` and runs `claude-autoship` against the plan. The orchestrator does not spawn workers.
|
|
105
|
+
5. Review the PR. When a worker opens a PR, run `claude-pr-review` to post findings to it. This is the deep, independent pass. The worker's autoship self-review was only the green gate.
|
|
114
106
|
- Learning that a PR moved is the mechanical half, so read `${CLAUDE_SKILL_DIR}/references/orchestrator-poll.md` and start the poll it carries on the first dispatch rather than checking the board by hand. That runbook holds the routing, and a summary of it here is a second source that drifts from it.
|
|
115
|
-
|
|
107
|
+
6. Dispatch the handback. A pass posting a finding at any severity tells the session holding that branch to run `claude-address-review`, rather than waiting for a person to relay it. Re-review when the answer lands, then the human merges. Tell the trailing worker to rebase when its branch shares a seam with the merged one.
|
|
116
108
|
- Read the threshold off `claude-pr-review`, which states it once and governs the heading with it, so an open heading and an owed dispatch answer the same question and either one is enough to send
|
|
117
|
-
- Resolve the target at the moment of sending with `aitk sessions list --branch`, never from a mapping written down earlier, since names rotate as sessions end and one recorded earlier in a session has failed inside the hour. The runbook read at step
|
|
109
|
+
- Resolve the target at the moment of sending with `aitk sessions list --branch`, never from a mapping written down earlier, since names rotate as sessions end and one recorded earlier in a session has failed inside the hour. The runbook read at step 5 routes on the count and the confidence it answers with
|
|
118
110
|
- Open the message with the worktree and branch the sender believes the reader holds, asking to be corrected, whenever that mapping is inferred rather than confirmed
|
|
119
111
|
- Name the skill for the reader to run rather than writing an invocation, which arrives as text
|
|
120
112
|
- Read the pull request's own draft flag rather than the state a worker reports, since two reported a draft that read ready inside ninety seconds
|
|
@@ -134,7 +126,7 @@ Stamp the block with the commit this session read the tree at, which the same se
|
|
|
134
126
|
- Run one orchestrator at a time. The board is gitignored, so a second session sees none of this one's writes: two task files land minutes apart under different labels for the same work, one session archives a task mid-sweep in the other, and each archives a plan the other had retargeted. An Owner column does not fix this, since neither session can read the other's rows.
|
|
135
127
|
- Do not implement features in this session. Hand the plan to a worker.
|
|
136
128
|
- Do not merge. Recommend merge or changes. The human merges.
|
|
137
|
-
- Do not spawn worker sessions with agents. The human launches each worktree so every build is an independent, steerable stream with its own PR. The handback dispatch in step
|
|
129
|
+
- Do not spawn worker sessions with agents. The human launches each worktree so every build is an independent, steerable stream with its own PR. The handback dispatch in step 6 reaches a session the human already launched, so it leaves this boundary where it is.
|
|
138
130
|
- Do not edit tracked files from this session, at any size. The boundary offers no proportionality exception and nothing enforces it.
|
|
139
131
|
- Do not hand a worker anything but a plan, since scope lives there. A plan carries exact diffs only when they are already known, otherwise it states the scope and the open questions and lets the worker write the diff.
|
|
140
132
|
|
|
@@ -7,7 +7,7 @@ description: What the independent pull request review is for, the gaps it closes
|
|
|
7
7
|
|
|
8
8
|
## Gap
|
|
9
9
|
|
|
10
|
-
Without this skill, a pull request is reviewed only by the session that wrote it, which cannot see the
|
|
10
|
+
Without this skill, a pull request is reviewed only by the session that wrote it, which cannot see the board's order or a sibling branch in flight. Findings land in chat, where they are read once and leave the thread with no record. A review that opens and never closes is worse than none, since a reader scanning the thread cannot tell an unanswered review from a confirmed one, and the author's claim that findings are fixed is the only evidence they are.
|
|
11
11
|
|
|
12
12
|
A finding also stops being true by argument rather than by a fix. A worker naming the plan question that already declined it, or a constraint the pass could not see, settles it in the exchange that carried the answer, and the pass that accepts the argument drops the finding from its next body. A reader then sees a finding raised once and never mentioned again, which is indistinguishable from one everyone forgot.
|
|
13
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: claude-pr-review
|
|
3
|
-
description: Reviews an open pull request from an independent session and posts findings as a review comment on the PR. Posts a first pass against the whole change and every later pass against only the commits added since, under `## Review` while any finding is open and `## Review closed` once a pass carries none. Reads project docs and the
|
|
3
|
+
description: Reviews an open pull request from an independent session and posts findings as a review comment on the PR. Posts a first pass against the whole change and every later pass against only the commits added since, under `## Review` while any finding is open and `## Review closed` once a pass carries none. Reads project docs and the task board for cross-feature context a self-review lacks. Use when asked to "review the PR", "review this feature's PR", "post a PR review", "re-review the PR", "close out the review", "confirm the findings are fixed", or acting as the orchestrator reviewing a worker's PR. Do NOT use to review local uncommitted changes. That is `claude-review`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Claude PR review
|
|
@@ -32,7 +32,7 @@ Read these in parallel from the project root, skipping any that do not exist:
|
|
|
32
32
|
- `CLAUDE.md`: project type, conventions, and commands
|
|
33
33
|
- `.claude/REQUIREMENTS.md`: feature scope and non-goals
|
|
34
34
|
- `.claude/ARCHITECTURE.md`: technical design decisions
|
|
35
|
-
- `.claude/
|
|
35
|
+
- `.claude/tasks/priority.md`: where this feature sits on the board and what each neighboring row waits on. Resolve this one at the main worktree root per Worktrees in `CLAUDE.md`, since the board is gitignored and a linked worktree holds no copy of it
|
|
36
36
|
- `.claude/plans/feature-<slug>.md` for the branch, when present: the intent the PR should satisfy
|
|
37
37
|
|
|
38
38
|
Coding standards from `.claude/rules/` are auto-loaded by Claude Code.
|
|
@@ -79,7 +79,7 @@ Read each changed file in scope. Skip deleted files. Run reads in parallel.
|
|
|
79
79
|
|
|
80
80
|
Review the diff and files for the same axes as `claude-review` (bugs, edge cases, error handling, logic flaws, security, rule violations), then add the three lenses a self-review structurally cannot apply:
|
|
81
81
|
|
|
82
|
-
- Integration: does this fit the
|
|
82
|
+
- Integration: does this fit the board's order, the shared wiring seam, and any sibling PR in flight?
|
|
83
83
|
- Contract: does a contract downstream features depend on land correctly, and should the plan itself be questioned?
|
|
84
84
|
- Consumers: when the change touches a resource with more than one consumer, enumerate them and check the rule against each. A rule written for the consumer the change targets can be wrong for a sibling that writes.
|
|
85
85
|
|
|
@@ -116,7 +116,7 @@ The comment is a rendered-for-human GitHub surface, so load the `write-human` sk
|
|
|
116
116
|
```markdown
|
|
117
117
|
## Review
|
|
118
118
|
|
|
119
|
-
X critical, Y should-fix, Z minor. Reviewed against project docs and
|
|
119
|
+
X critical, Y should-fix, Z minor. Reviewed against project docs and the board.
|
|
120
120
|
|
|
121
121
|
**`path/to/file.ext`**
|
|
122
122
|
|
|
@@ -172,14 +172,14 @@ Before posting, run the scan in `.claude/standards/publish.md` against the body,
|
|
|
172
172
|
gh pr review <number> --comment --body-file .claude/.tmp/pr-review/body-<number>-<short-sha>.md
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
-
A pass carrying nothing at all takes `## Review closed` and a short body, with the footer line included either way. On a first pass, post `✅ No findings. Reviewed against project docs and
|
|
175
|
+
A pass carrying nothing at all takes `## Review closed` and a short body, with the footer line included either way. On a first pass, post `✅ No findings. Reviewed against project docs and the board.` On a later pass, post `✅ Prior findings addressed. Re-reviewed <short-sha>, N commits since the prior pass.`
|
|
176
176
|
|
|
177
177
|
A pass carrying only minors is an ordinary finding-carrying pass, so it takes the open heading and the full shape rather than either short line, since the minors have to be readable and neither line reports them. Keep whichever scope sentence the pass owes on the summary line:
|
|
178
178
|
|
|
179
179
|
```markdown
|
|
180
180
|
## Review
|
|
181
181
|
|
|
182
|
-
0 critical, 0 should-fix, Z minor. Reviewed against project docs and
|
|
182
|
+
0 critical, 0 should-fix, Z minor. Reviewed against project docs and the board.
|
|
183
183
|
|
|
184
184
|
**`path/to/file.ext`**
|
|
185
185
|
|
|
@@ -11,7 +11,7 @@ Without this skill, a target holding a retired `.claude/` file is told what repl
|
|
|
11
11
|
|
|
12
12
|
A second failure comes from the ordering nobody gets right unaided. A retired file committed before its ignore entry was written is tracked and ignored at once, and removing the entry first leaves the file tracked with nothing naming it. No report catches that state, so the mistake is discovered by whoever next wonders why a gitignored path keeps appearing in diffs.
|
|
13
13
|
|
|
14
|
-
Two more follow from where a session looks when the report is thin. An uppercase stem under `.claude/` is not the test for a superseded file, so a session substituting a folder listing reaches `ARCHITECTURE.md`, `REQUIREMENTS.md`,
|
|
14
|
+
Two more follow from where a session looks when the report is thin. An uppercase stem under `.claude/` is not the test for a superseded file, so a session substituting a folder listing reaches `ARCHITECTURE.md`, `REQUIREMENTS.md`, and `DESIGN.md`, each a single file the layout intends to stay one, and proposes shredding three documents. A session falling back to the plugin's own copy of the destination standard proposes a shape the project never adopted, against content only the user can place.
|
|
15
15
|
|
|
16
16
|
The last is the two-speed release skew arriving as a confident wrong answer. `superseded` reached a release in `0.46.0`, and an older CLI exits zero with a well-formed report that never mentions the field. A session reading that absent key as an empty array reports a clean layout to exactly the population holding the retired files.
|
|
17
17
|
|
|
@@ -27,7 +27,7 @@ Stop on any of three conditions, naming which one fired:
|
|
|
27
27
|
|
|
28
28
|
Test for the key rather than for emptiness. A current CLI reporting `"superseded": []` has looked and found nothing, which is the pass above, and reading an absent key as an empty answer reports a clean layout to the projects this skill exists for.
|
|
29
29
|
|
|
30
|
-
No listing substitutes for the field. The pairing runs against the seed folder names the toolkit ships, and an uppercase stem is not the test, so a listing of `.claude/*.md` also reaches `ARCHITECTURE.md`, `REQUIREMENTS.md`,
|
|
30
|
+
No listing substitutes for the field. The pairing runs against the seed folder names the toolkit ships, and an uppercase stem is not the test, so a listing of `.claude/*.md` also reaches `ARCHITECTURE.md`, `REQUIREMENTS.md`, and `DESIGN.md`. Each of those is a single file the layout intends to stay one. A fallback that proposed splitting them would shred three documents to avoid reporting a stale CLI, which is why this skill stops where its siblings degrade.
|
|
31
31
|
|
|
32
32
|
## Step 2: resolve the destination standard
|
|
33
33
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Audits
|
|
3
|
+
description: Running every health check as one set, what the single verdict means, the exit code each outcome takes, the retained baseline and the delta it reports, and which corpora are kept out of the record
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Audits
|
|
7
|
+
|
|
8
|
+
`aitk audits run` runs every audit this repository owns, reports each one under a single verdict, and compares each count to the floor the last recorded run left behind. Nothing here is a new measure. Every number it prints comes from a verb that already published it, which is what makes the command cheap and what separates it from writing another check.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
aitk audits run
|
|
12
|
+
aitk audits run --json
|
|
13
|
+
aitk audits run --record
|
|
14
|
+
aitk audits list --json
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
| Option | Behavior |
|
|
18
|
+
| --------------- | ----------------------------------------------------------------- |
|
|
19
|
+
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
20
|
+
| `--root <path>` | Measure this tree instead of the current worktree |
|
|
21
|
+
| `--record` | Write this run's tracked counts to `.claude/audits/baseline.json` |
|
|
22
|
+
|
|
23
|
+
## What it runs
|
|
24
|
+
|
|
25
|
+
Twelve 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
|
+
|
|
27
|
+
The verbs walk separate trees and share no state, so they run together. Measured on the authoring machine, twelve runs finish in 0.8 seconds of wall clock against 4.4 seconds of processor, which is under every other stage in `bun run check`.
|
|
28
|
+
|
|
29
|
+
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.
|
|
30
|
+
|
|
31
|
+
## What gates and what reports
|
|
32
|
+
|
|
33
|
+
Three findings fail the run: an unresolved context citation, a banned character, word, or spelling, and a skill folder carrying no `REQUIREMENT.md`. Those are the same three `scripts/core/verify.sh` already fails a push on, and the aggregate adds nothing to the set. Each is a fact with no false-positive class behind it.
|
|
34
|
+
|
|
35
|
+
Everything else reports. A heavy bullet, a long entry, a board row nothing resolves, a degradation term in a comment, and an implementation reaching history ahead of its test are judgments a reader settles. A push failing on one of those teaches contributors to route around the stage, which is the split recorded across every audit here and the one this command inherits rather than moves.
|
|
36
|
+
|
|
37
|
+
Growth against the baseline reports too. The standards behind the largest measures set no hard cap, so a count that rose is a fact about the corpus and a judgment about whether it matters.
|
|
38
|
+
|
|
39
|
+
## Exit codes
|
|
40
|
+
|
|
41
|
+
| Code | Meaning |
|
|
42
|
+
| ---- | -------------------------------------------------------------------- |
|
|
43
|
+
| `0` | Every audit reported and none carried a finding that is a fact |
|
|
44
|
+
| `1` | Refused, with the reason on stderr |
|
|
45
|
+
| `2` | An audit carries a finding that is a fact |
|
|
46
|
+
| `3` | An audit did not report, so the run measured less than the whole set |
|
|
47
|
+
|
|
48
|
+
`3` is a defect in the run rather than in the tree, which is why it takes a code of its own. An aggregate reporting a pass over a set it never finished measuring is the failure the command exists against, so an unparseable record, a verb that could not be started, and an exit no verb documents all land there instead of quietly counting as clean. The `markdown audit` empty-ban-set exit is the precedent: a corpus walked with nothing looked for is a broken check, not a clean tree.
|
|
49
|
+
|
|
50
|
+
The verdict is published as a field as well as an exit code. A reader branching on `summary.verdict` gets `clean`, `reported`, `findings`, or `incomplete` without inferring the difference between a quiet corpus and one nobody finished measuring.
|
|
51
|
+
|
|
52
|
+
### An absent corpus is not an unmeasured one
|
|
53
|
+
|
|
54
|
+
A per-machine folder that is not on this disk reports as `absent` and moves neither the verdict nor the exit code. Every one of those folders is gitignored, so a fresh clone and every CI run carry none of them, and counting six expected absences as failures pins the verdict at `incomplete` forever. A signal that never changes is one nobody reads after the second time they see it.
|
|
55
|
+
|
|
56
|
+
The allowance is narrow. It covers a per-machine corpus refusing because its folder is missing, and nothing else. A tracked tree that cannot be found is a broken checkout, and a per-machine verb refusing for any other reason is a broken verb. Both stay `unmeasured`.
|
|
57
|
+
|
|
58
|
+
Every run states how many corpora it measured against how many it skipped, including a run with no findings at all. A count of what passed reads as a verdict on the whole set unless the run also says what it never reached.
|
|
59
|
+
|
|
60
|
+
### What a shallow checkout changes
|
|
61
|
+
|
|
62
|
+
`aitk gov test-order` scopes its range against the trunk and falls back to the root commit when no trunk ref resolves. A depth-1 checkout has neither, so the range is empty and the verb reports zero of everything rather than refusing. The numbers are real for the history present, which means a shallow run under-reports against a baseline taken from a full clone and shows as shrinkage. That is the safe direction, and it is the reason a `test-order` delta is worth reading only from a clone carrying the history.
|
|
63
|
+
|
|
64
|
+
## The retained baseline
|
|
65
|
+
|
|
66
|
+
`.claude/audits/baseline.json` holds the counts from the last run recorded with `--record`, alongside the day it was taken and the commit it was read at. Each later run reports which counts moved and by how much.
|
|
67
|
+
|
|
68
|
+
The file is committed. A per-machine record cannot answer the question this half exists for, which is whether a number grew since anyone last looked, and a fresh checkout has to inherit that answer rather than start over. The cost is that a branch moving a count either fixes it or re-records it and says why, which is the shape the consumed-copy assertion already carries.
|
|
69
|
+
|
|
70
|
+
It sits under the project root rather than beside the aggregator in `src/`. The numbers describe one repository's corpus, and `src/` ships to every project that installs the CLI, so a baseline in the package would hand a target these counts to measure its own tree against.
|
|
71
|
+
|
|
72
|
+
### What is kept out of it
|
|
73
|
+
|
|
74
|
+
A gitignored record folder holds one machine's session scratch. Its counts describe somebody else's disk, so committing them writes a floor no other clone can reproduce and every contributor reads a regression against a number that was never about their tree. The board and five of the six record kinds are therefore reported on every run and recorded in none. `records-standards` is the exception, because that corpus is tracked.
|
|
75
|
+
|
|
76
|
+
An audit that did not report is left out rather than written as zero. Zero there records a clean corpus nobody measured, and the next run reads its real numbers as a regression against a floor that was never taken.
|
|
77
|
+
|
|
78
|
+
### The first run
|
|
79
|
+
|
|
80
|
+
A run with no baseline behind it says so rather than showing a delta of zero. Those two states say the same thing to a reader and mean opposite things, which is a defect this repository has already had to fix twice elsewhere. The same reasoning covers a check the baseline never recorded, a key the run produced that the record does not carry, and a key the record carries that this run did not produce. Each is named for what it is.
|
|
81
|
+
|
|
82
|
+
A hand-edited baseline that does not parse refuses the whole run. Reading a broken record as an absent one would reset the floor the file exists to hold, silently.
|
|
83
|
+
|
|
84
|
+
## In the verify pipeline
|
|
85
|
+
|
|
86
|
+
`bun run check` runs the set as a reporting stage after the three gating stages, and never fails on it. Those three stages keep their own specific remedies, so the aggregate reports the rest and the growth, and a fact still fails the push at the stage that names what to do about it.
|
|
87
|
+
|
|
88
|
+
The stage reads `summary`, a flat object of scalars published beside the nested arrays. Every key in it is unique across the whole record, so a shell stage greps one out without a JSON parser. The three verbs the gating stages already ran walk their trees a second time here, which is the 0.8 seconds measured above and the reason the whole set runs rather than only the part those stages skip: one verdict over every audit is the value, and a stage measuring a subset would report a health nobody took.
|
package/docs/agents/commands.md
CHANGED
|
@@ -46,6 +46,8 @@ Full help: `aitk <command> --help`. Behavior notes for the install and sync verb
|
|
|
46
46
|
| `aitk claude skills audit` | Report both skill corpora against the mechanical rules in `standards/skill.md` |
|
|
47
47
|
| `aitk claude skills drift` | Name the shipped skill bodies rewritten between a given ref and `HEAD` (`--json`) |
|
|
48
48
|
| `aitk gov test-order` | Report where an implementation reached history ahead of the test covering it (`--json`) |
|
|
49
|
+
| `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`) |
|
|
50
|
+
| `aitk audits list` | List every audit the set runs, with the corpus each reads and whether it gates (`--json`) |
|
|
49
51
|
| `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
|
|
50
52
|
|
|
51
53
|
## Domain commands
|
|
@@ -68,6 +70,7 @@ Each domain exposes a consistent shape where applicable: `list`, `install`, `syn
|
|
|
68
70
|
| `comments` | `scan` |
|
|
69
71
|
| `context` | `audit` |
|
|
70
72
|
| `markdown` | `audit` |
|
|
73
|
+
| `audits` | `run`, `list` |
|
|
71
74
|
|
|
72
75
|
Common patterns:
|
|
73
76
|
|
package/docs/agents/index.md
CHANGED
|
@@ -8,6 +8,7 @@ category: Agent surface
|
|
|
8
8
|
|
|
9
9
|
CLI catalog and invocation rules for agents, split by command domain. Start with overview.
|
|
10
10
|
|
|
11
|
+
- [Audits](audits.md): Running every health check as one set, what the single verdict means, the exit code each outcome takes, the retained baseline and the delta it reports, and which corpora are kept out of the record
|
|
11
12
|
- [Capture](capture.md): Rendering committed HTML sources to PNG, what the command asserts about fonts, and why it is toolkit-only
|
|
12
13
|
- [Command catalog](commands.md): Every project-level command and every domain subcommand, plus the shape each domain exposes
|
|
13
14
|
- [Comments](comments.md): Comment density by language and kind, the two structural exclusions, and how the degradation sweep finds its vocabulary
|
package/docs/agents/scripting.md
CHANGED
|
@@ -11,15 +11,16 @@ What a skill or script reads to discover names at runtime, and how each domain i
|
|
|
11
11
|
|
|
12
12
|
Use these to discover what's available instead of hardcoding names.
|
|
13
13
|
|
|
14
|
-
| Command | Returns
|
|
15
|
-
| -------------------------------- |
|
|
16
|
-
| `aitk tooling list --json` | Stacks, extends chain, dep and script counts
|
|
17
|
-
| `aitk snippets list --json` | Presets and categories with their slugs
|
|
18
|
-
| `aitk standards list --json` | Standards docs and the paths each governs
|
|
19
|
-
| `aitk gov list --json` | Governance stacks, rule sets, and unreferenced rules
|
|
20
|
-
| `aitk claude seeds list --json` | Seed doc sources with content
|
|
21
|
-
| `aitk claude skills list --json` | Plugin skills, descriptions, requirement flag
|
|
22
|
-
| `aitk docs list --json` | Consumer docs plus per-domain context
|
|
14
|
+
| Command | Returns |
|
|
15
|
+
| -------------------------------- | ---------------------------------------------------------------- |
|
|
16
|
+
| `aitk tooling list --json` | Stacks, extends chain, dep and script counts |
|
|
17
|
+
| `aitk snippets list --json` | Presets and categories with their slugs |
|
|
18
|
+
| `aitk standards list --json` | Standards docs and the paths each governs |
|
|
19
|
+
| `aitk gov list --json` | Governance stacks, rule sets, and unreferenced rules |
|
|
20
|
+
| `aitk claude seeds list --json` | Seed doc sources with content |
|
|
21
|
+
| `aitk claude skills list --json` | Plugin skills, descriptions, requirement flag |
|
|
22
|
+
| `aitk docs list --json` | Consumer docs plus per-domain context |
|
|
23
|
+
| `aitk audits list --json` | Audits the set runs, the corpus each reads, and whether it gates |
|
|
23
24
|
|
|
24
25
|
### Catalog fields
|
|
25
26
|
|
|
@@ -129,4 +130,7 @@ AITK_NON_INTERACTIVE=1 aitk wiki init /path/to/project
|
|
|
129
130
|
|
|
130
131
|
# Run a sandbox scenario non-interactively
|
|
131
132
|
SANDBOX_SCENARIO=sync aitk sandbox infra:tooling
|
|
133
|
+
|
|
134
|
+
# Read every audit as one record. Exit 2 is a fact, 3 an audit that did not report
|
|
135
|
+
aitk audits run --json
|
|
132
136
|
```
|
package/docs/ai-workflow.md
CHANGED
|
@@ -75,9 +75,9 @@ When features are independent, run them in parallel instead of sequentially. Use
|
|
|
75
75
|
- Ship each worktree separately with `aitk:git-ship`
|
|
76
76
|
- For full autonomy per worktree, invoke `aitk:claude-autoship` instead of the manual chain. Approve the plan, walk away, come back to draft PRs.
|
|
77
77
|
|
|
78
|
-
To run several worktrees as a coordinated flow rather than ad hoc, assert the orchestrator role in one warm session with `aitk:claude-orchestrate`. It
|
|
78
|
+
To run several worktrees as a coordinated flow rather than ad hoc, assert the orchestrator role in one warm session with `aitk:claude-orchestrate`. It plans each feature, refills the ready queue so a free worker never waits, and reviews each worker's PR with `aitk:claude-pr-review`, then tells the session holding that branch to run `aitk:claude-address-review` whenever the pass posted a finding at any severity, which is the same threshold `aitk:claude-pr-review` states and posts its open heading under. The human launches workers and merges. See [operating model](operating-model.md) for the full loop.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Execution order comes off `.claude/tasks/priority.md` and nothing sequences work into versions. Scope stays in `.claude/REQUIREMENTS.md` as a statement of what is wanted, and it reaches the board as discrete tasks the orchestrator orders by readiness.
|
|
81
81
|
|
|
82
82
|
Run one orchestrator at a time. The board is gitignored, so a second session reads none of the first one's writes and the two collide on labels and archives. No fixed number caps the worker tracks underneath it. Collision between file sets is what binds, so a candidate opens only when its files are disjoint from every track in flight, and the ceiling in practice is how many outputs one session can still review properly.
|
|
83
83
|
|
|
@@ -193,7 +193,6 @@ This section is the corpus the coverage claim is measured against: every name `a
|
|
|
193
193
|
| `aitk:claude-intake-answer` | When an intake folder holds unread slots waiting on your decision |
|
|
194
194
|
| `aitk:claude-groundwork` | When the state is unmeasured and more than one approach is live |
|
|
195
195
|
| `aitk:decision-escalate` | When open decisions turn on your preference and want batching into one set |
|
|
196
|
-
| `aitk:claude-roadmap` | When MVP scope needs sequencing into ordered versions |
|
|
197
196
|
| `aitk:claude-tasks` | When a decided item needs a file on the board, or a shipped one needs archiving |
|
|
198
197
|
| `aitk:claude-feature` | When the approach is settled and the next step is a plan |
|
|
199
198
|
|
package/docs/operating-model.md
CHANGED
|
@@ -17,10 +17,10 @@ order, port collisions), see [Claude Code and git worktrees](../wiki/claude/clau
|
|
|
17
17
|
|
|
18
18
|
The split is by vantage, not by capability. Both are Claude Code sessions.
|
|
19
19
|
|
|
20
|
-
| Role | Session | Owns
|
|
21
|
-
| ------------ | ------------------------------------- |
|
|
22
|
-
| Orchestrator | One warm, long-lived session |
|
|
23
|
-
| Worker | One cold worktree session per feature | Implement, self-check, open PR
|
|
20
|
+
| Role | Session | Owns | Does not |
|
|
21
|
+
| ------------ | ------------------------------------- | ------------------------------------- | ----------------------------- |
|
|
22
|
+
| Orchestrator | One warm, long-lived session | Planning, deep PR review, merge order | Edit tracked files, merge PRs |
|
|
23
|
+
| Worker | One cold worktree session per feature | Implement, self-check, open PR | Question the plan, merge |
|
|
24
24
|
|
|
25
25
|
The orchestrator is worth asserting explicitly at the start of a session with
|
|
26
26
|
`claude-orchestrate`, which loads the loop and its boundaries. It is framing and
|
|
@@ -36,13 +36,12 @@ and no later session recovers that vantage.
|
|
|
36
36
|
|
|
37
37
|
One feature travels this path end to end.
|
|
38
38
|
|
|
39
|
-
1. Orchestrator
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
42
|
-
4. Orchestrator
|
|
43
|
-
5. Orchestrator
|
|
44
|
-
6.
|
|
45
|
-
7. The human reads the result and merges. The orchestrator tells any trailing worker whose branch shares a seam with the merged one to run `claude-address-review`, which rebases whether or not the review left anything open.
|
|
39
|
+
1. Orchestrator plans the next feature with `claude-feature`, writing a plan to `.claude/plans/`. Planning stays in the warm session because good planning is cross-feature. It needs the contract other features consume and the shared wiring seam. A cold session would re-derive or guess.
|
|
40
|
+
2. The human opens a worker worktree with `claude-worktree` and runs `claude-autoship` against the plan. The worker builds, self-checks, opens a PR, and stops at the PR boundary.
|
|
41
|
+
3. Orchestrator reviews the PR with `claude-pr-review` and posts findings to it.
|
|
42
|
+
4. Orchestrator tells the session holding that branch to run `claude-address-review` once the pass posted a finding at any severity, resolving the target then with `aitk sessions list --branch` and reporting the invocation for the human when no live session holds it. The worker addresses the findings, rebases onto `origin/main` when a sibling landed first and left the branch unable to merge, then pushes a follow-up. A pass carrying only minor findings dispatches too, since the grade runs low often enough that a floor at should-fix loses fixes a worker would have made. `claude-pr-review` states that threshold and the heading follows it, so an open heading is itself the signal to send.
|
|
43
|
+
5. Orchestrator closes the review out with `claude-pr-review` again. The second pass reads only the commits the follow-up added, or the worker's response alone when the follow-up added none, and posts under `## Review` when it finds anything and under `## Review closed` when it finds nothing, so a reader learns from the heading whether work is still owed and takes the merge decision from the counts on the line under it. Repeat from step 4 until a pass closes the review.
|
|
44
|
+
6. The human reads the result and merges. The orchestrator tells any trailing worker whose branch shares a seam with the merged one to run `claude-address-review`, which rebases whether or not the review left anything open.
|
|
46
45
|
|
|
47
46
|
There is no loop construct here. Each worker is a single build that halts at the
|
|
48
47
|
PR. The merge stays a manual human gate. Reliability comes from the plan being
|
|
@@ -55,7 +54,7 @@ The worker's self-review and the orchestrator's review are not the same pass run
|
|
|
55
54
|
twice. They differ by vantage.
|
|
56
55
|
|
|
57
56
|
- Worker self-review, inside `claude-autoship`: the session that wrote the code. Its job is "did I build the plan and does it pass?" Mechanical, and structurally blind to its own misreadings, because the same misreading wrote both the code and the review. This is the green gate that decides whether the PR opens.
|
|
58
|
-
- Orchestrator review, via `claude-pr-review`: a fresh session with cross-feature context (the
|
|
57
|
+
- Orchestrator review, via `claude-pr-review`: a fresh session with cross-feature context (the board, a sibling PR in flight, a downstream contract). Its job is "is this right and does it fit?" It can question the plan itself. This is the merge gate.
|
|
59
58
|
|
|
60
59
|
They collide only if the worker also runs a deep pass. Keep the worker's review
|
|
61
60
|
light and let the orchestrator own the deep, independent one. The human read at
|
|
@@ -127,19 +126,19 @@ coordination costs more than the change it was too small.
|
|
|
127
126
|
|
|
128
127
|
## Where work comes from
|
|
129
128
|
|
|
130
|
-
|
|
129
|
+
Two tiers hold work at different altitudes.
|
|
131
130
|
|
|
132
|
-
-
|
|
133
|
-
- Tasks (`.claude/tasks/`): the active few pulled into the current turn, one file each. Gitignored, high churn. Shape governed by `standards/tasks.md`.
|
|
131
|
+
- Tasks (`.claude/tasks/`): the active few pulled into the current turn, one file each. Gitignored, high churn. Shape governed by `standards/tasks.md`. `priority.md` beside them carries execution order, and `backlog.md` carries what nobody is scheduling.
|
|
134
132
|
- Edits: a few lines, done immediately with no ceremony.
|
|
135
133
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
Nothing above these sequences work into versions. Scope is stated in
|
|
135
|
+
`.claude/REQUIREMENTS.md` and reaches the board as discrete tasks, so why one
|
|
136
|
+
task runs before its neighbors is on its row and why one group of work runs
|
|
137
|
+
before another is carried nowhere at all.
|
|
139
138
|
|
|
140
139
|
## Parallelism
|
|
141
140
|
|
|
142
|
-
The binding constraint is the human and the shared files, not the
|
|
141
|
+
The binding constraint is the human and the shared files, not the board. Cap
|
|
143
142
|
at two or three worker tracks and split them across the stack so they do not
|
|
144
143
|
collide on the same files.
|
|
145
144
|
|
package/package.json
CHANGED
package/scripts/core/verify.sh
CHANGED
|
@@ -25,6 +25,11 @@ SANDBOX_UNDECLARED_CEILING=47
|
|
|
25
25
|
# a new arrival against, and a config file would absorb the arrival silently.
|
|
26
26
|
GOV_EXPECTED_UNREFERENCED="260-shadcn 320-tanstack-query"
|
|
27
27
|
|
|
28
|
+
# The retained counts the audit stage compares each run against. Spelled here
|
|
29
|
+
# rather than derived, because this script only ever names the file in a remedy
|
|
30
|
+
# a reader has to be able to open, and `aitk audits run` owns writing it.
|
|
31
|
+
AUDITS_BASELINE=".claude/audits/baseline.json"
|
|
32
|
+
|
|
28
33
|
check_dependencies() {
|
|
29
34
|
command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
|
|
30
35
|
}
|
|
@@ -209,10 +214,11 @@ seed_entry_count() {
|
|
|
209
214
|
awk '{ total += $1 } END { print total + 0 }'
|
|
210
215
|
}
|
|
211
216
|
|
|
212
|
-
# One numeric summary key out of a
|
|
213
|
-
#
|
|
214
|
-
# the caller does not depend on the order the keys are
|
|
215
|
-
|
|
217
|
+
# One numeric summary key out of a command's JSON record. Every caller passes a
|
|
218
|
+
# name the nested objects in that record do not carry, so the match reaches the
|
|
219
|
+
# top level alone and the caller does not depend on the order the keys are
|
|
220
|
+
# emitted in.
|
|
221
|
+
json_summary_field() {
|
|
216
222
|
printf '%s' "$2" | grep -o "\"$1\":[0-9]\+" | grep -o '[0-9]\+'
|
|
217
223
|
}
|
|
218
224
|
|
|
@@ -466,8 +472,8 @@ main() {
|
|
|
466
472
|
# `|| x=""` on both, because a grep that matches nothing exits non-zero and
|
|
467
473
|
# errexit would take the script down at the assignment, before the guard
|
|
468
474
|
# below could name what went missing.
|
|
469
|
-
total=$(
|
|
470
|
-
armed=$(
|
|
475
|
+
total=$(json_summary_field totalScenarios "$coverage_output") || total=""
|
|
476
|
+
armed=$(json_summary_field armedScenarios "$coverage_output") || armed=""
|
|
471
477
|
if [ -z "$total" ] || [ -z "$armed" ]; then
|
|
472
478
|
log_error "The coverage report carried no scenario totals, so the stage measured nothing. Run bun src/cli.ts sandbox coverage --json."
|
|
473
479
|
fi
|
|
@@ -478,6 +484,68 @@ main() {
|
|
|
478
484
|
log_info "$armed of $total scenarios declare expectations, $undeclared undeclared against a ceiling of $SANDBOX_UNDECLARED_CEILING"
|
|
479
485
|
fi
|
|
480
486
|
|
|
487
|
+
# The three stages above gate on the three findings here that are facts, and
|
|
488
|
+
# this stage reports the rest. It runs the whole set anyway rather than only
|
|
489
|
+
# what those stages skip, because the aggregate's own value is one verdict
|
|
490
|
+
# over every audit, and a stage measuring a subset would report a health this
|
|
491
|
+
# repository never took.
|
|
492
|
+
#
|
|
493
|
+
# The duplicate walk costs 0.8s wall against roughly 4.4s of processor,
|
|
494
|
+
# measured on the authoring machine at 12 verbs run together. That is under
|
|
495
|
+
# every other stage in this script, which is what settles the open question
|
|
496
|
+
# about whether the pipeline can afford it.
|
|
497
|
+
#
|
|
498
|
+
# This reports and never fails. Growth in a judgment count is the thing the
|
|
499
|
+
# baseline exists to make visible, and failing a push on one would teach a
|
|
500
|
+
# contributor to route around the stage, which is the split every audit stage
|
|
501
|
+
# here already keeps. A fact still fails the push, at the specific stage above
|
|
502
|
+
# that names its own remedy.
|
|
503
|
+
log_step "Audit set"
|
|
504
|
+
local audits_output audits_status=0 audits_grown audits_shrunk audits_facts audits_unmeasured audits_absent audits_unrecorded
|
|
505
|
+
audits_output=$(cd "$PROJECT_ROOT" && bun src/cli.ts audits run --json 2>/dev/null) || audits_status=$?
|
|
506
|
+
if [ -z "$audits_output" ]; then
|
|
507
|
+
log_warn "Skipped, the audit set did not report (exit $audits_status)"
|
|
508
|
+
else
|
|
509
|
+
audits_grown=$(json_summary_field grown "$audits_output") || audits_grown=""
|
|
510
|
+
audits_shrunk=$(json_summary_field shrunk "$audits_output") || audits_shrunk=""
|
|
511
|
+
audits_facts=$(json_summary_field facts "$audits_output") || audits_facts=""
|
|
512
|
+
audits_unmeasured=$(json_summary_field unmeasured "$audits_output") || audits_unmeasured=""
|
|
513
|
+
audits_absent=$(json_summary_field absent "$audits_output") || audits_absent=""
|
|
514
|
+
audits_unrecorded=$(json_summary_field unrecorded "$audits_output") || audits_unrecorded=""
|
|
515
|
+
|
|
516
|
+
# An absent field is a record this stage cannot read, which is not the same
|
|
517
|
+
# as a run with nothing to report. Reading it as zero would print a clean
|
|
518
|
+
# line over a summary nobody parsed.
|
|
519
|
+
if [ -z "$audits_grown" ] || [ -z "$audits_facts" ] || [ -z "$audits_unmeasured" ]; then
|
|
520
|
+
log_warn "The audit record carried no summary, so this stage measured nothing. Run bun src/cli.ts audits run."
|
|
521
|
+
else
|
|
522
|
+
# An absent per-machine folder is the ordinary state here rather than a
|
|
523
|
+
# finding, since every one of them is gitignored and CI carries none. It
|
|
524
|
+
# is still stated, because a stage naming only what it measured claims a
|
|
525
|
+
# coverage it does not have.
|
|
526
|
+
if [ -n "$audits_absent" ] && [ "$audits_absent" -gt 0 ]; then
|
|
527
|
+
log_info "$audits_absent per-machine corpus/corpora absent, so unmeasured here by design"
|
|
528
|
+
fi
|
|
529
|
+
if [ "$audits_unmeasured" -gt 0 ]; then
|
|
530
|
+
log_warn "$audits_unmeasured audit(s) did not report, so the set is incomplete. Run bun src/cli.ts audits run."
|
|
531
|
+
fi
|
|
532
|
+
if [ "$audits_facts" -gt 0 ]; then
|
|
533
|
+
log_warn "$audits_facts audit(s) carry a finding that is a fact. The stage above names the remedy."
|
|
534
|
+
fi
|
|
535
|
+
if [ -n "$audits_unrecorded" ] && [ "$audits_unrecorded" -gt 0 ]; then
|
|
536
|
+
log_warn "$audits_unrecorded tracked audit(s) have no recorded floor. Take one with bun src/cli.ts audits run --record."
|
|
537
|
+
fi
|
|
538
|
+
if [ "$audits_grown" -gt 0 ]; then
|
|
539
|
+
log_warn "$audits_grown measure(s) grew against $AUDITS_BASELINE. Run bun src/cli.ts audits run to see which, then fix them or re-record and say why."
|
|
540
|
+
else
|
|
541
|
+
log_info "No measure grew against $AUDITS_BASELINE"
|
|
542
|
+
fi
|
|
543
|
+
if [ -n "$audits_shrunk" ] && [ "$audits_shrunk" -gt 0 ]; then
|
|
544
|
+
log_info "$audits_shrunk measure(s) fell against $AUDITS_BASELINE"
|
|
545
|
+
fi
|
|
546
|
+
fi
|
|
547
|
+
fi
|
|
548
|
+
|
|
481
549
|
# The plugin is the second delivery path and this is the only stage gating it,
|
|
482
550
|
# so the skip below is for a contributor's machine rather than for the merge
|
|
483
551
|
# gate. A runner installs the CLI as a workflow step, which makes an absent
|