@erclx/aitk 0.107.2 → 0.109.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/markdown-audit.md +24 -2
- package/docs/agents/skills-audit.md +7 -1
- package/docs/ai-workflow.md +2 -3
- package/docs/operating-model.md +18 -19
- package/governance/rules/claude/570-skill.md +5 -0
- package/package.json +1 -1
- package/src/commands/markdown.ts +104 -1
- package/src/markdown/structure.ts +278 -33
- package/standards/requirements.md +2 -2
- package/standards/session.md +1 -1
- 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
|
|
|
@@ -27,7 +27,9 @@ 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
|
-
The three ban sets and all
|
|
30
|
+
The three ban sets and all nine 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
|
+
|
|
32
|
+
Six of the nine are stated in `markdown.md` and the three cadence numbers are stated in the `write-human` skill. That split is the content boundary rather than an accident: `markdown.md` carries the enforced rules a scan can decide, and the skill carries the rhythm rules a ban list cannot express. A cadence number moved in the skill and left in the code drifts the same way, so move both.
|
|
31
33
|
|
|
32
34
|
Reading them out of the standards per run was the original design. It put a parser contract on a document authored for people, and the standard 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
35
|
|
|
@@ -98,9 +100,29 @@ That re-sample ran once the scan stopped counting link syntax as prose. Findings
|
|
|
98
100
|
|
|
99
101
|
A bullet, a heading, a table row, a blockquote, a blank line, and a fence each end a paragraph, so a heavy bullet is reported by the bullet check alone and never counted twice.
|
|
100
102
|
|
|
103
|
+
### Cadence
|
|
104
|
+
|
|
105
|
+
Uniform cadence is the failure a ban list cannot express. A ban set states negatives, and fragments, verbless clauses, and sentences that all run one length are each the absence of something, so no addition to the thirteen banned words reaches any of them. The shape layer already measured a bullet, a paragraph, and a run, and stopped one level above where that failure lives.
|
|
106
|
+
|
|
107
|
+
Cadence measures a paragraph on two numbers. The spread is the words between its longest and shortest sentence, and the opener count is the times one word opens a sentence in it. A spread of five words or under reads as one cadence, and a word opening more than two sentences is a pattern rather than a coincidence. Both come from `## Rhythm` in the `write-human` skill, which states them about prose a person reads, and this measures against that statement rather than setting a threshold of its own.
|
|
108
|
+
|
|
109
|
+
Words are counted off the text a reader is shown. A link contributes its anchor text, an autolink contributes nothing, and the sentence boundaries do not move under that masking, since the boundary pattern requires whitespace after the terminal punctuation and no destination carries any. An opening word is lowercased and stripped of punctuation, so a sentence opening on a backticked command name reports the command.
|
|
110
|
+
|
|
111
|
+
A paragraph carrying fewer than three sentences is skipped rather than scored. A two-sentence configuration note has no spread worth reading, and the opener rule is written about a third sentence turning a coincidence into a pattern, so neither number says anything before the floor. That is the cheap form of a wider exemption: a shape-aware one, exempting a short reference block by what it is rather than by how many sentences it holds, waits on a second case.
|
|
112
|
+
|
|
113
|
+
The unit is the paragraph and each file names its worst on each measure, which follows the depth check rather than setting a precedent. A file's flattest paragraph and its most repetitive one are named only when each crosses its checkpoint, so a file reading healthy names nothing rather than offering its least healthy paragraph as a finding.
|
|
114
|
+
|
|
115
|
+
Neither number gates and neither names a file wrong. This is a weaker claim than the one the weight checkpoints make, because a healthy range differs by surface: a catalog entry is several short sentences carrying one fact each, and a page arguing a decision is not, so one range applied across the corpus would report the surfaces that are correct. The run therefore states where the numbers came from beside them, and the counts are what a reader compares against.
|
|
116
|
+
|
|
117
|
+
Measured at `6c273324` across the corpus, 2119 paragraphs reached the floor, 165 sat at or under the spread checkpoint and 50 past the opener checkpoint. Over the 60 files carrying at least ten measured paragraphs the flat rate runs from zero to 21 percent with a median near 6, which is the spread that makes the measure worth reading. Two wiki pages sit at either end of it: a server catalog reports 19 percent, its flattest paragraph five sentences within four words of each other and three of them verbless, while a page arguing where the prose rules came from reports none.
|
|
118
|
+
|
|
119
|
+
That reading travels with the command rather than staying here. `BASELINE` in `src/markdown/structure.ts` carries the overall share, the per-file range, and the ten-paragraph floor beneath which a file's own rate says nothing, and the run prints all four in the legend beside the rate it measured. A count with no range beside it reads as a finding, and naming that a healthy range differs by surface states that a range exists rather than what it looks like. This page is toolkit-internal, so a reader running the command in a project that installed no standards would otherwise have two counts and nothing to place them against.
|
|
120
|
+
|
|
121
|
+
Two of the rules `write-human` states are deliberately not implemented. A sentence's grammatical shape and whether it carries a finite verb each need a parse rather than a match, and an imperative or a heading fragment would read as a defect under a pattern that approximated either. The verbless share is the measure closest to the reported symptom, which is exactly why shipping it wrong would discredit the two that hold. It ships when something can identify a finite verb rather than guess at one.
|
|
122
|
+
|
|
101
123
|
## Exit codes
|
|
102
124
|
|
|
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
|
|
125
|
+
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, and cadence is a distribution whose healthy range moves with the surface, so all four report under every code.
|
|
104
126
|
|
|
105
127
|
`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.
|
|
106
128
|
|
|
@@ -67,7 +67,11 @@ aitk claude skills drift 02d7b265 --json
|
|
|
67
67
|
| -------- | ---------------------------------------------------------- |
|
|
68
68
|
| `--json` | Add a machine-readable record on stdout, keeping the frame |
|
|
69
69
|
|
|
70
|
-
A skill body enters a session once and stays. Re-invoking the skill
|
|
70
|
+
A skill body enters a session once and stays. Re-invoking the skill does not re-read the file, so an edit made this session is not picked up and nothing compares the two. A session that outlives a merge touching a body it loaded keeps applying what it loaded, and a compaction carries the held copy forward with the summary it writes, which makes that exposure the age of the oldest load rather than the age of the session.
|
|
71
|
+
|
|
72
|
+
Age is one route and not the only one. A session that edits a body and then invokes that same skill later in the same run replays the pre-edit text, which is what a ship chain does whenever a branch touches a body the chain runs at the end. This verb does not reach that route, because it reads history and the edit is still uncommitted when the replay happens. Re-read a body after editing it, and do not wait for a report to name it.
|
|
73
|
+
|
|
74
|
+
Which shape the staleness takes decides whether anyone notices. A held body naming a file the branch deleted fails loudly by having nothing to read. One naming a file that still exists while saying something different resolves and reads current, so the session applies the stale rule and reports success.
|
|
71
75
|
|
|
72
76
|
The ref is required and carries no default. `HEAD` is the only value the command could supply for itself, and it answers every run with nothing moved, which is the silence this verb exists to break. A session passes the commit it started from.
|
|
73
77
|
|
|
@@ -83,6 +87,8 @@ The report names the newest commit that rewrote each body rather than the first,
|
|
|
83
87
|
|
|
84
88
|
The report says a file moved, not that a session holds a stale copy. A session cannot read its own loaded body as bytes, so the comparison runs against history and a ref older than the oldest load over-reports. That is the safe direction, since confirming a name costs one read of the body while the failure being answered is silence. Every run states the bound, including the run that names nothing.
|
|
85
89
|
|
|
90
|
+
The verb answers only when someone runs it, and a report read is not a report acted on. A session that sees a moved body in the result and does not re-read that body stands where it started. One session reached that outcome after running the verb at session start, then followed the held copy for the rest of the day.
|
|
91
|
+
|
|
86
92
|
Two cases fall outside the range. An uncommitted edit in the working tree is not history yet, so a body changed and left unstaged reports as unmoved. A target project loads the plugin from a marketplace cache with no repository behind it, where the verb refuses and names the absent history rather than reporting a clean tree.
|
|
87
93
|
|
|
88
94
|
Exit codes are `0` when history was read, whether or not a body moved, and `1` when the question could not be answered. An absent shipped corpus, an absent repository, and a ref matching no commit each refuse with their own reason on stderr. A command exiting zero on a question it never answered is the failure `.claude/ARCHITECTURE.md` already records against two skills, which is why no path here reports an empty result in place of a refusal.
|
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
|
|
|
@@ -13,6 +13,11 @@ paths:
|
|
|
13
13
|
|
|
14
14
|
- Read the skill's sibling `REQUIREMENT.md` when one exists. If the change closes no gap it states, change the requirement first or drop the change.
|
|
15
15
|
|
|
16
|
+
## After editing
|
|
17
|
+
|
|
18
|
+
- Re-read a skill body this session edited before invoking that skill again in the same session
|
|
19
|
+
- Do not read a resolved file path in a held body as evidence the body is current
|
|
20
|
+
|
|
16
21
|
## Authority
|
|
17
22
|
|
|
18
23
|
- Follow `.claude/standards/skill.md` for skill structure, frontmatter fields, invocation rules, and the shape a `REQUIREMENT.md` states. It is the single source.
|
package/package.json
CHANGED
package/src/commands/markdown.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
scanBans,
|
|
12
12
|
} from '@/markdown/scan'
|
|
13
13
|
import {
|
|
14
|
+
BASELINE,
|
|
14
15
|
CHECKPOINTS,
|
|
15
16
|
type Checkpoints,
|
|
16
17
|
measureStructure,
|
|
@@ -58,7 +59,7 @@ export function register(program: Command): void {
|
|
|
58
59
|
markdown
|
|
59
60
|
.command('audit')
|
|
60
61
|
.description(
|
|
61
|
-
'Fail on a banned character, word, or spelling, and report bullet, paragraph, and depth
|
|
62
|
+
'Fail on a banned character, word, or spelling, and report bullet, paragraph, cadence, and depth',
|
|
62
63
|
)
|
|
63
64
|
.argument(
|
|
64
65
|
'[path...]',
|
|
@@ -80,6 +81,10 @@ export function register(program: Command): void {
|
|
|
80
81
|
'depth weight are judgments a reader settles, so all three report and',
|
|
81
82
|
'none of them fails a run.',
|
|
82
83
|
'',
|
|
84
|
+
'Cadence reports the same way and carries one more caveat. Its range is',
|
|
85
|
+
'drawn from prose a person reads, so terse reference prose sits below',
|
|
86
|
+
'it correctly and a flat paragraph there is not a defect.',
|
|
87
|
+
'',
|
|
83
88
|
'Rewrite the sentence carrying a hit rather than swapping the token for',
|
|
84
89
|
'a near-synonym. A code span clears the report and is the answer only',
|
|
85
90
|
'where the token is genuinely an identifier under discussion, which is',
|
|
@@ -146,6 +151,7 @@ async function runAudit(
|
|
|
146
151
|
reportBans(reports, bans, empty)
|
|
147
152
|
reportBullets(reports, checkpoints)
|
|
148
153
|
reportParagraphs(reports, checkpoints)
|
|
154
|
+
reportCadence(reports, checkpoints)
|
|
149
155
|
reportDepth(reports, checkpoints)
|
|
150
156
|
outro()
|
|
151
157
|
|
|
@@ -168,6 +174,9 @@ async function runAudit(
|
|
|
168
174
|
paragraph: checkpoints.paragraph,
|
|
169
175
|
sentences: checkpoints.sentences,
|
|
170
176
|
renderWidth: checkpoints.renderWidth,
|
|
177
|
+
cadence: checkpoints.cadence,
|
|
178
|
+
spread: checkpoints.spread,
|
|
179
|
+
opener: checkpoints.opener,
|
|
171
180
|
},
|
|
172
181
|
entries: reports.map((report) => ({
|
|
173
182
|
path: report.rel,
|
|
@@ -176,6 +185,7 @@ async function runAudit(
|
|
|
176
185
|
longestRunLine: report.structure.longestRunLine,
|
|
177
186
|
heavyBullets: report.structure.heavyBullets,
|
|
178
187
|
heavyParagraphs: report.structure.heavyParagraphs,
|
|
188
|
+
cadence: report.structure.cadence,
|
|
179
189
|
})),
|
|
180
190
|
})}\n`,
|
|
181
191
|
)
|
|
@@ -377,6 +387,99 @@ function reportParagraphs(
|
|
|
377
387
|
)
|
|
378
388
|
}
|
|
379
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Reports the distribution rather than a verdict, which is what separates this
|
|
392
|
+
* step from the three above it.
|
|
393
|
+
*
|
|
394
|
+
* Bullet, paragraph, and depth weight each report a count against a checkpoint
|
|
395
|
+
* a reader settles. Cadence reports a count as well, and the range behind it is
|
|
396
|
+
* drawn from one surface class rather than from the corpus, so the step states
|
|
397
|
+
* where the numbers came from beside them. A reader whose file is terse
|
|
398
|
+
* reference prose is meant to read a flat paragraph as correct, and a number
|
|
399
|
+
* printed with no comparison beside it reads as a finding whatever the step is
|
|
400
|
+
* called.
|
|
401
|
+
*/
|
|
402
|
+
function reportCadence(
|
|
403
|
+
reports: readonly FileReport[],
|
|
404
|
+
checkpoints: Checkpoints,
|
|
405
|
+
): void {
|
|
406
|
+
logStep('Cadence')
|
|
407
|
+
logInfo(
|
|
408
|
+
`Paragraphs of ${checkpoints.cadence} sentences or more measure the words between their longest and shortest sentence, and the times one word opens a sentence.`,
|
|
409
|
+
)
|
|
410
|
+
logInfo(
|
|
411
|
+
`A spread of ${checkpoints.spread} words or under reads as one cadence, and a word opening more than ${checkpoints.opener} sentences is a pattern rather than a coincidence.`,
|
|
412
|
+
)
|
|
413
|
+
logInfo(
|
|
414
|
+
'Both numbers are stated under Rhythm in the write-human skill, which writes them about prose a person reads.',
|
|
415
|
+
)
|
|
416
|
+
logInfo(
|
|
417
|
+
'A shorter paragraph stays unmeasured, since a two-sentence note carries no spread worth reading.',
|
|
418
|
+
)
|
|
419
|
+
logInfo(
|
|
420
|
+
'A healthy range differs by surface, so neither number gates and neither names a file wrong. Terse reference prose sits below the range a page written for a reader sits in.',
|
|
421
|
+
)
|
|
422
|
+
logInfo(
|
|
423
|
+
`The corpus these numbers were read against measured ${BASELINE.flatShare} percent flat overall, and its files carrying ${BASELINE.floor} or more measured paragraphs ran from ${BASELINE.low} to ${BASELINE.high} percent with a median near ${BASELINE.median}. Compare a rate against that rather than against zero.`,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
const measured = reports.reduce(
|
|
427
|
+
(sum, report) => sum + report.structure.cadence.measured,
|
|
428
|
+
0,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
if (measured === 0) {
|
|
432
|
+
logInfo(
|
|
433
|
+
`No paragraph reached ${checkpoints.cadence} sentences, so nothing was measured.`,
|
|
434
|
+
)
|
|
435
|
+
return
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const flat = reports.reduce(
|
|
439
|
+
(sum, report) => sum + report.structure.cadence.flat,
|
|
440
|
+
0,
|
|
441
|
+
)
|
|
442
|
+
const repeating = reports.reduce(
|
|
443
|
+
(sum, report) => sum + report.structure.cadence.repeating,
|
|
444
|
+
0,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
logInfo(
|
|
448
|
+
`${plural(measured, 'paragraph')} measured, ${flat} at or under the spread checkpoint (${Math.round((flat / measured) * 100)} percent) and ${repeating} past the opener checkpoint.`,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
const carrying = reports
|
|
452
|
+
.filter(
|
|
453
|
+
(report) =>
|
|
454
|
+
report.structure.cadence.flattest ||
|
|
455
|
+
report.structure.cadence.mostRepeated,
|
|
456
|
+
)
|
|
457
|
+
.sort(
|
|
458
|
+
(a, b) =>
|
|
459
|
+
b.structure.cadence.flat +
|
|
460
|
+
b.structure.cadence.repeating -
|
|
461
|
+
(a.structure.cadence.flat + a.structure.cadence.repeating),
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
if (carrying.length === 0) return
|
|
465
|
+
|
|
466
|
+
pipeOutput(
|
|
467
|
+
carrying
|
|
468
|
+
.map((report) => {
|
|
469
|
+
const { flattest, mostRepeated } = report.structure.cadence
|
|
470
|
+
const lines = [
|
|
471
|
+
flattest &&
|
|
472
|
+
` :${flattest.line} ${plural(flattest.sentences, 'sentence')}, spread ${plural(flattest.spread, 'word')}`,
|
|
473
|
+
mostRepeated &&
|
|
474
|
+
` :${mostRepeated.line} ${plural(mostRepeated.sentences, 'sentence')}, "${mostRepeated.opener}" opens ${mostRepeated.repeats}`,
|
|
475
|
+
].filter(Boolean)
|
|
476
|
+
|
|
477
|
+
return `${report.rel}\n${lines.join('\n')}`
|
|
478
|
+
})
|
|
479
|
+
.join('\n'),
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
|
|
380
483
|
/**
|
|
381
484
|
* Names the render width and the blank-line convention on every run.
|
|
382
485
|
*
|
|
@@ -33,6 +33,14 @@ const SENTENCE_END = /[.!?]["'’”)\]]*(?=\s+(?:["'“(\[]*[A-Z]|`)|\s*$)/g
|
|
|
33
33
|
* degraded a number and the report had to carry a legend saying which one. The
|
|
34
34
|
* standard still states every number for a reader, and moving one is an edit to
|
|
35
35
|
* both.
|
|
36
|
+
*
|
|
37
|
+
* The last three come from the `## Rhythm` section of the `write-human` skill
|
|
38
|
+
* rather than from a standard. That skill states what good rhythm is and this
|
|
39
|
+
* measures against the statement, so the numbers are read off the sentences
|
|
40
|
+
* that already carry them: a longest and shortest sentence within roughly five
|
|
41
|
+
* words of each other is one cadence, an opening word repeating twice is
|
|
42
|
+
* coincidence and three times is a pattern, and both rules are written about a
|
|
43
|
+
* paragraph, which needs a third sentence before either says anything.
|
|
36
44
|
*/
|
|
37
45
|
export const CHECKPOINTS = {
|
|
38
46
|
run: 40,
|
|
@@ -41,6 +49,37 @@ export const CHECKPOINTS = {
|
|
|
41
49
|
paragraph: 700,
|
|
42
50
|
sentences: 4,
|
|
43
51
|
renderWidth: 80,
|
|
52
|
+
cadence: 3,
|
|
53
|
+
spread: 5,
|
|
54
|
+
opener: 2,
|
|
55
|
+
} as const
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The reading a cadence rate is compared against, rather than a checkpoint.
|
|
59
|
+
*
|
|
60
|
+
* A count with nothing beside it reads as a finding, and the two counts the
|
|
61
|
+
* cadence step reports have no range a reader can place them in: twelve flat
|
|
62
|
+
* paragraphs out of forty says the same as two without one. Naming that a
|
|
63
|
+
* healthy range differs by surface states that a range exists rather than what
|
|
64
|
+
* it looks like, so the reading travels with the command.
|
|
65
|
+
*
|
|
66
|
+
* This is an observation rather than a rule, which is why it sits apart from
|
|
67
|
+
* `CHECKPOINTS`. Nothing compares a run against it and no exit code reads it. A
|
|
68
|
+
* project whose corpus is entirely terse reference prose is expected to sit
|
|
69
|
+
* above the high end, and that is the measure working.
|
|
70
|
+
*
|
|
71
|
+
* Read across 483 markdown files at `6c273324` on 2026-08-20. A number here
|
|
72
|
+
* goes stale against the corpus it describes with nothing comparing the two, so
|
|
73
|
+
* re-measure before moving one.
|
|
74
|
+
*/
|
|
75
|
+
export const BASELINE = {
|
|
76
|
+
/** Share of every measured paragraph sitting at or under the spread checkpoint. */
|
|
77
|
+
flatShare: 8,
|
|
78
|
+
/** Measured paragraphs a file needs before its own rate means anything. */
|
|
79
|
+
floor: 10,
|
|
80
|
+
low: 0,
|
|
81
|
+
median: 6,
|
|
82
|
+
high: 21,
|
|
44
83
|
} as const
|
|
45
84
|
|
|
46
85
|
/**
|
|
@@ -58,6 +97,12 @@ export interface Checkpoints {
|
|
|
58
97
|
readonly paragraph: number
|
|
59
98
|
readonly sentences: number
|
|
60
99
|
readonly renderWidth: number
|
|
100
|
+
/** Sentences a paragraph needs before its cadence is measured at all. */
|
|
101
|
+
readonly cadence: number
|
|
102
|
+
/** Words between the longest and shortest sentence, at or under which the paragraph reads as one cadence. */
|
|
103
|
+
readonly spread: number
|
|
104
|
+
/** Times one opening word may open a sentence in a paragraph before it is a pattern. */
|
|
105
|
+
readonly opener: number
|
|
61
106
|
}
|
|
62
107
|
|
|
63
108
|
export interface BulletFinding {
|
|
@@ -72,6 +117,47 @@ export interface ParagraphFinding {
|
|
|
72
117
|
readonly characters: number
|
|
73
118
|
}
|
|
74
119
|
|
|
120
|
+
/**
|
|
121
|
+
* One measured paragraph, carrying both cadence numbers rather than one.
|
|
122
|
+
*
|
|
123
|
+
* A paragraph reported for a narrow spread is usually worth reading for its
|
|
124
|
+
* openers too, and splitting the two into separate finding types would name the
|
|
125
|
+
* same line twice with half the picture on each.
|
|
126
|
+
*/
|
|
127
|
+
export interface CadenceFinding {
|
|
128
|
+
readonly line: number
|
|
129
|
+
readonly sentences: number
|
|
130
|
+
/** Words between the longest and shortest sentence. */
|
|
131
|
+
readonly spread: number
|
|
132
|
+
/** Times the most repeated opening word opens a sentence here. */
|
|
133
|
+
readonly repeats: number
|
|
134
|
+
/** The opening word behind `repeats`, lowercased and stripped of punctuation. */
|
|
135
|
+
readonly opener: string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A file's cadence distribution, reported rather than listed finding by finding.
|
|
140
|
+
*
|
|
141
|
+
* Both numbers are advisory in a way even the weight checkpoints are not. A
|
|
142
|
+
* healthy range differs by surface, so terse reference prose and a page written
|
|
143
|
+
* for a reader sit at different spreads and one range applied across the corpus
|
|
144
|
+
* would report the surfaces that are correct. The counts are what a reader
|
|
145
|
+
* compares against the stated range, and the two worst paragraphs are where
|
|
146
|
+
* they open the file.
|
|
147
|
+
*/
|
|
148
|
+
export interface CadenceReport {
|
|
149
|
+
/** Paragraphs carrying at least the floor sentence count. */
|
|
150
|
+
readonly measured: number
|
|
151
|
+
/** Measured paragraphs at or under the spread checkpoint. */
|
|
152
|
+
readonly flat: number
|
|
153
|
+
/** Measured paragraphs past the opener checkpoint. */
|
|
154
|
+
readonly repeating: number
|
|
155
|
+
/** Narrowest paragraph among the flat ones, or undefined when none is flat. */
|
|
156
|
+
readonly flattest: CadenceFinding | undefined
|
|
157
|
+
/** Most repetitive paragraph among the repeating ones, or undefined when none repeats. */
|
|
158
|
+
readonly mostRepeated: CadenceFinding | undefined
|
|
159
|
+
}
|
|
160
|
+
|
|
75
161
|
export interface StructureReport {
|
|
76
162
|
readonly rel: string
|
|
77
163
|
/** Rendered lines at the render width, not source lines. */
|
|
@@ -80,6 +166,7 @@ export interface StructureReport {
|
|
|
80
166
|
readonly longestRunLine: number
|
|
81
167
|
readonly heavyBullets: readonly BulletFinding[]
|
|
82
168
|
readonly heavyParagraphs: readonly ParagraphFinding[]
|
|
169
|
+
readonly cadence: CadenceReport
|
|
83
170
|
}
|
|
84
171
|
|
|
85
172
|
/**
|
|
@@ -275,48 +362,46 @@ export function heavyBullets(
|
|
|
275
362
|
return findings
|
|
276
363
|
}
|
|
277
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Splits a paragraph into the sentences terminal punctuation closes.
|
|
367
|
+
*
|
|
368
|
+
* A trailing span no punctuation closes is dropped rather than returned, which
|
|
369
|
+
* keeps this and the count below one definition. The pattern already matches a
|
|
370
|
+
* paragraph's final period through its end-of-text alternative, so the only
|
|
371
|
+
* span this drops is a paragraph genuinely ending without terminal punctuation,
|
|
372
|
+
* which is a fragment rather than a sentence to either measure.
|
|
373
|
+
*/
|
|
374
|
+
function splitSentences(text: string): string[] {
|
|
375
|
+
const sentences: string[] = []
|
|
376
|
+
let start = 0
|
|
377
|
+
|
|
378
|
+
for (const match of text.matchAll(SENTENCE_END)) {
|
|
379
|
+
const end = match.index + match[0].length
|
|
380
|
+
sentences.push(text.slice(start, end).trim())
|
|
381
|
+
start = end
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return sentences
|
|
385
|
+
}
|
|
386
|
+
|
|
278
387
|
function countSentences(text: string): number {
|
|
279
|
-
return
|
|
388
|
+
return splitSentences(text).length
|
|
280
389
|
}
|
|
281
390
|
|
|
282
391
|
/**
|
|
283
|
-
*
|
|
392
|
+
* Splits the body into its prose paragraphs, each a run of consecutive lines.
|
|
284
393
|
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* the sentence cap would find no weight rule to read at all.
|
|
290
|
-
*
|
|
291
|
-
* The two numbers coincide today because paragraph and bullet weight measure
|
|
292
|
-
* one population, sharing a median near 170 characters with no gap behind
|
|
293
|
-
* either candidate. They are separate checkpoints regardless, so either moves
|
|
294
|
-
* without dragging the other.
|
|
295
|
-
*
|
|
296
|
-
* A paragraph is a run of consecutive prose lines. A heading, a list item, a
|
|
297
|
-
* table row, a blockquote, a blank line, and a fence each end one, so a bullet
|
|
298
|
-
* is measured by `heavyBullets` alone and never twice.
|
|
394
|
+
* A heading, a list item, a table row, a blockquote, a blank line, and a fence
|
|
395
|
+
* each end one, so a bullet is measured by `heavyBullets` alone and never
|
|
396
|
+
* twice. Both paragraph measures walk this rather than one each, since two
|
|
397
|
+
* walks deciding what a paragraph is would drift apart.
|
|
299
398
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
checkpoints: Checkpoints,
|
|
303
|
-
): ParagraphFinding[] {
|
|
304
|
-
const findings: ParagraphFinding[] = []
|
|
399
|
+
function paragraphBlocks(lines: readonly BodyLine[]): BodyLine[][] {
|
|
400
|
+
const blocks: BodyLine[][] = []
|
|
305
401
|
let block: BodyLine[] = []
|
|
306
402
|
|
|
307
403
|
const close = (): void => {
|
|
308
|
-
if (block.length > 0)
|
|
309
|
-
const text = block.map((line) => line.text.trim()).join(' ')
|
|
310
|
-
const sentences = countSentences(text)
|
|
311
|
-
const characters = visibleText(text).length
|
|
312
|
-
|
|
313
|
-
if (
|
|
314
|
-
sentences > checkpoints.sentences ||
|
|
315
|
-
characters > checkpoints.paragraph
|
|
316
|
-
) {
|
|
317
|
-
findings.push({ line: block[0].number, sentences, characters })
|
|
318
|
-
}
|
|
319
|
-
}
|
|
404
|
+
if (block.length > 0) blocks.push(block)
|
|
320
405
|
block = []
|
|
321
406
|
}
|
|
322
407
|
|
|
@@ -340,9 +425,168 @@ export function heavyParagraphs(
|
|
|
340
425
|
|
|
341
426
|
close()
|
|
342
427
|
|
|
428
|
+
return blocks
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Folds a paragraph's source lines into the one line they would have wrapped from. */
|
|
432
|
+
function paragraphText(block: readonly BodyLine[]): string {
|
|
433
|
+
return block.map((line) => line.text.trim()).join(' ')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Finds the prose paragraphs past either half of the standard's checkpoint.
|
|
438
|
+
*
|
|
439
|
+
* Both halves are stated in the standard and both are read from it. The weight
|
|
440
|
+
* half was added there rather than borrowed from the bullet checkpoint, which
|
|
441
|
+
* governs a different construct: one number feeding both would move the
|
|
442
|
+
* paragraph rule whenever the bullet rule was changed, and an author reading
|
|
443
|
+
* the sentence cap would find no weight rule to read at all.
|
|
444
|
+
*
|
|
445
|
+
* The two numbers coincide today because paragraph and bullet weight measure
|
|
446
|
+
* one population, sharing a median near 170 characters with no gap behind
|
|
447
|
+
* either candidate. They are separate checkpoints regardless, so either moves
|
|
448
|
+
* without dragging the other.
|
|
449
|
+
*/
|
|
450
|
+
export function heavyParagraphs(
|
|
451
|
+
lines: readonly BodyLine[],
|
|
452
|
+
checkpoints: Checkpoints,
|
|
453
|
+
): ParagraphFinding[] {
|
|
454
|
+
const findings: ParagraphFinding[] = []
|
|
455
|
+
|
|
456
|
+
for (const block of paragraphBlocks(lines)) {
|
|
457
|
+
const text = paragraphText(block)
|
|
458
|
+
const sentences = countSentences(text)
|
|
459
|
+
const characters = visibleText(text).length
|
|
460
|
+
|
|
461
|
+
if (
|
|
462
|
+
sentences > checkpoints.sentences ||
|
|
463
|
+
characters > checkpoints.paragraph
|
|
464
|
+
) {
|
|
465
|
+
findings.push({ line: block[0].number, sentences, characters })
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
343
469
|
return findings
|
|
344
470
|
}
|
|
345
471
|
|
|
472
|
+
/** Punctuation either side of a word, so an opener is compared on its letters. */
|
|
473
|
+
const WORD_EDGE = /^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/gu
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The word a sentence opens on, lowercased and stripped of punctuation.
|
|
477
|
+
*
|
|
478
|
+
* A sentence opening on a code span yields the command inside it rather than a
|
|
479
|
+
* backtick, which is the word a reader hears. Casing is dropped because an
|
|
480
|
+
* opener repeating is a repetition whether or not one of the two sits mid-list
|
|
481
|
+
* and lost its capital.
|
|
482
|
+
*/
|
|
483
|
+
function openingWord(sentence: string): string {
|
|
484
|
+
const [first = ''] = sentence.split(/\s+/)
|
|
485
|
+
return first.replace(WORD_EDGE, '').toLowerCase()
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Measures one paragraph's spread and its most repeated opener.
|
|
490
|
+
*
|
|
491
|
+
* Words are counted off the text a reader is shown, so a link contributes its
|
|
492
|
+
* anchor text and a destination contributes nothing. The sentence boundaries do
|
|
493
|
+
* not move with that masking, since the pattern requires whitespace after the
|
|
494
|
+
* terminal punctuation and no destination or autolink carries any.
|
|
495
|
+
*/
|
|
496
|
+
function measureParagraph(block: readonly BodyLine[]): CadenceFinding {
|
|
497
|
+
const sentences = splitSentences(visibleText(paragraphText(block)))
|
|
498
|
+
const lengths = sentences.map(
|
|
499
|
+
(sentence) => sentence.split(/\s+/).filter(Boolean).length,
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
const counts = new Map<string, number>()
|
|
503
|
+
for (const sentence of sentences) {
|
|
504
|
+
const word = openingWord(sentence)
|
|
505
|
+
if (word !== '') counts.set(word, (counts.get(word) ?? 0) + 1)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let opener = ''
|
|
509
|
+
let repeats = 0
|
|
510
|
+
for (const [word, count] of counts) {
|
|
511
|
+
if (count > repeats) {
|
|
512
|
+
opener = word
|
|
513
|
+
repeats = count
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// A paragraph closing no sentence at all has no spread rather than a
|
|
518
|
+
// negative one, and the floor drops it before either number is read.
|
|
519
|
+
const spread =
|
|
520
|
+
lengths.length === 0 ? 0 : Math.max(...lengths) - Math.min(...lengths)
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
line: block[0].number,
|
|
524
|
+
sentences: sentences.length,
|
|
525
|
+
spread,
|
|
526
|
+
repeats,
|
|
527
|
+
opener,
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Reports how a file's prose is distributed across sentence length and opening
|
|
533
|
+
* word, which is the layer the ban sets cannot reach.
|
|
534
|
+
*
|
|
535
|
+
* A banned-word list expresses negatives and every symptom this answers is the
|
|
536
|
+
* absence of something, so no addition to that set catches a paragraph whose
|
|
537
|
+
* sentences are all one length. The measure stops at what is countable. A
|
|
538
|
+
* sentence's grammatical shape and whether it carries a finite verb are the two
|
|
539
|
+
* rules `write-human` states that this does not implement, because identifying
|
|
540
|
+
* either needs a parse rather than a match, and an imperative or a heading
|
|
541
|
+
* fragment would read as a defect. Reporting them wrong is worse than not
|
|
542
|
+
* reporting them, since they name the exact failure this exists to measure.
|
|
543
|
+
*
|
|
544
|
+
* A paragraph under the floor is skipped rather than scored. A two-sentence
|
|
545
|
+
* configuration note has no spread worth reading, and the opener rule is
|
|
546
|
+
* written about a third sentence turning a coincidence into a pattern, so
|
|
547
|
+
* neither measure says anything before the floor is reached.
|
|
548
|
+
*/
|
|
549
|
+
export function measureCadence(
|
|
550
|
+
lines: readonly BodyLine[],
|
|
551
|
+
checkpoints: Checkpoints,
|
|
552
|
+
): CadenceReport {
|
|
553
|
+
const measured = paragraphBlocks(lines)
|
|
554
|
+
.map(measureParagraph)
|
|
555
|
+
.filter((finding) => finding.sentences >= checkpoints.cadence)
|
|
556
|
+
|
|
557
|
+
const flat = measured.filter(
|
|
558
|
+
(finding) => finding.spread <= checkpoints.spread,
|
|
559
|
+
)
|
|
560
|
+
const repeating = measured.filter(
|
|
561
|
+
(finding) => finding.repeats > checkpoints.opener,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
return {
|
|
565
|
+
measured: measured.length,
|
|
566
|
+
flat: flat.length,
|
|
567
|
+
repeating: repeating.length,
|
|
568
|
+
flattest: worst(flat, (finding) => -finding.spread),
|
|
569
|
+
mostRepeated: worst(repeating, (finding) => finding.repeats),
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* The file's worst paragraph on one measure, earliest line breaking a tie.
|
|
575
|
+
*
|
|
576
|
+
* Only paragraphs already past their checkpoint are passed in, so a file
|
|
577
|
+
* reading healthy names nothing rather than naming its least healthy paragraph,
|
|
578
|
+
* which a reader would take for a finding.
|
|
579
|
+
*/
|
|
580
|
+
function worst(
|
|
581
|
+
findings: readonly CadenceFinding[],
|
|
582
|
+
rank: (finding: CadenceFinding) => number,
|
|
583
|
+
): CadenceFinding | undefined {
|
|
584
|
+
return findings.reduce<CadenceFinding | undefined>(
|
|
585
|
+
(held, finding) => (!held || rank(finding) > rank(held) ? finding : held),
|
|
586
|
+
undefined,
|
|
587
|
+
)
|
|
588
|
+
}
|
|
589
|
+
|
|
346
590
|
export function measureStructure(
|
|
347
591
|
rel: string,
|
|
348
592
|
lines: readonly BodyLine[],
|
|
@@ -356,5 +600,6 @@ export function measureStructure(
|
|
|
356
600
|
longestRunLine: run.line,
|
|
357
601
|
heavyBullets: heavyBullets(lines, checkpoints),
|
|
358
602
|
heavyParagraphs: heavyParagraphs(lines, checkpoints),
|
|
603
|
+
cadence: measureCadence(lines, checkpoints),
|
|
359
604
|
}
|
|
360
605
|
}
|
|
@@ -14,7 +14,7 @@ Governs the product-scope document at `.claude/REQUIREMENTS.md`: problem, goals,
|
|
|
14
14
|
Does not govern:
|
|
15
15
|
|
|
16
16
|
- Rationale for a technical choice: `architecture.md`
|
|
17
|
-
-
|
|
17
|
+
- Execution order across the work the scope generates: `tasks.md`
|
|
18
18
|
- Per-domain structure and narrative: `context.md`
|
|
19
19
|
|
|
20
20
|
## What goes in
|
|
@@ -39,7 +39,7 @@ Use `## Problem`, `## Goals`, `## Non-goals`, `## MVP features`, `## Tech stack`
|
|
|
39
39
|
|
|
40
40
|
The MVP list is a historical record of the original scope. Once those features ship it stays as written. Do not renumber it, do not append to it, and do not annotate entries with status. A reader telling the original scope apart from what followed depends on the first one staying legible.
|
|
41
41
|
|
|
42
|
-
Later scope arrives as a new section rather than as an extension of the MVP list. Name the section for what it delivers and state its entries as outcomes, the same way the goals are stated.
|
|
42
|
+
Later scope arrives as a new section rather than as an extension of the MVP list. Name the section for what it delivers and state its entries as outcomes, the same way the goals are stated. Nothing sequences either list into versions. Work reaches the board as discrete tasks under `tasks.md`, which orders them by readiness, so a section here states what is wanted and never when it lands.
|
|
43
43
|
|
|
44
44
|
## Distribution
|
|
45
45
|
|
package/standards/session.md
CHANGED
|
@@ -57,7 +57,7 @@ Add a section only for content that fits none of the three and would otherwise b
|
|
|
57
57
|
## Writing one
|
|
58
58
|
|
|
59
59
|
1. Capture what the session learned first, so the map cites what was written instead of restating the same lesson in prose.
|
|
60
|
-
2. Run `aitk claude skills drift <the commit this session started from>` and record what it names under `## Standing cautions`. A skill body enters a session once and re-invoking the skill
|
|
60
|
+
2. Run `aitk claude skills drift <the commit this session started from>` and record what it names under `## Standing cautions`. A skill body enters a session once and re-invoking the skill does not re-read the file, so the drift is worst at exactly this moment and a name here is a body the session has been following out of date. Recording a name is not acting on it, and the verb misses a body this session edited and has not committed. This step belongs to session length rather than to any role, so every writer runs it, and a refusal names the boundary of what the verb can read rather than a fault.
|
|
61
61
|
3. Recover that commit from how long the session has been running with `git log -1 --format=%H --before='<duration> ago'`, rounding the duration up rather than down. Nothing on the machine records it. A ref older than the oldest load over-reports, and confirming a name costs one read of the body, so the generous end is the safe one and a guess at the exact commit is not worth making.
|
|
62
62
|
4. Write only what a compaction destroys and no other artifact already carries. The board holds the ordering and what each task waits on, a task file holds its own findings, and a measurement folder holds its track.
|
|
63
63
|
5. Cite a commit, a task, or a file and line for every claim, so the next session can tell a read from a recall.
|
package/standards/tasks.md
CHANGED
|
@@ -19,7 +19,6 @@ Does not govern:
|
|
|
19
19
|
|
|
20
20
|
- The plan file a task cites, its sections, and its answer contract: `plan.md`
|
|
21
21
|
- Phase-label format and which surfaces a label may appear on: `versioning.md`
|
|
22
|
-
- Sequencing across versions and why the order is what it is: `roadmap.md`
|
|
23
22
|
- Architectural reasoning that outlives a task: `architecture.md`
|
|
24
23
|
- The pre-compaction handoff sitting in the folder, its filename and its sections: `session.md`
|
|
25
24
|
- When a project opens a task at all, which is project policy rather than a shape rule
|
|
@@ -52,7 +51,9 @@ The `claude-tasks` skill creates and archives task files. `claude-docs` marks ou
|
|
|
52
51
|
|
|
53
52
|
## Ordering
|
|
54
53
|
|
|
55
|
-
`priority.md` carries execution order and what each task is waiting on. The generated index sorts by filename and says nothing about order, so without this file board state gets reconstructed by hand every session. Why
|
|
54
|
+
`priority.md` carries execution order and what each task is waiting on. The generated index sorts by filename and says nothing about order, so without this file board state gets reconstructed by hand every session. Why a row sits where it does inside its group is stated on the row itself, in the column that already carries what the task is waiting on, one line per row.
|
|
55
|
+
|
|
56
|
+
That cell is the only home sequencing rationale has. Rationale spanning several rows, why one group of work runs before another, is carried by nothing and reaches a later session only through whoever remembers it. Naming the gap is deliberate: a second document holding it would be the version-sequencing surface this board replaced, and a row already states what it waits on, which is the part of the reasoning a reader acts on.
|
|
56
57
|
|
|
57
58
|
Group tasks by readiness rather than by status, one row per task, under the columns each group fixes below. Keep it to links and blockers: tables, plus at most one sentence per section. A paragraph in `priority.md` is a defect whatever it says. Stating the shape this way is what lets a single diff fail, since a size cap only trips after the fact and every addition looks defensible on its own.
|
|
58
59
|
|
|
@@ -240,7 +241,7 @@ The line is what lets a merge close its own task. Every merge on `main` is a squ
|
|
|
240
241
|
- Architectural reasoning that outlives the task. A finding explains why this task is shaped as it is. A decision the system keeps after the task closes belongs in `.claude/ARCHITECTURE.md`.
|
|
241
242
|
- Narrative of the session that produced the task. A finding states what constrains the task, so what was probed, what it cost, and who decided belongs in the groundwork folder the `Groundwork:` line names. A task with no groundwork folder cuts the narrative rather than relocating it, since the board is not the fallback destination for it.
|
|
242
243
|
- "In progress" or "Blocked" headings. Note status inline on the outcome instead.
|
|
243
|
-
- Sequencing rationale or which version is active. Why this task is planned before its neighbors goes on its row in `priority.md`, in the cell that already carries what it is waiting on.
|
|
244
|
+
- Sequencing rationale or which version is active. Why this task is planned before its neighbors goes on its row in `priority.md`, in the cell that already carries what it is waiting on. Rationale wider than one row has no home at all, so cut it rather than filing it here.
|
|
244
245
|
|
|
245
246
|
## Archiving
|
|
246
247
|
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: claude-roadmap
|
|
3
|
-
description: Why versions are sequenced from the MVP list alone, what makes a version a usable increment, and the lifecycle gate that stops a second pass
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Claude roadmap requirement
|
|
7
|
-
|
|
8
|
-
## Gap
|
|
9
|
-
|
|
10
|
-
Without this skill, versions are invented from a sense of what should come next rather than sequenced from the scope the requirements already fixed. Each one reads as a milestone nobody can use, because the ordering follows what feels foundational instead of what a user can then do, and a version that de-risks nothing sits ahead of the subsystem the whole plan rests on. The roadmap then drifts into task-level steps, which duplicates the plan folder and goes stale the first time a file moves.
|
|
11
|
-
|
|
12
|
-
An update is where the file quietly breaks. Rewriting rows that never changed hides the one line that moved, so a reader diffing the roadmap learns nothing from it. And a project whose MVP already shipped gets its later scope sequenced here, which puts a fresh requirements pass's work into a file that only ever sequenced the MVP.
|
|
13
|
-
|
|
14
|
-
## Must
|
|
15
|
-
|
|
16
|
-
- Sequence from the MVP list in the requirements file and name it as the source
|
|
17
|
-
- Make every version a usable increment, stated as what the user can then do
|
|
18
|
-
- Order by dependency and by de-risking, placing an unproven subsystem inside the version that first needs it
|
|
19
|
-
- Preserve rows that still hold when updating, resequencing or splitting only where scope shifted
|
|
20
|
-
- Follow the reference for the document shape and version format rather than inventing one
|
|
21
|
-
- Report which versions changed on an update
|
|
22
|
-
|
|
23
|
-
## Must not
|
|
24
|
-
|
|
25
|
-
- Break a version into task-level steps or a file list
|
|
26
|
-
- Rewrite a row that did not change
|
|
27
|
-
- Sequence a later scope section without an explicit override from the caller
|
|
28
|
-
- Stage or commit the file, which is tracked but belongs to the git skills
|
|
29
|
-
|
|
30
|
-
## Guards
|
|
31
|
-
|
|
32
|
-
- Requirements file absent or carrying no MVP features: stop, because there is nothing to sequence
|
|
33
|
-
- A later scope section present after the MVP list: stop and name the override, since that scope belongs to a fresh requirements pass
|
|
34
|
-
- Neither copy of the requirements standard resolves: draft without the lifecycle gate. Refusing on a rule that could not be read stops more than it protects.
|
|
35
|
-
|
|
36
|
-
## Out of scope
|
|
37
|
-
|
|
38
|
-
- Task-level steps and file lists, which `claude-feature` and the task board own
|
|
39
|
-
- Writing the requirements being sequenced
|
|
40
|
-
- Committing the file, which the git skills own
|
|
41
|
-
- Deciding what to build next once the MVP list has shipped
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: claude-roadmap
|
|
3
|
-
description: Drafts or updates `.claude/ROADMAP.md` by sequencing the MVP scope from `.claude/REQUIREMENTS.md` into ordered versions, each a usable increment. Use when asked to "draft a roadmap", "build the roadmap", "sequence the versions", "plan the roadmap", or "update the roadmap". Do NOT break versions into task-level steps. That is `claude-feature` and `.claude/tasks/`.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Claude roadmap
|
|
7
|
-
|
|
8
|
-
## Guards
|
|
9
|
-
|
|
10
|
-
- If `.claude/REQUIREMENTS.md` is absent or has no MVP features, stop: `❌ No requirements to sequence. Draft .claude/REQUIREMENTS.md first.`
|
|
11
|
-
- If the requirements file carries a later scope section, stop per Lifecycle gate below
|
|
12
|
-
- Sequence versions and their outcomes only. Do not produce file lists or task steps.
|
|
13
|
-
|
|
14
|
-
## Lifecycle gate
|
|
15
|
-
|
|
16
|
-
A roadmap sequences the MVP list alone, so a later scope section means that list has shipped and a fresh requirements pass owns what follows. The rule is the Lifecycle section of `.claude/standards/requirements.md`, falling back to `${CLAUDE_SKILL_DIR}/../../standards/requirements.md` when the project has no copy.
|
|
17
|
-
|
|
18
|
-
- A later scope section is a `##` heading after `## MVP features` that `.claude/standards/requirements.md` names nowhere, neither among the six it fixes nor as the conditional `## Distribution`
|
|
19
|
-
- On finding one, stop: `❌ MVP scope already sequenced. ## <section> is later scope, which a fresh requirements pass sequences rather than this roadmap. Say to sequence it anyway to override.`
|
|
20
|
-
- Draft without the gate when neither copy of the standard resolves. Refusing on a rule that could not be read stops more than it protects.
|
|
21
|
-
- When the caller overrides, sequence the later scope section's entries as versions and proceed
|
|
22
|
-
|
|
23
|
-
## Step 1: read context
|
|
24
|
-
|
|
25
|
-
Read these in parallel from the project root, skipping any that do not exist:
|
|
26
|
-
|
|
27
|
-
- `.claude/REQUIREMENTS.md`: the MVP feature scope to sequence
|
|
28
|
-
- `.claude/ROADMAP.md`: the existing roadmap, when updating rather than drafting
|
|
29
|
-
- `.claude/tasks/index.md`: what is in flight, to mark the active version
|
|
30
|
-
- `.claude/ARCHITECTURE.md`: technical dependencies that constrain ordering
|
|
31
|
-
|
|
32
|
-
Follow `${CLAUDE_SKILL_DIR}/references/roadmap.md` for the doc shape, sections, and version format.
|
|
33
|
-
|
|
34
|
-
## Step 2: sequence
|
|
35
|
-
|
|
36
|
-
Group the MVP features into versions, each a usable increment that stands on its own. Order the versions by two forces:
|
|
37
|
-
|
|
38
|
-
- Dependency: a version that produces data or a contract another version consumes comes first.
|
|
39
|
-
- De-risking: a version that de-risks an unproven subsystem comes early, inside the version that first needs it.
|
|
40
|
-
|
|
41
|
-
For each version, state the observable outcome as what the user can then do, list the features it groups by name, and note what it depends on and why.
|
|
42
|
-
|
|
43
|
-
When updating an existing roadmap, preserve rows that still hold. Resequence, add, or split only where scope shifted. Do not rewrite rows that did not change.
|
|
44
|
-
|
|
45
|
-
## Step 3: write
|
|
46
|
-
|
|
47
|
-
Write the roadmap to `.claude/ROADMAP.md` at the project root, following `${CLAUDE_SKILL_DIR}/references/roadmap.md`. Create the file when absent.
|
|
48
|
-
|
|
49
|
-
Unlike `.claude/plans/` and `.claude/tasks/`, `.claude/ROADMAP.md` is committed. Do not stage or commit it here. Leave staging to the git skills.
|
|
50
|
-
|
|
51
|
-
## Step 4: output
|
|
52
|
-
|
|
53
|
-
```markdown
|
|
54
|
-
📝 Wrote .claude/ROADMAP.md
|
|
55
|
-
|
|
56
|
-
<N> versions sequenced. Active: vX.Y.
|
|
57
|
-
|
|
58
|
-
Next: /claude-feature for the first version's opening feature.
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
When updating, replace the first line with `📝 Updated .claude/ROADMAP.md` and name which versions changed.
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Roadmap reference
|
|
3
|
-
description: Shape and content rules for .claude/ROADMAP.md
|
|
4
|
-
consumers: claude-roadmap
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Roadmap reference
|
|
8
|
-
|
|
9
|
-
Applies to `.claude/ROADMAP.md`. Sequences the scope from `.claude/REQUIREMENTS.md` into ordered versions, each a usable increment. Update when the sequence changes or a version ships. The roadmap is committed because the sequence and its rationale are shared strategic truth a fresh session needs, unlike `.claude/tasks/`, which is ephemeral scratch.
|
|
10
|
-
|
|
11
|
-
The roadmap is one scannable table. That is what keeps it distinct from `.claude/tasks/`: the roadmap is an overview read at a glance, while tasks are worked one file at a time.
|
|
12
|
-
|
|
13
|
-
## Scope
|
|
14
|
-
|
|
15
|
-
Governs the sequencing document at `.claude/ROADMAP.md`: the version table, its columns, and its lifecycle.
|
|
16
|
-
|
|
17
|
-
Does not govern:
|
|
18
|
-
|
|
19
|
-
- What the scope is, which the roadmap sequences rather than defines: `requirements.md`
|
|
20
|
-
- Task files, outcomes, and board state: `tasks.md`
|
|
21
|
-
- Phase-label format and semver discipline: `versioning.md`
|
|
22
|
-
- Rationale for a technical choice: `architecture.md`
|
|
23
|
-
|
|
24
|
-
## What goes in
|
|
25
|
-
|
|
26
|
-
- One row per version, ordered top to bottom by sequence
|
|
27
|
-
- A status per version: active, next in line, or deferred
|
|
28
|
-
- The observable outcome each version delivers, as a short phrase
|
|
29
|
-
- The features a version groups, by name, drawn from the MVP list in `.claude/REQUIREMENTS.md`
|
|
30
|
-
- The version each row depends on, and a short why
|
|
31
|
-
|
|
32
|
-
## What does not go in
|
|
33
|
-
|
|
34
|
-
- Task breakdown, checkboxes, or per-feature file lists
|
|
35
|
-
- Implementation detail, API names, or component references
|
|
36
|
-
- Sentence-long cells. Keep each cell terse so the table stays scannable.
|
|
37
|
-
|
|
38
|
-
## Format
|
|
39
|
-
|
|
40
|
-
One table. Columns, in order:
|
|
41
|
-
|
|
42
|
-
- `Version`: the `vX.Y` phase label. Phase-label format is governed by `standards/versioning.md`.
|
|
43
|
-
- `Status`: `Now` for the active version, `Next` for sequenced upcoming versions, `Later` for deferred themes.
|
|
44
|
-
- `Outcome`: what the user can do once this version ships, as a phrase.
|
|
45
|
-
- `Features`: the feature names this version groups, comma-separated.
|
|
46
|
-
- `Depends on`: the version this one needs and a short why, or `none` when independent.
|
|
47
|
-
|
|
48
|
-
## Template
|
|
49
|
-
|
|
50
|
-
```markdown
|
|
51
|
-
# Roadmap
|
|
52
|
-
|
|
53
|
-
| Version | Status | Outcome | Features | Depends on |
|
|
54
|
-
| ------- | ------ | -------- | -------------------- | ------------------ |
|
|
55
|
-
| v0.1 | Now | <phrase> | <feature>, <feature> | none |
|
|
56
|
-
| v0.2 | Next | <phrase> | <feature> | v0.1, for <reason> |
|
|
57
|
-
| v0.3 | Later | <phrase> | <feature> | none |
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Lifecycle
|
|
61
|
-
|
|
62
|
-
The roadmap sequences a finite, known scope. When the last version ships, the scope is exhausted and the project cuts a semver release per `standards/versioning.md`. Later work arrives as discrete items, a fresh requirements pass or tracked issues, rather than extending the original roadmap without end.
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Roadmap reference
|
|
3
|
-
description: Shape and content rules for .claude/ROADMAP.md
|
|
4
|
-
consumers: claude-roadmap
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Roadmap reference
|
|
8
|
-
|
|
9
|
-
Applies to `.claude/ROADMAP.md`. Sequences the scope from `.claude/REQUIREMENTS.md` into ordered versions, each a usable increment. Update when the sequence changes or a version ships. The roadmap is committed because the sequence and its rationale are shared strategic truth a fresh session needs, unlike `.claude/tasks/`, which is ephemeral scratch.
|
|
10
|
-
|
|
11
|
-
The roadmap is one scannable table. That is what keeps it distinct from `.claude/tasks/`: the roadmap is an overview read at a glance, while tasks are worked one file at a time.
|
|
12
|
-
|
|
13
|
-
## Scope
|
|
14
|
-
|
|
15
|
-
Governs the sequencing document at `.claude/ROADMAP.md`: the version table, its columns, and its lifecycle.
|
|
16
|
-
|
|
17
|
-
Does not govern:
|
|
18
|
-
|
|
19
|
-
- What the scope is, which the roadmap sequences rather than defines: `requirements.md`
|
|
20
|
-
- Task files, outcomes, and board state: `tasks.md`
|
|
21
|
-
- Phase-label format and semver discipline: `versioning.md`
|
|
22
|
-
- Rationale for a technical choice: `architecture.md`
|
|
23
|
-
|
|
24
|
-
## What goes in
|
|
25
|
-
|
|
26
|
-
- One row per version, ordered top to bottom by sequence
|
|
27
|
-
- A status per version: active, next in line, or deferred
|
|
28
|
-
- The observable outcome each version delivers, as a short phrase
|
|
29
|
-
- The features a version groups, by name, drawn from the MVP list in `.claude/REQUIREMENTS.md`
|
|
30
|
-
- The version each row depends on, and a short why
|
|
31
|
-
|
|
32
|
-
## What does not go in
|
|
33
|
-
|
|
34
|
-
- Task breakdown, checkboxes, or per-feature file lists
|
|
35
|
-
- Implementation detail, API names, or component references
|
|
36
|
-
- Sentence-long cells. Keep each cell terse so the table stays scannable.
|
|
37
|
-
|
|
38
|
-
## Format
|
|
39
|
-
|
|
40
|
-
One table. Columns, in order:
|
|
41
|
-
|
|
42
|
-
- `Version`: the `vX.Y` phase label. Phase-label format is governed by `standards/versioning.md`.
|
|
43
|
-
- `Status`: `Now` for the active version, `Next` for sequenced upcoming versions, `Later` for deferred themes.
|
|
44
|
-
- `Outcome`: what the user can do once this version ships, as a phrase.
|
|
45
|
-
- `Features`: the feature names this version groups, comma-separated.
|
|
46
|
-
- `Depends on`: the version this one needs and a short why, or `none` when independent.
|
|
47
|
-
|
|
48
|
-
## Template
|
|
49
|
-
|
|
50
|
-
```markdown
|
|
51
|
-
# Roadmap
|
|
52
|
-
|
|
53
|
-
| Version | Status | Outcome | Features | Depends on |
|
|
54
|
-
| ------- | ------ | -------- | -------------------- | ------------------ |
|
|
55
|
-
| v0.1 | Now | <phrase> | <feature>, <feature> | none |
|
|
56
|
-
| v0.2 | Next | <phrase> | <feature> | v0.1, for <reason> |
|
|
57
|
-
| v0.3 | Later | <phrase> | <feature> | none |
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Lifecycle
|
|
61
|
-
|
|
62
|
-
The roadmap sequences a finite, known scope. When the last version ships, the scope is exhausted and the project cuts a semver release per `standards/versioning.md`. Later work arrives as discrete items, a fresh requirements pass or tracked issues, rather than extending the original roadmap without end.
|