@erclx/aitk 0.101.0 → 0.102.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/SKILL.md +9 -2
- package/claude/skills/claude-orchestrate/references/orchestrator-sweep.md +1 -1
- package/claude/skills/claude-tasks/SKILL.md +10 -1
- package/claude/skills/session-resume/SKILL.md +1 -1
- package/docs/agents/index.md +1 -1
- package/docs/agents/markdown-audit.md +21 -11
- package/docs/agents/tasks.md +8 -4
- package/docs/target-projects.md +1 -1
- package/package.json +1 -1
- package/scripts/core/verify.sh +3 -0
- package/src/commands/markdown.ts +44 -28
- package/src/commands/tasks.ts +6 -3
- package/src/markdown/bans.ts +71 -210
- package/src/markdown/structure.ts +9 -71
- package/src/tasks/archive.ts +9 -4
- package/src/tasks/validate.ts +103 -3
- package/standards/prose.md +0 -2
- package/standards/tasks.md +51 -8
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +43 -47
|
@@ -25,6 +25,7 @@ session's review are different passes, and how a feature is sized.
|
|
|
25
25
|
Read the board in parallel, resolving the paths at the main worktree root per Worktrees in `CLAUDE.md`:
|
|
26
26
|
|
|
27
27
|
- `.claude/tasks/priority.md`: execution order and what each task is waiting on
|
|
28
|
+
- `.claude/tasks/backlog.md`: what is not being scheduled, when the file exists
|
|
28
29
|
- `.claude/tasks/index.md`: what is queued
|
|
29
30
|
- `.claude/plans/*.md`: features already planned and ready to hand off
|
|
30
31
|
- `.claude/ROADMAP.md`: sequencing rationale, when the file exists
|
|
@@ -34,6 +35,8 @@ Then output the state of play so the human knows what to launch, review, and mer
|
|
|
34
35
|
|
|
35
36
|
`priority.md` is the ordering source. `index.md` sorts by filename and says nothing about order, so read the sequence from the first and never infer it from the second. When `priority.md` is absent, report the queue and say the order is unrecorded.
|
|
36
37
|
|
|
38
|
+
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
|
+
|
|
37
40
|
The roadmap is optional and this skill does not require it. It carries why a sequence is what it is, changes only when strategy changes, and is absent in a project whose scope has already shipped. Report what it says and name it as the source. Never assert an active version the file does not state, and say nothing about one when the file is missing.
|
|
38
41
|
|
|
39
42
|
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.
|
|
@@ -155,7 +158,8 @@ It counts unclaimed plans against workers rather than reading the reserve in ste
|
|
|
155
158
|
- A task whose outcomes are all `[x]` runs `claude-docs` for the plan sweep, then `claude-tasks` to archive
|
|
156
159
|
- A task whose outcomes describe standing policy rather than a deliverable never closes on its own, so hand it to a worker to encode the policy where it is enforced, then cut the outcomes with the reason recorded and archive once that branch merges. Encoding it from this session would write a tracked file, which Boundaries forbids.
|
|
157
160
|
4. Read `.claude/tasks/priority.md` and count entries under its `## Run now` heading that carry a written plan. Keep one in reserve beyond what is running.
|
|
158
|
-
5. Promote
|
|
161
|
+
5. Promote from the top of `## Needs a plan`, which is where the last sweep recorded what to plan next. Depart from that order when something has changed under it and say what changed, since a position nobody honors is the ordering going stale on the surface built to hold it. What sets the order in the first place is whether a task establishes functionality rather than how old it is, so prefer a task that adds or proves a mechanism over one that trims, tidies, or audits an existing surface.
|
|
162
|
+
- Re-take the board-or-backlog call while the file is open. A row that has stopped being near-term moves to `backlog.md`, and a backlogged task the last wave made near-term moves onto the board. Both are one line removed from one surface and written to the other, and the standard states the test.
|
|
159
163
|
6. Before promoting a candidate, list the files it touches against every task already running, per Parallelism below. Name the overlap and serialize when the sets are not disjoint.
|
|
160
164
|
- A candidate held by something outside the tree stays where it is whatever those sets show. A collision is one of the reasons a task cannot start, so disjointness clears that reason alone and leaves an external condition standing.
|
|
161
165
|
7. Write a plan for each newly promoted task with `claude-feature`, carrying the in-flight constraint that The loop above states, then report:
|
|
@@ -166,6 +170,7 @@ Findings placed: <finding> → <destination>
|
|
|
166
170
|
Archived: <task>
|
|
167
171
|
Promoted: <task>, touches <surfaces>, parallel with <task> because <disjoint sets>
|
|
168
172
|
Serialized: <task> behind <task>, both write <file>
|
|
173
|
+
Backlogged: <task>, because <what stopped being near-term>
|
|
169
174
|
Ready now: <tasks with plans, and what each waits on>
|
|
170
175
|
```
|
|
171
176
|
|
|
@@ -179,9 +184,11 @@ Do not promote a task to fill the queue when nothing qualifies. A thin queue is
|
|
|
179
184
|
|
|
180
185
|
### Writing the board
|
|
181
186
|
|
|
182
|
-
Promoting, demoting, and archiving a row all write `.claude/tasks/priority.md`, and this session is the only writer apart from `aitk tasks archive`.
|
|
187
|
+
Promoting, demoting, and archiving a row all write `.claude/tasks/priority.md`, and this session is the only writer apart from `aitk tasks archive`. Moving a task between the board and `.claude/tasks/backlog.md` writes both files, and this session is that file's only writer.
|
|
183
188
|
|
|
184
189
|
- Edit the file with the file-editing tool. A shell stream editor and an inline string replace both exit clean on a non-match, so a promotion that matched nothing leaves the board wrong with nothing reporting it, and the file-editing tool errors instead.
|
|
190
|
+
- Write both halves of a move before reporting it. A row removed from one surface and not written to the other leaves a task file nothing names, and the folder is gitignored with no history to recover the row from. `aitk tasks validate` reports that state, so run it after any move.
|
|
191
|
+
- Put the reason a row sits where it does in its Waiting on cell. Position is the ordering and the cell is where the ordering's rationale lives, so a row promoted with the cell left alone carries an order the next sweep cannot check.
|
|
185
192
|
- Put a pointer in the Plan column, never prose. `## Run now` claims a written plan covers every open outcome, and `claude-autoship` refuses at its guard when it follows the column and finds no plan, which spends a worker dispatch to learn what the row should have said.
|
|
186
193
|
- Name the file set in the Touches column. The disjointness call in step 6 is only checkable later when the sets are written down rather than reasoned once and discarded.
|
|
187
194
|
- Re-resolve every Plan pointer after anything archives a plan
|
|
@@ -10,7 +10,7 @@ Sweep the board as orchestrator after merging. Run this once per batch of merges
|
|
|
10
10
|
3. Re-verify every plan already written, not only the ones this sweep writes. A queued plan goes stale from whatever merged while it waited, and the loop's verify step fires at handoff rather than after a merge, so nothing else catches it. Grep each construct the plan names and count the sites against its claim, then open each file rather than trusting its account.
|
|
11
11
|
4. Re-check any precondition a plan states about live state outside the repository. A remote branch, an open issue, or an installed version was true when the plan was written and is not a fact about the tree.
|
|
12
12
|
|
|
13
|
-
Run `aitk tasks validate` once the board is rewritten and before reporting it. It resolves every plan pointer,
|
|
13
|
+
Run `aitk tasks validate` once the board is rewritten and before reporting it. It resolves every plan pointer, accounts every task file against the board and the backlog both, tests the `## Run now` file sets for overlap, and re-takes the two blocker kinds a command can settle. It reports and never writes, so fix each row it names and run it again. A finding it reports is a board defect rather than a task finding, so it goes nowhere but the board.
|
|
14
14
|
|
|
15
15
|
Then run `orchestrator-parked.md` over the rows the validator listed as untested. Those carry the three blocker kinds no command settles, and the merge this sweep followed changed the tree under every one of them at once while the refill above re-read none. Take the untested rows alone rather than the whole board, since the validator already answered the rest.
|
|
16
16
|
|
|
@@ -43,7 +43,15 @@ Never write a `Pull request:` line here. `git-pr` adds it when a pull request op
|
|
|
43
43
|
|
|
44
44
|
Write it immediately. Claude Code's tool permission dialog is the confirmation gate. Do not pause for approval.
|
|
45
45
|
|
|
46
|
-
### Step 4:
|
|
46
|
+
### Step 4: place it on a surface
|
|
47
|
+
|
|
48
|
+
A task file with no row is a dropped task, so name the surface it lands on in the same pass that creates it. A task that would plausibly be planned within the next few waves takes a row under `## Needs a plan` in `.claude/tasks/priority.md`, positioned by where it sits against the rows already there, with the reason for that position in its `Waiting on` cell. Anything else takes a line in `.claude/tasks/backlog.md`, which is unordered and where in the file it goes means nothing.
|
|
49
|
+
|
|
50
|
+
The test and both file shapes are in `.claude/standards/tasks.md`. From a linked worktree the file-editing tools refuse the main root, so a row lands through the same `Bash` route the file itself took.
|
|
51
|
+
|
|
52
|
+
Say which surface it went to and why in the report. The call is a judgment restated on every sweep rather than a property of the task, and a placement with no stated reason is one the next sweep re-derives from nothing.
|
|
53
|
+
|
|
54
|
+
### Step 5: report unlinked origins
|
|
47
55
|
|
|
48
56
|
Scan for work that has been decided and would otherwise be forgotten. List `.claude/groundwork/` and run `gh issue list --state open` when a remote is configured, then grep the board for each track name and issue number. Report any with no task, one line each.
|
|
49
57
|
|
|
@@ -105,6 +113,7 @@ Create:
|
|
|
105
113
|
✅ Created: .claude/tasks/vXX.Y-<slug>.md
|
|
106
114
|
|
|
107
115
|
<label> sits between <neighbor> and <neighbor>.
|
|
116
|
+
<board or backlog, and why it landed there>.
|
|
108
117
|
|
|
109
118
|
**Origin with no task:**
|
|
110
119
|
|
|
@@ -12,7 +12,7 @@ Resolve `.claude/plans/`, `.claude/memory/`, and `.claude/tasks/` at the main wo
|
|
|
12
12
|
Read these in parallel, skipping any that do not exist:
|
|
13
13
|
|
|
14
14
|
- the newest `.claude/tasks/session-*.md`: the handoff a previous session wrote before a compaction, per `.claude/standards/session.md`, or `${CLAUDE_SKILL_DIR}/../../standards/session.md` when the project does not have it. It leads the report rather than the reads.
|
|
15
|
-
- `.claude/tasks/index.md`: the folder catalog. Read this before any individual task file, and take the
|
|
15
|
+
- `.claude/tasks/index.md`: the folder catalog. Read this before any individual task file, and take the task list from it by dropping the `index`, `priority`, `backlog`, and `session-` rows, which are siblings rather than tasks.
|
|
16
16
|
- `.claude/plans/*.md`: execution detail for in-progress tasks
|
|
17
17
|
- `.claude/memory/index.md` and any memory files relevant to the top backlog item
|
|
18
18
|
|
package/docs/agents/index.md
CHANGED
|
@@ -25,5 +25,5 @@ CLI catalog and invocation rules for agents, split by command domain. Start with
|
|
|
25
25
|
- [Scripting](scripting.md): The runtime catalogs that replace hardcoded names, what each carries, and a headless invocation per domain
|
|
26
26
|
- [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
|
|
27
27
|
- [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
|
|
28
|
-
- [Tasks](tasks.md): Selecting a shipped task by stem or pull request, recording a number and closing an outcome, the refusal reasons, the board checks validate runs, and why the board root defaults to the main worktree
|
|
28
|
+
- [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
|
|
29
29
|
- [Teach](teach.md): Listing learning workspaces and the ordinal a new one takes, opening one with its required files, recording sources and glossary terms, the refusal reasons, and why every write here runs through a verb
|
|
@@ -27,21 +27,25 @@ A bare run measures every markdown file git lists, tracked plus untracked-and-no
|
|
|
27
27
|
|
|
28
28
|
## Where the rules come from
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
The three ban sets and all six checkpoints ship with the `aitk` package as data, in `src/markdown/bans.ts` and `src/markdown/structure.ts`. Every project is measured against the same sets whether or not it installed any standards, and no file has to resolve for a run to mean something.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Reading them out of the standards per run was the original design. It put a parser contract on two documents authored for people, and `prose.md` had to carry a paragraph of its own warning an author that a one-word backticked example in a `- Do not use ` bullet would be lifted into a literal ban set and ban that word everywhere. A rule existing to protect a parser from the prose it parses is the argument for separating them.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
`markdown.md` and `prose.md` still state every ban and every checkpoint, and a reader follows those rather than the code. Nothing compares the two, so a number moved in one place and left in the other drifts silently. Move both in the same change.
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
The sets are closed rather than extensible, so a project cannot add a term by editing a file. What decides that is the measurement behind them: 21 terms across 483 markdown files report a clean exit, and every occurrence of a banned word in the corpus sits inside the ban list itself or inside an example demonstrating the ban. The set is a prior an author already knows rather than a filter that has caught anything, and enumeration cannot close the gap it aims at, since `just`, `allows`, and `very` carry honest uses no literal match separates.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Freezing the spellings gave up a property worth naming. They were derived by applying the standard's own suffix pairs to its own examples, so an example added there extended the check with no code edit. The set is now carried whole, and `analyse` stays out of it for the reason it was always out: the standard's example is `analyze`, which ends in `-yze` rather than the `-ize` its rule states.
|
|
39
|
+
|
|
40
|
+
A set shipped empty is reported rather than passed. It finds nothing and would exit clean, which reports a corpus nobody checked as a corpus carrying no violation, so the run names the empty set and exits `1`. The sets ship with the package, so a defect in the build is the only cause left.
|
|
41
|
+
|
|
42
|
+
`aitk standards <name>` still resolves a standard under `.claude/standards/`, then the authoring root, then the package corpus, and prints it, so the human catalog reads without a project copy on disk.
|
|
39
43
|
|
|
40
44
|
## What each check reports
|
|
41
45
|
|
|
42
46
|
### Bans
|
|
43
47
|
|
|
44
|
-
Three closed sets report a hit: the characters `markdown.md` bans under `## Punctuation`, the single lowercase words `prose.md` bans under `## Language`, and the British spellings
|
|
48
|
+
Three closed sets report a hit: the characters `markdown.md` bans under `## Punctuation`, the single lowercase words `prose.md` bans under `## Language`, and the British spellings of the American examples that section lists.
|
|
45
49
|
|
|
46
50
|
Deriving the spellings rather than pattern-matching a suffix is what keeps `exercises`, `promises`, and `revised` out of the report. A suffix pattern over the same corpus produced 46 false positives from words of that shape, and a closed set of whole words reaches none of them.
|
|
47
51
|
|
|
@@ -96,7 +100,9 @@ A bullet, a heading, a table row, a blockquote, a blank line, and a fence each e
|
|
|
96
100
|
|
|
97
101
|
## Exit codes
|
|
98
102
|
|
|
99
|
-
Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, and `
|
|
103
|
+
Exit codes are `0` for a completed run with no gating finding, `1` for a refusal, `2` for a ban hit, and `3` for a shipped ban set that arrived empty. A banned character, word, or spelling fails the run. Bullet, paragraph, and depth weight are judgments a reader settles, so all three report under every code.
|
|
104
|
+
|
|
105
|
+
`3` is separate from `1` because the two want different responses from a caller. A refusal means no corpus was built, and the `Markdown bans` stage in `scripts/core/verify.sh` is right to warn and skip. An empty set means the corpus was walked and nothing was looked for, so that stage fails the push on `3` rather than skipping.
|
|
100
106
|
|
|
101
107
|
`2` rather than `1` for the gate keeps a measurement that succeeded and found something distinct from the audit declining to measure at all. A caller reading one as the other sends a reader hunting a defect that does not exist, which is the distinction `aitk context audit` and the `verify.sh` seed stage already draw between the same two codes.
|
|
102
108
|
|
|
@@ -118,13 +124,17 @@ A hit the closed set cannot separate from correct prose is the case with no thir
|
|
|
118
124
|
|
|
119
125
|
### Where the rules are enforced
|
|
120
126
|
|
|
121
|
-
Four surfaces
|
|
127
|
+
Four surfaces apply the ban sets and three of them go through this verb. `.claude/hooks/standards-audit.sh` runs it against a single file after each markdown edit, the seed copy a project installs does the same, and the `Markdown bans` stage in `scripts/core/verify.sh` runs it across the whole corpus before a push. Each hook parsed its own copy of the word bans in awk before that, which left a British spelling passing at edit time and failing the push with nothing in between explaining the difference.
|
|
128
|
+
|
|
129
|
+
The seed copy moved onto the verb when the sets became data, since its awk had nothing left to parse. It resolves one runner where the toolkit copy resolves two, looking for no checkout source, and a machine carrying no `aitk` gets a report naming the binary to install rather than a silent pass. `scripts/core/check-seed-independence.sh` scopes its walk to markdown and leaves the seed hooks outside it, which its own comment records as deliberate.
|
|
130
|
+
|
|
131
|
+
The fourth surface reads the standards directly and is not a consolidation left half done. `claude/skills/claude-standards-audit/SKILL.md` greps the banned tokens agent-side, which is a session reading prose rather than a process it can shell out to, and it ships to every target. It is the likeliest place for the next drift, since nothing compares it against the verb.
|
|
122
132
|
|
|
123
|
-
The
|
|
133
|
+
The hook prefers a checkout's own `src/cli.ts` over a globally installed binary, so it and the push stage read one build. A published binary lags a branch by whatever has not been released, which would put a ban kind added on the branch into the push and not into the edit. It reads its findings out of the `--json` record rather than off the exit code, so an older binary still reports where the fallback applies. It reads `bans.emptySets` out of the same record, so a set the verb shipped empty reaches the author as a check narrowed to what it could measure rather than as a clean pass.
|
|
124
134
|
|
|
125
|
-
|
|
135
|
+
That field replaced `bans.missingStandards`, which answered a standard resolving under none of three roots. The sets ship with the package now, so the state it named cannot occur and the narrowed check has one cause left, a defect in the build. The hook keeps reading a field either way, since a reader cannot tell a narrowed check from a clean one without it.
|
|
126
136
|
|
|
127
|
-
|
|
137
|
+
Both hooks answer an absent record as well. A completed run always writes the record and a refusal writes none, so an empty one means the verb declined to measure rather than measured and found nothing. The verb needs a git repository to build its corpus and refuses without one, which is a project the seeded hook can be installed into, and reading the findings alone reported that as a clean file.
|
|
128
138
|
|
|
129
139
|
A machine with neither runner still blocks no edit, and it says so rather than exiting clean. The push stage holds either way. An edit nobody checked and an edit carrying no violation are one silence to a reader, so the enforcement a machine lacks is reported rather than inferred.
|
|
130
140
|
|
package/docs/agents/tasks.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Tasks
|
|
3
|
-
description: Selecting a shipped task by stem or pull request, recording a number and closing an outcome, the refusal reasons, the board checks validate runs, and why the board root defaults to the main worktree
|
|
3
|
+
description: 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
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Tasks
|
|
@@ -95,11 +95,15 @@ Five checks run. Plan and Collisions reach one half each of the `## Run now` tes
|
|
|
95
95
|
| Check | What it reports |
|
|
96
96
|
| ---------- | ------------------------------------------------------------------------------------ |
|
|
97
97
|
| Plan | A `## Run now` row whose Plan column carries no link, or one resolving to no file |
|
|
98
|
-
| Mapping | A row naming no task file, and a task file
|
|
99
|
-
| Grouping | A task carrying a row in more than one readiness group
|
|
98
|
+
| Mapping | A row or backlog line naming no task file, and a task file neither surface names |
|
|
99
|
+
| Grouping | A task carrying a row in more than one readiness group, or on both surfaces |
|
|
100
100
|
| Collisions | Two `## Run now` rows whose Touches columns name a path in common |
|
|
101
101
|
| Blockers | A parked row whose blocker has stopped holding, or whose cited task resolves nowhere |
|
|
102
102
|
|
|
103
|
+
Mapping spans two surfaces, because a task sits on `priority.md` when it would plausibly be planned soon and on `backlog.md` otherwise. A task file either surface names is accounted for, a file neither names is `row-missing`, and a file both name is `row-duplicated` for the reason a task in two groups is: it claims two things about itself and only one can hold. One check across both is what lets a task move between them without the move reading as a dropped file.
|
|
104
|
+
|
|
105
|
+
A backlog line is a bullet carrying a link to a sibling task, since the backlog is a flat unordered list rather than a table. A bullet holding prose is skipped rather than reported, which keeps the file's own intro out of the findings, and the task that bullet meant to name is still reported as reaching neither surface. A project carrying no `backlog.md` reads as an empty backlog rather than a refusal, which leaves the one-to-one mapping this check ran before the second surface existed.
|
|
106
|
+
|
|
103
107
|
The collision check is the one a person cannot run by eye. Paths come from the backticked spans in the Touches column, a span naming no file is dropped, and a directory collides with any file beneath it. A `## Run now` row whose column parses to nothing is reported rather than skipped, since a row stating no file set makes a claim nothing can check.
|
|
104
108
|
|
|
105
109
|
The blocker check re-takes a measurement the board records once and never repeats. Two of the five blocker kinds put a fact on disk: a dependency is settled by the cited task being archived or closing every outcome, and a collision is settled by nothing under `## Run now` still holding the file the cell cites.
|
|
@@ -126,7 +130,7 @@ An untested row is not a finding and moves no exit code. Reading a clean finding
|
|
|
126
130
|
|
|
127
131
|
Exit codes: `0` every check passed, `1` refused, `2` at least one finding. The `reason` field carries which gate refused: `no-board`, `no-ordering`, or `no-groups`. A board grouping under headings of its own trips `no-groups` rather than being read against columns it never declared.
|
|
128
132
|
|
|
129
|
-
Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index` and `
|
|
133
|
+
Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index`, `priority`, and `backlog` siblings are skipped, along with every pre-compaction handoff, which takes one file per session under a `session-` prefix. None of them is a task, and a handoff counted as one would be reported as a task carrying no row on every session that wrote one.
|
|
130
134
|
|
|
131
135
|
Skills branch on the findings rather than on the exit code:
|
|
132
136
|
|
package/docs/target-projects.md
CHANGED
|
@@ -89,7 +89,7 @@ Run `aitk tooling list --json` and `aitk gov list --json` to see the current cat
|
|
|
89
89
|
`governance`, `standards`, and `wiki` are skippable:
|
|
90
90
|
|
|
91
91
|
- `--skip governance`: leave `.claude/rules/` empty. Standards still install, so `.claude/standards/prose.md` lands with nothing pointing at it and no coding standard loads on a file match. The preview names any `--add` rules the skip drops, and the run prints the `aitk gov install <stack> <path>` command to add rules afterward, carrying those extras so one paste restores what the skip declined.
|
|
92
|
-
- `--skip standards`: leave standards out. The governance rules still reference `.claude/standards/`, so their authority lines resolve to nothing for a reader following the path. Toolkit skills are unaffected, since each falls back to the copy in its own plugin root
|
|
92
|
+
- `--skip standards`: leave standards out. The governance rules still reference `.claude/standards/`, so their authority lines resolve to nothing for a reader following the path. Toolkit skills are unaffected, since each falls back to the copy in its own plugin root. `aitk standards <name>` searches the corpus inside the CLI's own package behind both project roots, so it prints a standard in a project that skipped the install, and `aitk markdown audit` needs no standard at all, its ban sets and checkpoints shipping with the package as data. What the skip costs is the editable copy rather than the enforcement.
|
|
93
93
|
- `--skip wiki`: skip the `.claude/wiki/` scaffold. A target that already carries a root `wiki/` keeps it, since the verb reports that folder rather than migrating it.
|
|
94
94
|
|
|
95
95
|
That standards fallback carries runtime behavior rather than reference prose alone, because the pre-publish scan and the branch-slug transform each have a standard of their own, `publish.md` and `slug.md`, cited by the skills that run them.
|
package/package.json
CHANGED
package/scripts/core/verify.sh
CHANGED
|
@@ -363,6 +363,9 @@ main() {
|
|
|
363
363
|
1)
|
|
364
364
|
log_warn "Skipped, the markdown audit refused and measured nothing"
|
|
365
365
|
;;
|
|
366
|
+
3)
|
|
367
|
+
log_error "The markdown audit shipped an empty ban set, so the corpus was walked and nothing was looked for. Check src/markdown/bans.ts."
|
|
368
|
+
;;
|
|
366
369
|
2)
|
|
367
370
|
# `|| true` because the re-run exits non-zero by construction, and `set -e`
|
|
368
371
|
# would take the script down before log_error names the remedy.
|
package/src/commands/markdown.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises'
|
|
2
2
|
import { resolve } from 'node:path'
|
|
3
3
|
import type { Command } from 'commander'
|
|
4
|
-
import {
|
|
4
|
+
import { BAN_SETS, emptyBanSets } from '@/markdown/bans'
|
|
5
5
|
import { resolveMarkdown } from '@/markdown/files'
|
|
6
6
|
import { isGating } from '@/markdown/gate'
|
|
7
|
-
import { type BanFinding, bodyLines, scanBans } from '@/markdown/scan'
|
|
8
7
|
import {
|
|
8
|
+
type BanFinding,
|
|
9
|
+
type BanSets,
|
|
10
|
+
bodyLines,
|
|
11
|
+
scanBans,
|
|
12
|
+
} from '@/markdown/scan'
|
|
13
|
+
import {
|
|
14
|
+
CHECKPOINTS,
|
|
9
15
|
type Checkpoints,
|
|
10
16
|
measureStructure,
|
|
11
|
-
parseCheckpoints,
|
|
12
17
|
type StructureReport,
|
|
13
18
|
} from '@/markdown/structure'
|
|
14
19
|
import {
|
|
@@ -21,8 +26,19 @@ import {
|
|
|
21
26
|
plural,
|
|
22
27
|
} from '@/ui'
|
|
23
28
|
|
|
29
|
+
const EXIT_REFUSED = 1
|
|
24
30
|
const EXIT_GATE = 2
|
|
25
31
|
|
|
32
|
+
/**
|
|
33
|
+
* A set shipped empty, so the run measured a corpus against nothing.
|
|
34
|
+
*
|
|
35
|
+
* Distinct from `EXIT_REFUSED` because the two want different responses. A
|
|
36
|
+
* refusal means no corpus was built and a push stage is right to skip, while an
|
|
37
|
+
* empty set means the corpus was walked and nothing was looked for, which is a
|
|
38
|
+
* defect in the build and has to fail.
|
|
39
|
+
*/
|
|
40
|
+
const EXIT_UNUSABLE = 3
|
|
41
|
+
|
|
26
42
|
interface AuditCommandOptions {
|
|
27
43
|
readonly json?: boolean
|
|
28
44
|
}
|
|
@@ -58,6 +74,7 @@ export function register(program: Command): void {
|
|
|
58
74
|
' 0 the audit completed with no gating finding',
|
|
59
75
|
' 1 refused, with the reason on stderr',
|
|
60
76
|
' 2 a banned character, word, or spelling is present',
|
|
77
|
+
' 3 a shipped ban set is empty, so the run measured nothing',
|
|
61
78
|
'',
|
|
62
79
|
'A ban hit is a fact and gates unconditionally. Bullet, paragraph, and',
|
|
63
80
|
'depth weight are judgments a reader settles, so all three report and',
|
|
@@ -68,12 +85,11 @@ export function register(program: Command): void {
|
|
|
68
85
|
'where the token is genuinely an identifier under discussion, which is',
|
|
69
86
|
'what markdown.md reserves the span for.',
|
|
70
87
|
'',
|
|
71
|
-
'Bans and checkpoints
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
76
|
-
'are in reach.',
|
|
88
|
+
'Bans and checkpoints ship with the aitk package rather than being read',
|
|
89
|
+
'out of a standards file, so a project that installed no standards is',
|
|
90
|
+
'measured the same as one that did. markdown.md and prose.md still state',
|
|
91
|
+
'every rule for a reader. No folder has to resolve and no index.md has',
|
|
92
|
+
'to exist, so .claude/rules/, governance/, and snippets/ are in reach.',
|
|
77
93
|
'',
|
|
78
94
|
'Examples:',
|
|
79
95
|
' aitk markdown audit',
|
|
@@ -110,9 +126,9 @@ async function runAudit(
|
|
|
110
126
|
)
|
|
111
127
|
}
|
|
112
128
|
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
const checkpoints =
|
|
129
|
+
const bans = BAN_SETS
|
|
130
|
+
const empty = emptyBanSets(bans)
|
|
131
|
+
const checkpoints = CHECKPOINTS
|
|
116
132
|
|
|
117
133
|
const reports: FileReport[] = await Promise.all(
|
|
118
134
|
scope.files.map(async (rel) => {
|
|
@@ -127,7 +143,7 @@ async function runAudit(
|
|
|
127
143
|
|
|
128
144
|
intro('aitk markdown audit')
|
|
129
145
|
reportScope(scope.files, scope.unmatched)
|
|
130
|
-
reportBans(reports, bans)
|
|
146
|
+
reportBans(reports, bans, empty)
|
|
131
147
|
reportBullets(reports, checkpoints)
|
|
132
148
|
reportParagraphs(reports, checkpoints)
|
|
133
149
|
reportDepth(reports, checkpoints)
|
|
@@ -143,8 +159,7 @@ async function runAudit(
|
|
|
143
159
|
characters: bans.characters,
|
|
144
160
|
words: bans.words,
|
|
145
161
|
spellings: bans.spellings,
|
|
146
|
-
|
|
147
|
-
missingStandards: bans.missing,
|
|
162
|
+
emptySets: empty,
|
|
148
163
|
},
|
|
149
164
|
checkpoints: {
|
|
150
165
|
run: checkpoints.run,
|
|
@@ -153,7 +168,6 @@ async function runAudit(
|
|
|
153
168
|
paragraph: checkpoints.paragraph,
|
|
154
169
|
sentences: checkpoints.sentences,
|
|
155
170
|
renderWidth: checkpoints.renderWidth,
|
|
156
|
-
fellBack: checkpoints.fellBack,
|
|
157
171
|
},
|
|
158
172
|
entries: reports.map((report) => ({
|
|
159
173
|
path: report.rel,
|
|
@@ -167,6 +181,10 @@ async function runAudit(
|
|
|
167
181
|
)
|
|
168
182
|
}
|
|
169
183
|
|
|
184
|
+
// An empty set finds nothing and would exit clean, which reports a corpus
|
|
185
|
+
// nobody checked as a corpus carrying no violation.
|
|
186
|
+
if (empty.length > 0) return EXIT_UNUSABLE
|
|
187
|
+
|
|
170
188
|
const gating = isGating({
|
|
171
189
|
bans: reports.flatMap((report) => report.bans),
|
|
172
190
|
structure: reports.map((report) => report.structure),
|
|
@@ -180,7 +198,7 @@ function refuse(message: string): number {
|
|
|
180
198
|
logStep('Refused')
|
|
181
199
|
logWarn(message)
|
|
182
200
|
outro()
|
|
183
|
-
return
|
|
201
|
+
return EXIT_REFUSED
|
|
184
202
|
}
|
|
185
203
|
|
|
186
204
|
function reportScope(
|
|
@@ -203,18 +221,22 @@ function reportScope(
|
|
|
203
221
|
* the sentence and every voice rule is a judgment, so a report listing hits
|
|
204
222
|
* without naming those would read as a verdict on the whole standard.
|
|
205
223
|
*/
|
|
206
|
-
function reportBans(
|
|
224
|
+
function reportBans(
|
|
225
|
+
reports: readonly FileReport[],
|
|
226
|
+
bans: BanSets,
|
|
227
|
+
empty: readonly string[],
|
|
228
|
+
): void {
|
|
207
229
|
logStep('Bans')
|
|
208
230
|
|
|
209
|
-
if (
|
|
231
|
+
if (empty.length > 0) {
|
|
210
232
|
logWarn(
|
|
211
|
-
`Not measured.
|
|
233
|
+
`Not measured. The shipped set is empty for: ${empty.join(', ')}. The sets ship with the aitk package, so an empty one is a defect in the build rather than a missing install.`,
|
|
212
234
|
)
|
|
213
|
-
|
|
235
|
+
return
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
logInfo(
|
|
217
|
-
`${plural(bans.characters.length, 'character')}, ${plural(bans.words.length, 'word')}, and ${plural(bans.spellings.length, 'spelling')}
|
|
239
|
+
`${plural(bans.characters.length, 'character')}, ${plural(bans.words.length, 'word')}, and ${plural(bans.spellings.length, 'spelling')} shipped with the aitk package`,
|
|
218
240
|
)
|
|
219
241
|
logInfo(
|
|
220
242
|
'Frontmatter, fenced blocks, code spans, and link destinations are excluded.',
|
|
@@ -379,12 +401,6 @@ function reportDepth(
|
|
|
379
401
|
'A run that is entirely table rows is excluded too, since a heading inside a table splits the table rather than the run.',
|
|
380
402
|
)
|
|
381
403
|
|
|
382
|
-
if (checkpoints.fellBack.length > 0) {
|
|
383
|
-
logWarn(
|
|
384
|
-
`Read no number from the standard for: ${checkpoints.fellBack.join(', ')}. Measured against the shipped default instead.`,
|
|
385
|
-
)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
404
|
const over = reports
|
|
389
405
|
.filter((report) => report.structure.longestRun > checkpoints.run)
|
|
390
406
|
.sort((a, b) => b.structure.longestRun - a.structure.longestRun)
|
package/src/commands/tasks.ts
CHANGED
|
@@ -104,7 +104,7 @@ export function register(program: Command): void {
|
|
|
104
104
|
'',
|
|
105
105
|
'Checks:',
|
|
106
106
|
' every Run now row points at a plan file that resolves',
|
|
107
|
-
' every
|
|
107
|
+
' every task file carries a board row or a backlog line, never both',
|
|
108
108
|
' no task carries more than one row',
|
|
109
109
|
' no two Run now rows touch the same file',
|
|
110
110
|
'',
|
|
@@ -450,12 +450,14 @@ function reportValidation(
|
|
|
450
450
|
intro('aitk tasks validate')
|
|
451
451
|
logStep('Board')
|
|
452
452
|
logInfo(
|
|
453
|
-
`${outcome.rows} row(s) across the readiness groups, ${outcome.tasks} task file(s)`,
|
|
453
|
+
`${outcome.rows} row(s) across the readiness groups, ${outcome.backlog} backlog line(s), ${outcome.tasks} task file(s)`,
|
|
454
454
|
)
|
|
455
455
|
|
|
456
456
|
logStep(outcome.findings.length === 0 ? 'Clean' : 'Findings')
|
|
457
457
|
if (outcome.findings.length === 0) {
|
|
458
|
-
logInfo(
|
|
458
|
+
logInfo(
|
|
459
|
+
'every row resolves, every task sits on one surface, and each touches its own files',
|
|
460
|
+
)
|
|
459
461
|
} else {
|
|
460
462
|
for (const finding of outcome.findings) logWarn(describe(finding))
|
|
461
463
|
}
|
|
@@ -481,6 +483,7 @@ function reportValidation(
|
|
|
481
483
|
ok: true,
|
|
482
484
|
root,
|
|
483
485
|
rows: outcome.rows,
|
|
486
|
+
backlog: outcome.backlog,
|
|
484
487
|
tasks: outcome.tasks,
|
|
485
488
|
findings: outcome.findings,
|
|
486
489
|
untested: outcome.untested,
|