@erclx/aitk 3.33.1 → 3.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aitk",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "3.33.1",
4
+ "version": "3.34.1",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -68,7 +68,9 @@ The session also records nothing of what it learns. Both other callers of memory
68
68
  - Plan a row the re-test clears, since a cleared row carrying no plan is one the next pass looks at again
69
69
  - Split a task whose file set collides with every other by construction, rather than re-measuring a scoping defect that reads as a blocker
70
70
  - Check a candidate branch is unclaimed by an existing worktree or a live session before dispatching a background worker for it, since the measured failure this closes is a worker colliding with someone else's work already sitting in the row
71
- - Cap concurrent self-dispatched workers at three, counted by a session name no human-launched worker carries, since the evidence behind self-dispatch is one task shipped once
71
+ - Dispatch only a candidate whose file set is disjoint from every track in flight, compared at the file path rather than a folder above it, since a count knows nothing about what two workers write
72
+ - Hold a candidate whose sets are disjoint when a stated reason serializes it, and write that reason on the hold, since disjointness is necessary and not sufficient
73
+ - Name each self-dispatched worker with the `orchestrator-` prefix, since that is what separates one from an operator's own launch in a session listing
72
74
  - Report each self-dispatch and the row it fired against loudly enough to follow, since a person no longer watches the launch step happen
73
75
  - Stop dispatching once `## Run now` is empty or every row in it reads claimed, rather than waking again to fire on a board nobody is clearing
74
76
 
@@ -76,7 +78,7 @@ The session also records nothing of what it learns. Both other callers of memory
76
78
 
77
79
  - Implement a feature or edit any tracked file from this session, at any size, since the ban offers no proportionality exception
78
80
  - Merge. Recommend merge or changes and leave the gate to the human.
79
- - Spawn a worker with the Agent tool, since an in-process subagent shares this session's context and cannot be steered or reached independently. A dispatched `claude --bg` process is not this: it is a separate session with its own worktree and its own PR, gated by the collision check and the worker cap.
81
+ - Spawn a worker with the Agent tool, since an in-process subagent shares this session's context and cannot be steered or reached independently. A dispatched `claude --bg` process is not this: it is a separate session with its own worktree and its own PR, gated by the collision check and the file-set disjointness test.
80
82
  - Hand a worker anything but a plan, because scope lives there
81
83
  - Run a second orchestrator against the same board
82
84
  - Promote a task to fill the queue when nothing qualifies. A thin queue is a real answer.
@@ -93,7 +95,8 @@ The session also records nothing of what it learns. Both other callers of memory
93
95
  - 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
94
96
  - 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
95
97
  - Collision check refuses, with no session registry or no repository resolved: treat the candidate as unverified and fall back to the human-launch line, rather than reading a check that could not run as a clear one
96
- - Worker cap already at three: stop dispatching for the pass and leave the row ready, rather than queueing past it
98
+ - Candidate's file set overlaps a track in flight, or a stated reason serializes it: leave the row ready for the next pass, rather than dispatching onto a shared seam
99
+ - Operator states a cap for the session: honor it for that session alone, rather than treating it as a standing rule or writing the number into a file
97
100
 
98
101
  ## Out of scope
99
102
 
@@ -102,7 +102,7 @@ Write no shape for a correction. A correction is a sentence, and a format for ad
102
102
  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.
103
103
  2. Decide parallelism and merge order. Note which plans touch a shared wiring seam so their PRs merge in sequence, not at once.
104
104
  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.
105
- 4. Hand off. Read `${CLAUDE_SKILL_DIR}/references/orchestrator-dispatch.md` and follow it: check the branch is unclaimed, check the worker cap, then dispatch a background worker with `claude --bg`. Fall back to the human-launch line it replaces when the check refuses, the cap is reached, or the row fails Parallelism against something already in flight.
105
+ 4. Hand off. Read `${CLAUDE_SKILL_DIR}/references/orchestrator-dispatch.md` and follow it: check the branch is unclaimed, check the row's file set against every track in flight, then dispatch a background worker with `claude --bg`. Fall back to the human-launch line it replaces when the check refuses, the sets overlap, or a stated reason serializes the row behind something already out.
106
106
  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.
107
107
  - 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.
