@erclx/aitk 0.31.0 → 0.32.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 +24 -5
- package/src/context/audit.ts +78 -9
- package/standards/context.md +4 -3
package/docs/agents.md
CHANGED
|
@@ -391,11 +391,13 @@ Exit codes are `0` for a clean run, `1` for a refusal, and `2` for an unresolved
|
|
|
391
391
|
|
|
392
392
|
### What each check reports
|
|
393
393
|
|
|
394
|
-
Length and depth quote their checkpoints from `.claude/standards/context.md`: roughly 150 lines for an entry, roughly 40 for a run
|
|
394
|
+
Length and depth quote their checkpoints from `.claude/standards/context.md`: roughly 150 rendered lines for an entry, roughly 40 for a run no heading breaks. Depth measures the longest such run rather than everything under one `##`, skips fenced blocks so a markdown example does not read as three headings, and exempts a run whose lines are all list items at one indent averaging under 130 characters. The weight condition is what separates a scannable catalog of one-liners from a stack of paragraph-bullets, which reach the same count and read nothing alike.
|
|
395
|
+
|
|
396
|
+
Both checks count rendered lines rather than source lines, wrapping each line at 80 columns and summing the heights. Entries here are authored one line per bullet, so a block of fifteen paragraph-bullets occupies fifteen source lines and renders past sixty, which source counting cannot see. Measuring one checkpoint in each unit would put an entry length beside a run length that mean different things. Their exclusions still differ: the file measure counts fenced blocks and frontmatter, while the run measure skips a fence so an example cannot break the run around it. A reference-heavy entry therefore ranks by its examples, which the length legend states on every run. Runs count blank lines, which the standard leaves open, so a hand reader who drops them lands a line or two lower. Both sections state the width on every run, since a number in rendered lines cannot be reproduced without it.
|
|
395
397
|
|
|
396
398
|
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
399
|
|
|
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
|
|
400
|
+
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 the content checks read 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. Length is the exception, counting the whole file, so a reader applying the 150-rendered-line checkpoint against the body alone lands a few lines under what the tool reports.
|
|
399
401
|
|
|
400
402
|
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.
|
|
401
403
|
|
package/package.json
CHANGED
package/src/commands/context.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
type EntryReport,
|
|
5
5
|
LENGTH_CHECKPOINT,
|
|
6
6
|
measureFolders,
|
|
7
|
+
PEER_BULLET_CHECKPOINT,
|
|
8
|
+
RENDER_WIDTH,
|
|
7
9
|
RUN_CHECKPOINT,
|
|
8
10
|
} from '@/context/audit'
|
|
9
11
|
import { auditCitations, type CitationReport } from '@/context/citations'
|
|
@@ -160,6 +162,8 @@ async function runAudit(
|
|
|
160
162
|
lines: LENGTH_CHECKPOINT,
|
|
161
163
|
run: RUN_CHECKPOINT,
|
|
162
164
|
runCountsBlankLines: true,
|
|
165
|
+
renderWidth: RENDER_WIDTH,
|
|
166
|
+
peerBullet: PEER_BULLET_CHECKPOINT,
|
|
163
167
|
},
|
|
164
168
|
})}\n`,
|
|
165
169
|
)
|
|
@@ -241,6 +245,12 @@ function reportCitations(report: ScannedCitations): void {
|
|
|
241
245
|
|
|
242
246
|
function reportLength(entries: readonly EntryReport[]): void {
|
|
243
247
|
logStep('Length')
|
|
248
|
+
logInfo(
|
|
249
|
+
`Entries measure rendered lines at ${RENDER_WIDTH} columns, counting frontmatter and fenced blocks.`,
|
|
250
|
+
)
|
|
251
|
+
logInfo(
|
|
252
|
+
'A reference-heavy entry therefore ranks by its examples, which the depth check excludes.',
|
|
253
|
+
)
|
|
244
254
|
|
|
245
255
|
const over = entries
|
|
246
256
|
.filter((entry) => entry.lines > LENGTH_CHECKPOINT)
|
|
@@ -253,20 +263,29 @@ function reportLength(entries: readonly EntryReport[]): void {
|
|
|
253
263
|
|
|
254
264
|
logWarn(`${over.length} past the ${LENGTH_CHECKPOINT}-line checkpoint`)
|
|
255
265
|
pipeOutput(
|
|
256
|
-
over
|
|
266
|
+
over
|
|
267
|
+
.map((entry) => `${entry.rel} ${entry.lines} rendered lines`)
|
|
268
|
+
.join('\n'),
|
|
257
269
|
)
|
|
258
270
|
}
|
|
259
271
|
|
|
260
272
|
/**
|
|
261
|
-
* Names the blank-line convention on every run.
|
|
273
|
+
* Names the render width and the blank-line convention on every run.
|
|
262
274
|
*
|
|
263
275
|
* The standard settles heading level and fenced blocks and stops there, so a
|
|
264
276
|
* hand reader who drops blank lines lands a line or two below this number.
|
|
265
|
-
* Stating
|
|
277
|
+
* Stating both is what keeps the two measurements reconcilable, and the width
|
|
278
|
+
* matters more than the blank lines because a number counted in rendered lines
|
|
279
|
+
* cannot be reproduced without it.
|
|
266
280
|
*/
|
|
267
281
|
function reportDepth(entries: readonly EntryReport[]): void {
|
|
268
282
|
logStep('Depth')
|
|
269
|
-
logInfo(
|
|
283
|
+
logInfo(
|
|
284
|
+
`Runs measure rendered lines at ${RENDER_WIDTH} columns and count blank lines.`,
|
|
285
|
+
)
|
|
286
|
+
logInfo(
|
|
287
|
+
`Fenced blocks are excluded, and so are peer lists averaging under ${PEER_BULLET_CHECKPOINT} characters a bullet.`,
|
|
288
|
+
)
|
|
270
289
|
|
|
271
290
|
const over = entries
|
|
272
291
|
.filter((entry) => entry.longestRun > RUN_CHECKPOINT)
|
|
@@ -282,7 +301,7 @@ function reportDepth(entries: readonly EntryReport[]): void {
|
|
|
282
301
|
over
|
|
283
302
|
.map(
|
|
284
303
|
(entry) =>
|
|
285
|
-
`${entry.rel}:${entry.longestRunLine} ${entry.longestRun} lines unbroken`,
|
|
304
|
+
`${entry.rel}:${entry.longestRunLine} ${entry.longestRun} rendered lines unbroken`,
|
|
286
305
|
)
|
|
287
306
|
.join('\n'),
|
|
288
307
|
)
|
package/src/context/audit.ts
CHANGED
|
@@ -6,6 +6,27 @@ import type { AuditedFolder } from '@/context/folders'
|
|
|
6
6
|
export const LENGTH_CHECKPOINT = 150
|
|
7
7
|
export const RUN_CHECKPOINT = 40
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Columns a source line wraps at when rendered.
|
|
11
|
+
*
|
|
12
|
+
* Nothing in this repository sets a line width and entries are authored one
|
|
13
|
+
* line per bullet, so the rendered width is the viewer's rather than the file's.
|
|
14
|
+
* The common terminal and diff width is the reproducible choice, and the report
|
|
15
|
+
* legend states it so a reader can arrive at the same number by hand.
|
|
16
|
+
*/
|
|
17
|
+
export const RENDER_WIDTH = 80
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Characters a bullet averages before its list stops reading as a set of peers.
|
|
21
|
+
*
|
|
22
|
+
* The exemption below covers a flat catalog of one-liners and a stack of
|
|
23
|
+
* paragraphs equally, and only the first is navigable. Measured across this
|
|
24
|
+
* corpus the two shapes separate with nothing between roughly 100 and 170
|
|
25
|
+
* characters a bullet, so the midpoint splits the population rather than a
|
|
26
|
+
* continuum. It is a checkpoint like the two above, not a cap.
|
|
27
|
+
*/
|
|
28
|
+
export const PEER_BULLET_CHECKPOINT = 130
|
|
29
|
+
|
|
9
30
|
/**
|
|
10
31
|
* A table this size or larger whose first column mostly names artifacts reads
|
|
11
32
|
* as a catalog that grows a row per shipped thing, which is the shape the
|
|
@@ -56,7 +77,20 @@ export interface ProvenanceFinding {
|
|
|
56
77
|
|
|
57
78
|
export interface EntryReport {
|
|
58
79
|
readonly rel: string
|
|
80
|
+
/**
|
|
81
|
+
* Rendered lines across the whole file, counting frontmatter and fenced
|
|
82
|
+
* blocks. Both this and `longestRun` measure in the same unit, since the two
|
|
83
|
+
* checkpoints they feed sit in one section of the standard and a reader
|
|
84
|
+
* compares them.
|
|
85
|
+
*
|
|
86
|
+
* The exclusions differ on purpose. `longestRun` skips a fence so an example
|
|
87
|
+
* cannot break the run around it, and a file measure has no run to protect.
|
|
88
|
+
* Excluding fences here would change which entries report by one and would
|
|
89
|
+
* not reach the case that motivates it: the most fenced entry in the corpus
|
|
90
|
+
* runs 20 percent fenced and sits past the checkpoint either way.
|
|
91
|
+
*/
|
|
59
92
|
readonly lines: number
|
|
93
|
+
/** Rendered lines at `RENDER_WIDTH`, not source lines. */
|
|
60
94
|
readonly longestRun: number
|
|
61
95
|
/** First line of the longest run, or 0 when the entry has no run at all. */
|
|
62
96
|
readonly longestRunLine: number
|
|
@@ -90,24 +124,44 @@ function bodyLines(source: string): BodyLine[] {
|
|
|
90
124
|
*
|
|
91
125
|
* Every non-blank line has to be a list item at one indent. Prose mixed into
|
|
92
126
|
* the run or a nested level inside it ends the exemption, because either one
|
|
93
|
-
* means the block is no longer a flat set a reader can skim.
|
|
127
|
+
* means the block is no longer a flat set a reader can skim. Bullet count says
|
|
128
|
+
* nothing on its own, since a catalog of one-liners and a wall of paragraphs
|
|
129
|
+
* reach the same count and read nothing alike, so the average bullet is what
|
|
130
|
+
* decides whether the set is still skimmable.
|
|
94
131
|
*/
|
|
95
|
-
function
|
|
132
|
+
function isScannablePeerList(run: readonly BodyLine[]): boolean {
|
|
96
133
|
const indents = new Set<number>()
|
|
134
|
+
let items = 0
|
|
135
|
+
let characters = 0
|
|
97
136
|
|
|
98
137
|
for (const line of run) {
|
|
99
|
-
|
|
138
|
+
const text = line.text.trim()
|
|
139
|
+
if (text === '') continue
|
|
100
140
|
|
|
101
141
|
const match = line.text.match(LIST_ITEM)
|
|
102
142
|
if (!match) return false
|
|
103
143
|
indents.add(match[1].length)
|
|
144
|
+
items++
|
|
145
|
+
characters += text.length
|
|
104
146
|
}
|
|
105
147
|
|
|
106
|
-
|
|
148
|
+
if (indents.size !== 1) return false
|
|
149
|
+
|
|
150
|
+
return characters / items < PEER_BULLET_CHECKPOINT
|
|
107
151
|
}
|
|
108
152
|
|
|
109
153
|
/**
|
|
110
|
-
*
|
|
154
|
+
* Height a source line occupies once wrapped.
|
|
155
|
+
*
|
|
156
|
+
* A blank line renders as the gap it is rather than as nothing, which keeps it
|
|
157
|
+
* the distance the source measure already counted it as.
|
|
158
|
+
*/
|
|
159
|
+
function renderedHeight(text: string): number {
|
|
160
|
+
return Math.max(1, Math.ceil(text.length / RENDER_WIDTH))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Measures the longest run of lines no heading breaks, in rendered lines.
|
|
111
165
|
*
|
|
112
166
|
* Fenced blocks are skipped rather than treated as breaks, per the standard:
|
|
113
167
|
* they leave the count without ending the run, so prose either side of an
|
|
@@ -116,6 +170,11 @@ function isPeerList(run: readonly BodyLine[]): boolean {
|
|
|
116
170
|
* between signposts and a blank line is distance like any other. A hand reader
|
|
117
171
|
* measuring without them lands one or two lines lower, which the report legend
|
|
118
172
|
* states.
|
|
173
|
+
*
|
|
174
|
+
* Height is what a reader travels, and source lines only stand in for it while
|
|
175
|
+
* lines stay short. An entry authored one line per bullet puts a paragraph on
|
|
176
|
+
* each, so a block of fifteen bullets measures as fifteen and renders past
|
|
177
|
+
* sixty. Wrapping every line at a stated width is what closes that gap.
|
|
119
178
|
*/
|
|
120
179
|
function longestRun(lines: readonly BodyLine[]): {
|
|
121
180
|
length: number
|
|
@@ -132,9 +191,16 @@ function longestRun(lines: readonly BodyLine[]): {
|
|
|
132
191
|
// two headings rather than a stretch a reader travels, so it never counts.
|
|
133
192
|
const first = run.find((line) => line.text.trim() !== '')
|
|
134
193
|
|
|
135
|
-
if (first &&
|
|
136
|
-
|
|
137
|
-
|
|
194
|
+
if (first && !isScannablePeerList(run)) {
|
|
195
|
+
const height = run.reduce(
|
|
196
|
+
(sum, line) => sum + renderedHeight(line.text),
|
|
197
|
+
0,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if (height > longest) {
|
|
201
|
+
longest = height
|
|
202
|
+
longestLine = first.number
|
|
203
|
+
}
|
|
138
204
|
}
|
|
139
205
|
run = []
|
|
140
206
|
}
|
|
@@ -260,7 +326,10 @@ export function measureEntry(rel: string, source: string): EntryReport {
|
|
|
260
326
|
|
|
261
327
|
return {
|
|
262
328
|
rel,
|
|
263
|
-
lines: source
|
|
329
|
+
lines: source
|
|
330
|
+
.replace(/\n$/, '')
|
|
331
|
+
.split('\n')
|
|
332
|
+
.reduce((sum, text) => sum + renderedHeight(text), 0),
|
|
264
333
|
longestRun: run.length,
|
|
265
334
|
longestRunLine: run.line,
|
|
266
335
|
catalogTables: catalogTables(lines),
|
package/standards/context.md
CHANGED
|
@@ -100,9 +100,10 @@ Only the `development` entry carries this section. It is not a general-purpose h
|
|
|
100
100
|
## Length
|
|
101
101
|
|
|
102
102
|
- Aim for one entry per domain. There is no hard cap. Length is a symptom, not the defect.
|
|
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.
|
|
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.
|
|
105
|
-
-
|
|
103
|
+
- Past roughly 150 rendered 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.
|
|
104
|
+
- Past roughly 40 rendered 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.
|
|
105
|
+
- Both checkpoints count rendered lines, so wrap each source line at 80 columns and sum the heights. Source lines undercount an entry authored one line per bullet, where a block of fifteen paragraph-bullets occupies fifteen lines and renders past sixty. Counting the two checkpoints in different units would put a file measured one way beside a run measured another.
|
|
106
|
+
- Exempt a block whose lines are all list items at one level averaging under roughly 130 characters. A flat list of short peers is already navigable, and a subheading dropped into it splits a set that belongs together. Bullet count says nothing on its own, since a catalog of one-liners and a stack of paragraphs reach the same count and read nothing alike, so weight is what decides. Mixing prose with the list, or nesting levels inside it, ends the exemption at any weight.
|
|
106
107
|
- Never cut a `## Decisions` or `## Gotchas` entry to shorten a file. Cut a `## Layout` or `## CLI` section instead.
|
|
107
108
|
- Split into a folder (`.claude/context/<domain>/<sub-area>.md`) when a domain has three or more sub-areas that do not fit cleanly in one file. That split is the natural ceiling.
|
|
108
109
|
- Keep a split domain's `index.md` generated. The catalog body is rewritten on every regen, so the domain's own overview and layout belong in a sibling file rather than in it, and the `subtitle` is what names the file to start with.
|