@erclx/aitk 3.34.0 → 3.35.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/references/orchestrator-dispatch.md +3 -1
- package/docs/agents/markdown-audit.md +2 -2
- package/docs/agents/tasks.md +20 -0
- package/package.json +1 -1
- package/src/commands/markdown.ts +3 -0
- package/src/commands/tasks.ts +16 -0
- package/src/markdown/structure.ts +38 -2
- package/src/tasks/validate.ts +112 -6
- package/standards/markdown.md +2 -0
- package/standards/plan.md +1 -0
|
@@ -37,7 +37,9 @@ No count binds this. List the files the candidate's plan touches, from its `**Fi
|
|
|
37
37
|
|
|
38
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
39
|
|
|
40
|
-
Take the comparison at the file path rather than at a folder above it. `aitk tasks validate` compares
|
|
40
|
+
Take the comparison at the file path rather than at a folder above it. `aitk tasks validate` compares the paths each row wrote, so a collision it reports on a folder means a row's Touches cell claimed that folder rather than the verb widening anything. On 2026-08-28 it called two rows colliding on `src` because one cell named the bare folder while the other wrote `src/markdown/structure.ts`, which this paragraph once misread as the verb comparing path segments too coarsely.
|
|
41
|
+
|
|
42
|
+
The finding names which row contributed the containing path, and a bare-folder cell reports as a claim of its own beside the findings. Read that output as a candidate list, settle each pair by file, and narrow the cell that over-claimed rather than discounting the collision it caused.
|
|
41
43
|
|
|
42
44
|
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
45
|
|
|
@@ -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`,
|
|
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
|
|
package/docs/agents/tasks.md
CHANGED
|
@@ -129,6 +129,8 @@ A backlog line is a bullet carrying a link to a sibling task, since the backlog
|
|
|
129
129
|
|
|
130
130
|
The collision check is the one a person cannot run by eye. Paths come from the backticked spans in the Touches column, a span naming no file is dropped, and a directory collides with any file beneath it. A `## Run now` row whose column parses to nothing is reported rather than skipped, since a row stating no file set makes a claim nothing can check.
|
|
131
131
|
|
|
132
|
+
Where a directory holds the other row's file, the finding names the row that claimed it, reading `both touch src/tasks, which v2.0-second claims as a folder.` The shared strings alone leave an over-broad cell and a genuine overlap identical, which is how a correct report was once read as the verb comparing folders rather than files.
|
|
133
|
+
|
|
132
134
|
The blocker check re-takes a measurement the board records once and never repeats. Two of the five blocker kinds put a fact on disk: a dependency is settled by the cited task being archived or by its work reaching the trunk, and a collision is settled by nothing under `## Run now` still holding the file the cell cites.
|
|
133
135
|
|
|
134
136
|
A closed outcome is not the fact the dependency half needs. The ship chain marks outcomes as its first step and opens the pull request several steps later, so a check reading the checkbox reports the row settled while the branch is still in review. A live task therefore settles the row only once it closed every outcome and carries a `Pull request:` line the trunk holds. One that names no pull request, and one whose number no trunk ref could answer for, land in the untested array below rather than being settled or left silent.
|
|
@@ -155,6 +157,24 @@ The other three kinds rest on a person's judgment, so a row neither half reached
|
|
|
155
157
|
|
|
156
158
|
An untested row is not a finding and moves no exit code. Reading a clean findings list as a clean board is the failure the array exists to prevent, and `orchestrator-parked.md` is the pass that takes those rows by hand.
|
|
157
159
|
|
|
160
|
+
A `## Run now` row whose Touches column names a bare folder lands in a third array on the same reasoning:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"claims": [
|
|
165
|
+
{
|
|
166
|
+
"group": "Run now",
|
|
167
|
+
"subject": "v1.0-first",
|
|
168
|
+
"message": "claims the whole src/tasks folder, so it collides with every row written under it."
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
That claim collides with every row a later session writes under the folder, and it is legitimate whenever the row does rewrite the directory, so the array states the reach and moves no exit code. A measure failing on a cell that is right teaches a reader to skip it. Folder against file is decided by asking the tree for a path that resolves, and by the extension only for a path the row has yet to create, since the name alone reads a file carrying no extension as a folder.
|
|
175
|
+
|
|
176
|
+
The scan reaches `## Run now` and stops, where the collision check stops. A cell in another group describes work nobody has planned, written as a sentence and rewritten once a plan exists, so a claim read off one reports on prose rather than on a file set. A parked folder claim surfaces when its row is promoted, which is when the cell becomes something a dispatcher can act on.
|
|
177
|
+
|
|
158
178
|
Exit codes: `0` every check passed, `1` refused, `2` at least one finding. The `reason` field carries which gate refused: `no-board`, `no-ordering`, or `no-groups`. A board grouping under headings of its own trips `no-groups` rather than being read against columns it never declared.
|
|
159
179
|
|
|
160
180
|
Columns are read from each table's own header rather than by position, so a project whose board differs from this one is reported for what it lacks. The `index`, `priority`, and `backlog` siblings are skipped, along with every pre-compaction handoff, which takes one file per session under a `session-` prefix. None of them is a task, and a handoff counted as one would be reported as a task carrying no row on every session that wrote one.
|
package/package.json
CHANGED
package/src/commands/markdown.ts
CHANGED
|
@@ -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)
|
package/src/commands/tasks.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
} from '@/tasks/record'
|
|
18
18
|
import {
|
|
19
19
|
type Finding,
|
|
20
|
+
type FolderClaim,
|
|
20
21
|
type Untested,
|
|
21
22
|
type ValidateOutcome,
|
|
22
23
|
validateBoard,
|
|
@@ -581,6 +582,16 @@ function reportValidation(
|
|
|
581
582
|
)
|
|
582
583
|
for (const row of outcome.untested) logWarn(describeUntested(row))
|
|
583
584
|
}
|
|
585
|
+
|
|
586
|
+
// A folder claim is often the correct way to say a row rewrites a whole
|
|
587
|
+
// directory, so it reports beside the findings and moves no exit code. A
|
|
588
|
+
// measure that fails on a legitimate cell trains a reader to skip it.
|
|
589
|
+
logStep('Folder claims')
|
|
590
|
+
if (outcome.claims.length === 0) {
|
|
591
|
+
logInfo('every run now row names files rather than folders')
|
|
592
|
+
} else {
|
|
593
|
+
for (const claim of outcome.claims) logWarn(describeClaim(claim))
|
|
594
|
+
}
|
|
584
595
|
outro()
|
|
585
596
|
}
|
|
586
597
|
|
|
@@ -594,6 +605,7 @@ function reportValidation(
|
|
|
594
605
|
tasks: outcome.tasks,
|
|
595
606
|
findings: outcome.findings,
|
|
596
607
|
untested: outcome.untested,
|
|
608
|
+
claims: outcome.claims,
|
|
597
609
|
})}\n`,
|
|
598
610
|
)
|
|
599
611
|
}
|
|
@@ -610,6 +622,10 @@ function describeUntested(row: Untested): string {
|
|
|
610
622
|
return `${row.group}: ${row.subject} ${row.message}`
|
|
611
623
|
}
|
|
612
624
|
|
|
625
|
+
function describeClaim(claim: FolderClaim): string {
|
|
626
|
+
return `${claim.group}: ${claim.subject} ${claim.message}`
|
|
627
|
+
}
|
|
628
|
+
|
|
613
629
|
async function runArchive(
|
|
614
630
|
task: string | undefined,
|
|
615
631
|
opts: ArchiveCommandOptions,
|
|
@@ -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
|
|
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
|
}
|
package/src/tasks/validate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
1
|
+
import { existsSync, statSync } from 'node:fs'
|
|
2
2
|
import { readdir, readFile } from 'node:fs/promises'
|
|
3
3
|
import { join, resolve } from 'node:path'
|
|
4
4
|
import {
|
|
@@ -63,6 +63,19 @@ export interface Untested {
|
|
|
63
63
|
readonly message: string
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* A `Touches` cell naming a bare folder, which claims every file under it and
|
|
68
|
+
* so collides with every row a later session writes there. The claim is often
|
|
69
|
+
* correct, since a row rewriting a whole directory has no other way to say so,
|
|
70
|
+
* which is why it reports beside the findings rather than inside them and moves
|
|
71
|
+
* no exit code.
|
|
72
|
+
*/
|
|
73
|
+
export interface FolderClaim {
|
|
74
|
+
readonly group: BoardGroup
|
|
75
|
+
readonly subject: string
|
|
76
|
+
readonly message: string
|
|
77
|
+
}
|
|
78
|
+
|
|
66
79
|
/**
|
|
67
80
|
* A backlog line, which carries a pointer and nothing else. The backlog is
|
|
68
81
|
* explicitly unordered, so a line has no position to read and no columns to
|
|
@@ -91,6 +104,7 @@ export interface ValidateReport {
|
|
|
91
104
|
readonly tasks: number
|
|
92
105
|
readonly findings: readonly Finding[]
|
|
93
106
|
readonly untested: readonly Untested[]
|
|
107
|
+
readonly claims: readonly FolderClaim[]
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
export interface ValidateRefused {
|
|
@@ -164,6 +178,23 @@ function sharesPath(left: string, right: string): boolean {
|
|
|
164
178
|
)
|
|
165
179
|
}
|
|
166
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Whether a `Touches` path names a directory. The tree answers for a path that
|
|
183
|
+
* resolves, which is the only reading that separates a folder from a file
|
|
184
|
+
* carrying no extension, such as a hook script.
|
|
185
|
+
*
|
|
186
|
+
* A path the row has yet to create resolves to nothing, so the name decides
|
|
187
|
+
* there. `readPaths` admits a span for one of two reasons, a slash or an
|
|
188
|
+
* extension, so a span surviving without an extension is one a slash let
|
|
189
|
+
* through and reads as a folder.
|
|
190
|
+
*/
|
|
191
|
+
function isFolder(path: string, root: string): boolean {
|
|
192
|
+
const target = resolve(root, path)
|
|
193
|
+
if (existsSync(target)) return statSync(target).isDirectory()
|
|
194
|
+
|
|
195
|
+
return !/\.[A-Za-z][A-Za-z0-9]*$/.test(path)
|
|
196
|
+
}
|
|
197
|
+
|
|
167
198
|
function splitCells(line: string): string[] {
|
|
168
199
|
return line
|
|
169
200
|
.trim()
|
|
@@ -457,17 +488,22 @@ function checkCollisions(rows: readonly BoardRow[]): Finding[] {
|
|
|
457
488
|
for (let j = i + 1; j < ready.length; j += 1) {
|
|
458
489
|
const left = ready[i]
|
|
459
490
|
const right = ready[j]
|
|
460
|
-
const shared = (left.touches ?? []).
|
|
461
|
-
(right.touches ?? []).
|
|
462
|
-
|
|
491
|
+
const shared = (left.touches ?? []).flatMap((path) => {
|
|
492
|
+
const other = (right.touches ?? []).find((candidate) =>
|
|
493
|
+
sharesPath(path, candidate),
|
|
494
|
+
)
|
|
495
|
+
return other === undefined
|
|
496
|
+
? []
|
|
497
|
+
: [describeShared(path, other, left, right)]
|
|
498
|
+
})
|
|
463
499
|
|
|
464
500
|
if (shared.length === 0) continue
|
|
465
501
|
|
|
466
502
|
findings.push({
|
|
467
503
|
kind: 'touches-collided',
|
|
468
504
|
group: 'Run now',
|
|
469
|
-
subject: `${left
|
|
470
|
-
message: `both touch ${shared
|
|
505
|
+
subject: `${subjectOf(left)} and ${subjectOf(right)}`,
|
|
506
|
+
message: `both touch ${joinShared(shared)}.`,
|
|
471
507
|
})
|
|
472
508
|
}
|
|
473
509
|
}
|
|
@@ -475,6 +511,75 @@ function checkCollisions(rows: readonly BoardRow[]): Finding[] {
|
|
|
475
511
|
return findings
|
|
476
512
|
}
|
|
477
513
|
|
|
514
|
+
function subjectOf(row: BoardRow): string {
|
|
515
|
+
return row.stem ?? row.label
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Names one path two rows share, and the row that claimed it as a folder when
|
|
520
|
+
* one side named a directory holding the other's file. Which side contributed
|
|
521
|
+
* the containing path is the fact a reader acts on, and printing the shared
|
|
522
|
+
* strings alone leaves an over-broad cell and a genuine overlap identical.
|
|
523
|
+
*
|
|
524
|
+
* The shorter path is the container, because `sharesPath` holds for an unequal
|
|
525
|
+
* pair only when one is a prefix of the other up to a separator.
|
|
526
|
+
*/
|
|
527
|
+
function describeShared(
|
|
528
|
+
path: string,
|
|
529
|
+
other: string,
|
|
530
|
+
left: BoardRow,
|
|
531
|
+
right: BoardRow,
|
|
532
|
+
): string {
|
|
533
|
+
if (path === other) return path
|
|
534
|
+
|
|
535
|
+
const container = path.length < other.length ? path : other
|
|
536
|
+
const owner = container === path ? left : right
|
|
537
|
+
return `${container}, which ${subjectOf(owner)} claims as a folder`
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* A folder clause carries a comma of its own, so a comma between clauses would
|
|
542
|
+
* read as another path. The plain list keeps the comma it has always had.
|
|
543
|
+
*/
|
|
544
|
+
function joinShared(clauses: readonly string[]): string {
|
|
545
|
+
const separator = clauses.some((clause) => clause.includes(',')) ? '; ' : ', '
|
|
546
|
+
return clauses.join(separator)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Reports a `Touches` cell naming a bare folder. The claim collides with every
|
|
551
|
+
* row a later session writes under that folder, and it is legitimate whenever
|
|
552
|
+
* the row does rewrite the directory, so this states the reach rather than
|
|
553
|
+
* calling it a defect.
|
|
554
|
+
*
|
|
555
|
+
* The scan takes `## Run now` alone, where `checkCollisions` takes it. A cell in
|
|
556
|
+
* another group describes work nobody has planned, so it is written as a
|
|
557
|
+
* sentence and rewritten at planning time, and a claim read off one reports on
|
|
558
|
+
* prose rather than on a file set. That is the shape that teaches a reader to
|
|
559
|
+
* skip the report. A parked folder claim surfaces when the row is promoted,
|
|
560
|
+
* which is also when its cell becomes a set anything can act on.
|
|
561
|
+
*/
|
|
562
|
+
function checkFolderClaims(
|
|
563
|
+
rows: readonly BoardRow[],
|
|
564
|
+
root: string,
|
|
565
|
+
): FolderClaim[] {
|
|
566
|
+
const claims: FolderClaim[] = []
|
|
567
|
+
|
|
568
|
+
for (const row of rows.filter((candidate) => candidate.group === 'Run now')) {
|
|
569
|
+
for (const path of row.touches ?? []) {
|
|
570
|
+
if (!isFolder(path, root)) continue
|
|
571
|
+
|
|
572
|
+
claims.push({
|
|
573
|
+
group: row.group,
|
|
574
|
+
subject: subjectOf(row),
|
|
575
|
+
message: `claims the whole ${path} folder, so it collides with every row written under it.`,
|
|
576
|
+
})
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return claims
|
|
581
|
+
}
|
|
582
|
+
|
|
478
583
|
/**
|
|
479
584
|
* Reads the task a blocker cell cites. A task pointer is a bare sibling
|
|
480
585
|
* filename, the way every `Task` column spells one, so a target carrying a
|
|
@@ -734,5 +839,6 @@ export async function validateBoard(
|
|
|
734
839
|
tasks: stems.length,
|
|
735
840
|
findings,
|
|
736
841
|
untested: parked.untested,
|
|
842
|
+
claims: checkFolderClaims(rows, root),
|
|
737
843
|
}
|
|
738
844
|
}
|
package/standards/markdown.md
CHANGED
|
@@ -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.
|