108
108
  6. Dispatch the handback. A pass posting anything owed, a finding at any severity or a testing question, 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.
@@ -128,7 +128,7 @@ Stamp the block with the commit this session read the tree at, which the same se
128
128
  - Do not implement features in this session. Hand the plan to a worker.
129
129
  - Do not merge. Recommend merge or changes. The human merges.
130
130
  - Do not spawn a worker with the Agent tool. An in-process subagent shares this session's context and cannot be steered or reached independently, which breaks the property this boundary protects rather than the mechanism it names. The dispatch in `orchestrator-dispatch.md` is a separate `claude --bg` process with its own worktree and its own PR, so it preserves that property instead.
131
- - Dispatch a background worker only once the collision check in `orchestrator-dispatch.md` clears and the worker cap still has room. Colliding with an existing worktree or session, or exceeding the cap, is what the check and the cap exist to catch, not a judgment call this session makes case by case.
131
+ - Dispatch a background worker only once the collision check in `orchestrator-dispatch.md` clears and the row's file set is disjoint from every track in flight. Colliding with an existing worktree or session is what the check exists to catch rather than a judgment call this session makes case by case. No fixed count binds how many tracks run at once, and Parallelism below states what does.
132
132
  - Do not edit tracked files from this session, at any size. The boundary offers no proportionality exception and nothing enforces it.
133
133
  - 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.
134
134
 
@@ -196,9 +196,16 @@ No fixed number caps worker tracks. Collision between file sets is what binds, s
196
196
  list the files a candidate touches against every track already in flight and open
197
197
  it only when the sets are disjoint. What thins as tracks multiply is the review
198
198
  attention each output gets, so add a track while you can still review every one
199
- properly and stop when you cannot.
199
+ properly and stop when you cannot. An operator can also cap this session's
200
+ workers by saying so, and a spoken cap binds for that session rather than
201
+ standing as a number in a file.
202
+
203
+ Serialize any track that touches a shared wiring seam with another in flight, and
204
+ serialize one whose sets are disjoint when a stated reason still puts it behind
205
+ another, since two tracks interact in ways no file-set comparison reads. One
206
+ building a skill and one auditing that catalog write nothing in common and the
207
+ audit still counts a denominator the other is moving.
200
208
 
201
- Serialize any track that touches a shared wiring seam with another in flight.
202
209
  Merge the branch with the smallest shared-file footprint first, and merge a
203
210
  branch touching `CLAUDE.md`, a Claude context entry, or a regenerated `index.md`
204
211
  last. Have every sibling rebase on the new `main` before the next merge. Two
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  title: Orchestrator dispatch runbook
3
- description: The collision check before a self-dispatch, the worker cap, the branch and model the launch names, and the loop's stopping condition
3
+ description: The collision check before a self-dispatch, the file-set disjointness gate, the branch and model the launch names, and the loop's stopping condition
4
4
  ---
5
5
 
6
- Run this at loop step 4, for a `## Run now` row whose plan is verified and whose file set has already cleared the Parallelism test against every track in flight, in place of handing the worktree to a human.
6
+ Run this at loop step 4, for a `## Run now` row whose plan is verified, in place of handing the worktree to a human. The disjointness gate below is where that row's file set is tested against every track in flight.
7
7
 
8
8
  ## Derive the candidate
9
9
 
@@ -16,7 +16,7 @@ This is the branch the worker takes, not a guess at one it will derive for itsel
16
16
  Run `aitk sessions list --branch <type>/<slug> --json` and read `claimed` off the record.
17
17
 
18
18
  - `claimed: true`: the row is not free. Report what holds it, `worktree` when it names a path, `sessions` when it carries a row, and `refs` when the branch already exists. Move to the next candidate rather than colliding.
19
- - `claimed: false`, `sessionsReadable: true`, and `refsReadable: true`: proceed to the cap check.
19
+ - `claimed: false`, `sessionsReadable: true`, and `refsReadable: true`: proceed to the disjointness gate.
20
20
  - `claimed: false` with either flag false, or the command refuses, or the record carries no `claimed` key (`reason` reads `no-registry` or `no-repository`): treat the candidate as unverified rather than clear. Report which reading could not be taken and fall back to the human-launch line below. Dispatching on a check that could not be read reproduces the exact collision this exists to prevent.
