@erclx/aitk 0.29.0 → 0.30.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/docs/agents.md +4 -2
- package/package.json +1 -1
- package/src/commands/context.ts +45 -2
- package/src/context/audit.ts +64 -0
- package/standards/context.md +3 -1
package/docs/agents.md
CHANGED
|
@@ -80,7 +80,7 @@ Full help: `aitk <command> --help`.
|
|
|
80
80
|
| `aitk transcripts <url>` | Fetch a YouTube transcript with metadata frontmatter (needs `yt-dlp`) |
|
|
81
81
|
| `aitk tasks archive` | Move a shipped task off the board, clear its ordering row, and regenerate the index |
|
|
82
82
|
| `aitk comments scan` | Measure comment density by language and comment kind, with a trend recomputed from git |
|
|
83
|
-
| `aitk context audit` | Report entry length, depth, cited-path resolution, and index drift
|
|
83
|
+
| `aitk context audit` | Report entry length, depth, cited-path resolution, provenance, and index drift |
|
|
84
84
|
| `aitk capture [source]` | Render HTML capture sources to PNG, toolkit-only and absent from an installed package |
|
|
85
85
|
|
|
86
86
|
### Domain commands
|
|
@@ -387,7 +387,7 @@ aitk context audit --folder context,diagrams
|
|
|
387
387
|
|
|
388
388
|
Scope defaults to `context`, `diagrams`, and `wireframes`, and a folder the project does not carry is skipped rather than reported. A domain that outgrew one file and split into `<domain>/` is audited as its own folder, so a split entry measures at the same grain as a flat one.
|
|
389
389
|
|
|
390
|
-
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved citation. Only the citation check sets a failing code. Length, depth, table, and index findings print and return `0`, because each is a judgment
|
|
390
|
+
Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved citation. Only the citation check sets a failing code. Length, depth, table, provenance, and index findings print and return `0`, because each is a judgment and failing a push on one would make the check something to route around.
|
|
391
391
|
|
|
392
392
|
### What each check reports
|
|
393
393
|
|
|
@@ -395,6 +395,8 @@ Length and depth quote their checkpoints from `.claude/standards/context.md`: ro
|
|
|
395
395
|
|
|
396
396
|
The table check reports a catalog that grows a row per shipped thing, not a table count. A fixed comparison table never reflows, so its size costs nothing. A table qualifies at six or more body rows whose first column mostly carries a path, command, or link, which is what separates a catalog from a comparison without reading the prose.
|
|
397
397
|
|
|
398
|
+
The provenance check reports the markers narrating how a domain reached its shape rather than describing what it is: a date, a change number, or a release label. The standard admits a rejected alternative and the reasoning that killed it while refusing the provenance attached to it, so a marker names a line to read rather than a line to delete. Findings group by entry and sort left to right within a line, since what a reader acts on is which file to open. Fenced blocks are excluded, which keeps a pinned version in an install command from reading as a claim the entry makes. Frontmatter is excluded with them, since every check reads the body alone, and that is what keeps a diagram entry's dated `verified` stamp a record of its last check rather than a marker to settle.
|
|
399
|
+
|
|
398
400
|
Index drift compares an index against its siblings in both directions. An entry the index does not link is invisible to a session choosing what to open, and a linked name resolving to nothing sends one to a path that opens nothing.
|
|
399
401
|
|
|
400
402
|
### The citation gate
|
package/package.json
CHANGED
package/src/commands/context.ts
CHANGED
|
@@ -45,7 +45,9 @@ export function register(program: Command): void {
|
|
|
45
45
|
|
|
46
46
|
context
|
|
47
47
|
.command('audit')
|
|
48
|
-
.description(
|
|
48
|
+
.description(
|
|
49
|
+
'Report entry length, depth, citations, provenance, and index drift',
|
|
50
|
+
)
|
|
49
51
|
.argument('[path]', 'Project root, defaulting to the current directory')
|
|
50
52
|
.helpOption('-h, --help', 'Show this help message')
|
|
51
53
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
@@ -61,7 +63,7 @@ export function register(program: Command): void {
|
|
|
61
63
|
' 2 a cited path did not resolve',
|
|
62
64
|
'',
|
|
63
65
|
'Only unresolved citations set a failing exit code. Length, depth,',
|
|
64
|
-
'table, and index findings are advisory.',
|
|
66
|
+
'table, provenance, and index findings are advisory.',
|
|
65
67
|
'',
|
|
66
68
|
'Examples:',
|
|
67
69
|
' aitk context audit',
|
|
@@ -134,6 +136,7 @@ async function runAudit(
|
|
|
134
136
|
reportLength(entries)
|
|
135
137
|
reportDepth(entries)
|
|
136
138
|
reportTables(entries)
|
|
139
|
+
reportProvenance(entries)
|
|
137
140
|
reportDrift(drift)
|
|
138
141
|
outro()
|
|
139
142
|
}
|
|
@@ -303,6 +306,46 @@ function reportTables(entries: readonly EntryReport[]): void {
|
|
|
303
306
|
pipeOutput(candidates.join('\n'))
|
|
304
307
|
}
|
|
305
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Groups by entry rather than listing every marker.
|
|
311
|
+
*
|
|
312
|
+
* The two entries carrying most of a corpus's markers carry them a dozen at a
|
|
313
|
+
* time, and a flat list of those buries the entries holding one. What a reader
|
|
314
|
+
* acts on is which file to open, so the count sits beside the name and the
|
|
315
|
+
* lines follow it.
|
|
316
|
+
*/
|
|
317
|
+
function reportProvenance(entries: readonly EntryReport[]): void {
|
|
318
|
+
logStep('Provenance')
|
|
319
|
+
logInfo('Fenced blocks are excluded. A marker is a judgment, never a defect.')
|
|
320
|
+
|
|
321
|
+
const carrying = entries
|
|
322
|
+
.filter((entry) => entry.provenance.length > 0)
|
|
323
|
+
.sort((a, b) => b.provenance.length - a.provenance.length)
|
|
324
|
+
|
|
325
|
+
if (carrying.length === 0) {
|
|
326
|
+
logInfo('No entry narrates a change.')
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const total = carrying.reduce(
|
|
331
|
+
(sum, entry) => sum + entry.provenance.length,
|
|
332
|
+
0,
|
|
333
|
+
)
|
|
334
|
+
logWarn(
|
|
335
|
+
`${plural(total, 'marker')} across ${carrying.length} ${carrying.length === 1 ? 'entry' : 'entries'}`,
|
|
336
|
+
)
|
|
337
|
+
pipeOutput(
|
|
338
|
+
carrying
|
|
339
|
+
.map(
|
|
340
|
+
(entry) =>
|
|
341
|
+
`${entry.rel} ${plural(entry.provenance.length, 'marker')}\n${entry.provenance
|
|
342
|
+
.map((found) => ` :${found.line} ${found.kind} ${found.text}`)
|
|
343
|
+
.join('\n')}`,
|
|
344
|
+
)
|
|
345
|
+
.join('\n'),
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
306
349
|
function reportDrift(drift: readonly FolderDrift[]): void {
|
|
307
350
|
logStep('Index drift')
|
|
308
351
|
|
package/src/context/audit.ts
CHANGED
|
@@ -25,11 +25,35 @@ const TABLE_ROW = /^\s*\|/
|
|
|
25
25
|
const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
|
|
26
26
|
const NAMED_CELL = /`[^`]+`|\[[^\]]+\]\([^)]+\)/
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Spellings of how the domain reached its shape rather than what it is now.
|
|
30
|
+
*
|
|
31
|
+
* The standard admits a rejected alternative and its reasoning while refusing
|
|
32
|
+
* the provenance attached to it, and these three are what a session reaches for
|
|
33
|
+
* when it records the second: when a change landed, which change carried it,
|
|
34
|
+
* and which release labelled it. A marker is a judgment rather than a defect,
|
|
35
|
+
* so this is measured and reported and never gates.
|
|
36
|
+
*/
|
|
37
|
+
const PROVENANCE: readonly { kind: ProvenanceKind; pattern: RegExp }[] = [
|
|
38
|
+
{ kind: 'date', pattern: /\b\d{4}-\d{2}-\d{2}\b/g },
|
|
39
|
+
{ kind: 'change', pattern: /#\d{3,}\b/g },
|
|
40
|
+
{ kind: 'release', pattern: /\bv\d+\.\d+(?:\.\d+)?\b/g },
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
export type ProvenanceKind = 'date' | 'change' | 'release'
|
|
44
|
+
|
|
28
45
|
export interface TableFinding {
|
|
29
46
|
readonly line: number
|
|
30
47
|
readonly rows: number
|
|
31
48
|
}
|
|
32
49
|
|
|
50
|
+
export interface ProvenanceFinding {
|
|
51
|
+
readonly line: number
|
|
52
|
+
readonly kind: ProvenanceKind
|
|
53
|
+
/** The marker as written, so a report names what to go and look at. */
|
|
54
|
+
readonly text: string
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
export interface EntryReport {
|
|
34
58
|
readonly rel: string
|
|
35
59
|
readonly lines: number
|
|
@@ -37,6 +61,7 @@ export interface EntryReport {
|
|
|
37
61
|
/** First line of the longest run, or 0 when the entry has no run at all. */
|
|
38
62
|
readonly longestRunLine: number
|
|
39
63
|
readonly catalogTables: readonly TableFinding[]
|
|
64
|
+
readonly provenance: readonly ProvenanceFinding[]
|
|
40
65
|
}
|
|
41
66
|
|
|
42
67
|
interface BodyLine {
|
|
@@ -191,6 +216,44 @@ function catalogTables(lines: readonly BodyLine[]): TableFinding[] {
|
|
|
191
216
|
return findings
|
|
192
217
|
}
|
|
193
218
|
|
|
219
|
+
/**
|
|
220
|
+
* Finds the markers narrating a change rather than describing the domain.
|
|
221
|
+
*
|
|
222
|
+
* Fenced blocks are skipped for the same reason the table scan skips them: a
|
|
223
|
+
* sample command or a fixture inside an example is content the entry displays
|
|
224
|
+
* rather than a claim it makes, and a version pinned in an install line is the
|
|
225
|
+
* ordinary shape of one.
|
|
226
|
+
*/
|
|
227
|
+
function provenance(lines: readonly BodyLine[]): ProvenanceFinding[] {
|
|
228
|
+
// Scanning one pattern at a time emits a line's markers grouped by kind, so
|
|
229
|
+
// the column is carried out of the match and sorted on. Without it a line
|
|
230
|
+
// holding a date and two change numbers reports them in an order the reader
|
|
231
|
+
// cannot find by scanning left to right.
|
|
232
|
+
const found: { finding: ProvenanceFinding; column: number }[] = []
|
|
233
|
+
let fenced = false
|
|
234
|
+
|
|
235
|
+
for (const line of lines) {
|
|
236
|
+
if (FENCE.test(line.text)) {
|
|
237
|
+
fenced = !fenced
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
if (fenced) continue
|
|
241
|
+
|
|
242
|
+
for (const { kind, pattern } of PROVENANCE) {
|
|
243
|
+
for (const match of line.text.matchAll(pattern)) {
|
|
244
|
+
found.push({
|
|
245
|
+
finding: { line: line.number, kind, text: match[0] },
|
|
246
|
+
column: match.index,
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return found
|
|
253
|
+
.sort((a, b) => a.finding.line - b.finding.line || a.column - b.column)
|
|
254
|
+
.map((each) => each.finding)
|
|
255
|
+
}
|
|
256
|
+
|
|
194
257
|
export function measureEntry(rel: string, source: string): EntryReport {
|
|
195
258
|
const lines = bodyLines(source)
|
|
196
259
|
const run = longestRun(lines)
|
|
@@ -201,6 +264,7 @@ export function measureEntry(rel: string, source: string): EntryReport {
|
|
|
201
264
|
longestRun: run.length,
|
|
202
265
|
longestRunLine: run.line,
|
|
203
266
|
catalogTables: catalogTables(lines),
|
|
267
|
+
provenance: provenance(lines),
|
|
204
268
|
}
|
|
205
269
|
}
|
|
206
270
|
|
package/standards/context.md
CHANGED
|
@@ -94,11 +94,13 @@ Only the `development` entry carries this section. It is not a general-purpose h
|
|
|
94
94
|
- Tutorials or human onboarding. Those go in `docs/` if a public audience exists.
|
|
95
95
|
- Generated content (API references). Generate, do not write by hand.
|
|
96
96
|
- Anything already in `.claude/REQUIREMENTS.md` or `.claude/ARCHITECTURE.md`.
|
|
97
|
+
- The history of how the domain reached its current shape. An entry describes the repository as it stands, so a change number, release label, or date attached to a change goes wherever the project tracks work.
|
|
98
|
+
- A rejected alternative's provenance, which is the same rule at the one place the section above admits history. Keep what was tried and why it lost. Cut who tried it and when.
|
|
97
99
|
|
|
98
100
|
## Length
|
|
99
101
|
|
|
100
102
|
- Aim for one entry per domain. There is no hard cap. Length is a symptom, not the defect.
|
|
101
|
-
- Past roughly 150 lines, check
|
|
103
|
+
- Past roughly 150 lines, check three things before adding more: whether the entry still covers a single domain, whether it has filled with content `ls` or `--help` reproduces, and whether it has accumulated the history of its own changes. Fix whichever is true rather than trimming to hit a number.
|
|
102
104
|
- Past roughly 40 lines with no heading of any level breaking them, add a subheading at the seam. Measure the longest such run rather than everything under one `##`, and exclude fenced code blocks. The number is a checkpoint like the 150 above, not a cap.
|
|
103
105
|
- Exempt a block whose lines are all list items at one level. A long list of peers is already navigable, and a subheading dropped into it splits a set that belongs together. Mixing prose with the list, or nesting levels inside it, ends the exemption.
|
|
104
106
|
- Never cut a `## Decisions` or `## Gotchas` entry to shorten a file. Cut a `## Layout` or `## CLI` section instead.
|