21
21
 
22
22
  Reading `claimed` off the record is what keeps this a check rather than a rule a session can talk itself out of. The field is already the composed answer across the worktree listing, the live session roster, and the refs that name the branch, so nothing here re-derives the OR.
@@ -31,11 +31,17 @@ A worker registers with `branch: main` and the main worktree as its `cwd` until
31
31
 
32
32
  Keep the branch of every row this pass has launched and treat a candidate matching one as claimed, without re-running the check. That closes the window for this dispatcher and only for it. A second dispatcher in another session reads git and the roster alone, sees none of this record, and can still take the same row. Say so when reporting, rather than implying the window is shut.
33
33
 
34
- ## Check the worker cap
34
+ ## Check the file sets are disjoint
35
35
 
36
- Run `aitk sessions list --json` with no `--branch`, then count entries whose `name` starts with `orchestrator-` and whose `repository` matches this run's own (`git rev-parse --path-format=absolute --git-common-dir`). An unscoped listing spans every repository on the machine, so the repository match is what keeps a busy sibling project from binding this one's cap. Every dispatch below names its session that way for exactly this count, so a worker the human launched by hand carries no such name and is never counted against it.
36
+ No count binds this. List the files the candidate's plan touches, from its `**Files to touch:**` lines, against the file set of every track already in flight, read off the Touches column of each row on the board. Dispatch when the sets are disjoint and hold the row otherwise.
37
37
 
38
- Three already out: report the cap and stop dispatching for this pass, leaving the row ready for the next one. The cap binds the self-dispatch path alone, since the evidence behind it is one task shipped once and the operator's own launches stay uncapped by count.
38
+ The board is not the whole set. A track a person launched by hand carries no row, so that column cannot see it, which is the ordinary shape whenever the operator is launching rather than dispatching. Read `aitk sessions list --json` for the branches in flight, and take the file set of any branch no row names from the plan that branch is building. A candidate cleared against the board alone is cleared against a partial reading.
39
+
40
+ Take the comparison at the file path rather than at a folder above it. `aitk tasks validate` compares path segments, and on 2026-08-28 it reported two rows as colliding on `src` where one writes `src/github.ts` and the other `src/markdown/structure.ts`. Most of the CLI sits under `src/`, so a folder-level reading fires on nearly every parallel pair and buries the one real collision that same run caught, `.claude/ARCHITECTURE.md` held by two rows. Read that verb's output as a candidate list and settle each pair by file.
41
+
42
+ Disjointness is necessary and not sufficient, so hold a candidate whose sets do not touch when a stated reason serializes it, and write the reason on the hold. One row creating a skill and another auditing that catalog and counting it write nothing in common, measured 2026-08-27, and dispatching both still leaves the audit counting a denominator that moves underneath it. Nothing verifies that a reason was written, so the rule holds only while the dispatcher applies it.
43
+
44
+ What binds past that is review attention rather than a count, and `## Parallelism` in the skill body states it along with the cap an operator can set for a session. No file here carries a number and this runbook does not either.
39
45
 
40
46
  ## Pick the model
41
47
 
@@ -49,7 +55,7 @@ Name `<model>` on the launch, and pick it against the task rather than copying w
49
55
  claude --bg --model <model> -n "orchestrator-<slug>" "Run /aitk:claude-worktree <type>/<slug>, then /aitk:claude-autoship <plan>"
50
56
  ```
51
57
 
52
- `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name `aitk sessions list` reads back for the worker cap, and `--model` overrides the inheritance the section above measured.
58
+ `--bg, --background` starts the session as a background agent and returns immediately, `-n, --name` sets the display name that tells a self-dispatched worker from an operator's own launch in `aitk sessions list`, and `--model` overrides the inheritance the section above measured. Keep the `orchestrator-` prefix. It outlives the count it was introduced for, since the roster read still needs a way to separate the two kinds of launch.
53
59
 
54
60
  The worktree call comes first and carries the branch as its argument, which is tier 0 of `claude-worktree` Step 2 and the only tier a caller can reach. `claude-autoship` Step 0 then finds the session already in a linked worktree and continues, which is a path it already documents. The autoship call carries `<plan>`, the same file this runbook already read to derive the branch, so its Step 1 takes it as the caller-supplied plan rather than re-deriving one from the slug the worker's branch happens to carry.
55
61
 
@@ -59,7 +65,7 @@ Report the dispatch as loudly as the human-launch line it replaces: name the bra
59
65
 
60
66
  ## Fall back to the human
61
67
 
62
- Hand the row to the human-launch line in step 4 instead of dispatching when any of these hold, and name which one: the collision check refused, the cap is reached, or the row's file set failed the Parallelism test against something already out.
68
+ Hand the row to the human-launch line in step 4 instead of dispatching when any of these hold, and name which one: the collision check refused, the row's file set overlaps a track already out, or a stated reason holds the row behind one.
63
69
 
64
70
  ## Stop the loop
65
71
 
@@ -61,7 +61,7 @@ Two ban shapes stay unmeasured and the report says so on every run. A multi-word
61
61
 
62
62
  ### Bullets, paragraphs, and depth
63
63
 
64
- Bullet weight and depth are the checks that moved off `aitk context audit`, unchanged in what they measure. A top-level bullet reports past roughly 400 characters with continuation lines folded in and nested items left out. A run of lines no heading breaks reports past roughly 40 rendered lines, measured at 80 columns, skipping fenced blocks and exempting a flat peer list averaging under 130 characters a bullet and a run that is entirely table rows. Each file reports its longest run alone, so a second run past the checkpoint in the same file is never named.
64
+ Bullet weight and depth are the checks that moved off `aitk context audit`, carrying what they measured at the time. A top-level bullet reports past roughly 400 characters with continuation lines folded in and nested items left out. A run reports past roughly 40 rendered lines, measured at 80 columns, where a heading breaks one and so does a bold section marker taking the whole line at column zero and ending in a colon, skipping fenced blocks and exempting a flat peer list averaging under 130 characters a bullet and a run that is entirely table rows. Each file reports its longest run alone, so a second run past the checkpoint in the same file is never named.
65
65
 
66
66
  Every weight and depth measure counts the text a reader is shown. A link reduces to its anchor text and an autolink drops whole, since no reader is shown either destination. A backticked path stays counted, which is where these measures part from the ban scan above: that one blanks a code span so a standard quoting its own banned character does not report itself, and discounting the same span here would under-report a paragraph carrying several. One file holds both span sets and each answers its own question.
67
67
 
@@ -136,7 +136,7 @@ Measured at `4b7b13a2` across 444 files with the paragraph checkpoint at 600: 8
136
136
 
137
137
  The ban half reached zero, which was the precondition the gate waited on, and it was re-measured against the same corpus at the moment the gate landed. Three sweep slices then took the structural half, and the corpus reports 1 heavy bullet and 22 heavy paragraphs across 6 files on 2026-08-06, every one on a ground the triage record holds as exempt.
138
138
 
139
- Depth is the one measure no slice triaged, so its figure moved with the sweep rather than with the corpus. Read all four structural counts from a run rather than from this paragraph.
139
+ Depth is the one measure no slice triaged, so its figure moved with the sweep rather than with the corpus. The break rule widened to bold section markers on 2026-08-28, which drops the count again with no edit to any file, so the 41 above reads against a rule the command no longer runs. Read all four structural counts from a run rather than from this paragraph.
140
140
 
141
141
  ### What a hit asks of an author
142
142
 
@@ -79,7 +79,9 @@ To run several worktrees as a coordinated flow rather than ad hoc, assert the or
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
- 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.
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.
83
+
84
+ 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, compared at the file path rather than at a folder above it, and the ceiling in practice is how many outputs one session can still review properly. Disjointness is necessary and not sufficient, since two tracks interact in ways no file-set comparison reads, so a stated reason can still hold a disjoint candidate behind another. An operator caps a session's workers by saying so, and that cap binds for the session rather than standing as a number in a file.
83
85
 
84
86
  Before a handoff, the orchestrator checks the plan against the tree rather than reading it: grep each construct it names and count the sites, confirm every phase label it cites is still open, and open each file it describes. A plan goes stale from whatever merged after it was written, and reading cannot catch that.
85
87
 
@@ -37,7 +37,7 @@ and no later session recovers that vantage.
37
37
  One feature travels this path end to end.
38
38
 
39
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. Orchestrator checks the branch is unclaimed and the worker cap has room, then dispatches a background worker with `claude --bg` against the plan, naming the branch and the model on the launch rather than leaving the worker to derive either. The branch travels as the argument to the worker's own worktree call, which is the one place the name is read rather than inferred. It falls back to naming the invocation for a human to run through `claude-worktree` and `claude-autoship` when the check refuses, the cap is reached, or the plan collides with a track already in flight. Either way, the worker enters its own worktree, builds, self-checks, opens a PR, and stops at the PR boundary.
40
+ 2. Orchestrator checks the branch is unclaimed and the plan's file set is disjoint from every track in flight, then dispatches a background worker with `claude --bg` against the plan, naming the branch and the model on the launch rather than leaving the worker to derive either. No count caps how many run at once. The branch travels as the argument to the worker's own worktree call, which is the one place the name is read rather than inferred. It falls back to naming the invocation for a human to run through `claude-worktree` and `claude-autoship` when the check refuses, the sets overlap, or a stated reason serializes the plan behind a track already in flight. Either way, the worker enters its own worktree, builds, self-checks, opens a PR, and stops at the PR boundary.
41
41
  3. Orchestrator reviews the PR with `claude-pr-review` and posts findings to it.
42
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
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.
@@ -147,9 +147,12 @@ before another is carried nowhere at all.
147
147
 
148
148
  ## Parallelism
149
149
 
150
- The binding constraint is the human and the shared files, not the board. Cap
151
- at two or three worker tracks and split them across the stack so they do not
152
- collide on the same files.
150
+ The binding constraint is the human and the shared files rather than the board.
151
+ No number caps worker tracks. Open one whenever its file set is disjoint from
152
+ every track already in flight, compared at the file path rather than at a folder
153
+ above it, and stop adding once you can no longer review every output properly.
154
+ Serialize a track sharing a wiring seam with another, and serialize one whose
155
+ sets are disjoint when a stated reason still puts it behind another.
153
156
 
154
157
  Unit checks run freely in many worktrees at once.
155
158
  A dev server, an end-to-end run, and a screenshot run alongside each other on a
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/aitk",
3
3
  "type": "module",
4
- "version": "3.33.1",
4
+ "version": "3.34.1",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -519,6 +519,9 @@ function reportDepth(
519
519
  logInfo(
520
520
  'A run that is entirely table rows is excluded too, since a heading inside a table splits the table rather than the run.',
521
521
  )
522
+ logInfo(
523
+ 'A heading breaks a run and so does a bold section marker at column zero ending in a colon, such as a line reading only Risks in bold. A colon-less or indented one stays prose.',
524
+ )
522
525
 
523
526
  const over = reports
524
527
  .filter((report) => report.structure.longestRun > checkpoints.run)
@@ -1,6 +1,38 @@
1
1
  import { type BodyLine, visibleText } from '@/markdown/scan'
2
2
 
3
3
  const HEADING = /^#{1,6}\s/
4
+
5
+ /**
6
+ * A section marker holding its line alone, which is the signpost a document
7
+ * uses where its own template asks for bold rather than a heading.
8
+ *
9
+ * `standards/plan.md` gives `## Summary` a heading and marks the four sections
10
+ * below it this way, so a conforming plan read as one run from its first line
11
+ * to its last and all seven live plans reported past the depth checkpoint, at
12
+ * 106 to 166 rendered lines. A measure firing on a whole corpus says nothing
13
+ * about it, and it costs more than silence, since a reader who learns to skip
14
+ * the depth section skips it on the file where a deep run genuinely sits.
15
+ *
16
+ * Breaking on the marker clears two of those seven and moves the other five
17
+ * onto the seams inside them, at 42 to 77 lines, which is the measure telling
18
+ * a long section from a long file for the first time. Moving the template to
19
+ * headings was the alternative and it clears no plan already written, leaving
20
+ * each flagged until someone rewrites it.
21
+ *
22
+ * The marker starts at column zero and carries a colon, and the whole line is
23
+ * the marker or none of it is. A bold phrase opening a sentence is emphasis
24
+ * rather than a seam, and an indented one is a label inside a list item, so
25
+ * both stay prose. This ships as package data every project reads, where a
26
+ * missed break costs one unbroken run and a false one shortens every run around
27
+ * it until the measure stops reporting, which is the dearer of the two.
28
+ *
29
+ * The colon is what a colon-less `**Testing**` is held out by, and that shape
30
+ * is a real section marker in a review body rather than a hypothetical. Widening
31
+ * to reach it moves the shipped pattern rather than the wording, so the rule
32
+ * states the colon and the widening stays open for a decision of its own.
33
+ */
34
+ const SECTION_MARKER = /^\*\*[^*]+:\*\*\s*$/
35
+
4
36
  const LIST_ITEM = /^(\s*)([-*+]|\d+\.)\s+/
5
37
  const TABLE_ROW = /^\s*\|/
6
38
  const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
@@ -241,7 +273,11 @@ function isTableRun(run: readonly BodyLine[]): boolean {
241
273
  }
242
274
 
243
275
  /**
244
- * Measures the longest run of lines no heading breaks, in rendered lines.
276
+ * Measures the longest run of lines no signpost breaks, in rendered lines.
277
+ *
278
+ * A heading breaks a run and so does a section marker, which is the same
279
+ * signpost written the way a template asked for it. `SECTION_MARKER` above
280
+ * fixes which lines qualify.
245
281
  *
246
282
  * Fenced lines are skipped rather than treated as breaks, per the standard:
247
283
  * they leave the count without ending the run, so prose either side of an
@@ -289,7 +325,7 @@ export function longestRun(
289
325
  for (const line of lines) {
290
326
  if (line.fenced) continue
291
327
 
292
- if (HEADING.test(line.text)) {
328
+ if (HEADING.test(line.text) || SECTION_MARKER.test(line.text)) {
293
329
  close()
294
330
  continue
295
331
  }
@@ -36,6 +36,8 @@ Does not govern:
36
36
  - Mixing prose with that list, or nesting levels inside it, ends the exemption at any weight.
37
37
  - Exempt a block whose lines are all table rows, at any length. The peer list above is exempt because it is already navigable, and a table because the remedy does not exist: a subheading dropped inside one splits the table rather than the run, so no edit short of rewriting it as a list clears the checkpoint.
38
38
  - Prose either side of the table ends that exemption, since the block has a seam and a heading breaks it there.
39
+ - Break a run on a bold section marker holding its line alone, the way a heading breaks one. A document whose template separates sections with `**Risks:**` rather than `## Risks` has written the seam a reader lands on, and reading only the heading reported every such file at its full length whatever it carried.
40
+ - Require the marker to start at column zero and to end in a colon inside the bold, and to be the whole line. `**Risks:**` breaks a run, a bold phrase opening a sentence stays emphasis, and an indented one stays a label inside its list item. A colon-less `**Testing**` does not break either, which holds a real section marker out and is the cost of keeping the pattern narrow on data every project is measured against.
39
41
 
40
42
  ## Paragraphs and lists
41
43
 
package/standards/plan.md CHANGED
@@ -57,6 +57,7 @@ The document opens with `# Feature: <short title>` and one paragraph stating wha
57
57
 
58
58
  - Write each marker as the table gives it. `Summary` opens the prose a reader scans, so it takes an H2, and the four that follow are labels over lists rather than sections of prose.
59
59
  - A section written in the other spelling is still that section. `## Risks` and `**Risks:**` name one thing, and a plan carrying either has stated its risks, so neither reads as an omission. Write the table's form in a new plan and leave an existing plan's spelling alone.
60
+ - Both spellings break a depth run, so `aitk markdown audit` reads either as the seam it is and reports a plan for a section that genuinely runs long rather than for its whole body. The markdown standard states the rule.
60
61
  - Write `None identified.` under a required section with nothing to report rather than dropping the marker. A dropped section and an unconsidered one read identically.
61
62
  - Aim `## Summary` at a person scanning the plan, not at the session executing it. The other sections carry what execution needs.
62
63
  - Give every `**Files to touch:**` entry a backticked path and something said about it. A bare path states scope and not intent, and the reason is what an executing session checks its edit against. Lead with the path or lead with a label carrying the path, whichever reads better for the entry.