@erclx/aitk 0.106.0 → 0.107.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.
|
@@ -39,6 +39,12 @@ A line is wrapped at the width it renders at, so a link counts as its anchor tex
|
|
|
39
39
|
|
|
40
40
|
The measure counts fenced blocks and frontmatter, so a reference-heavy entry ranks by its examples, which the legend states on every run alongside the width, since a number in rendered lines cannot be reproduced without it.
|
|
41
41
|
|
|
42
|
+
Every entry past the checkpoint carries the three questions the standard asks of it rather than a count alone: whether it still covers one domain, whether it has filled with content `ls` or `--help` reproduces, and whether it has accumulated the history of its own changes. The checkpoint is not a cap, so the count is not the finding. What a reader acts on is which question came back true, and the standard directs the fix at that rather than at the number.
|
|
43
|
+
|
|
44
|
+
Only the third is mechanical, and it is the provenance check already reported below, so the length finding cites that count rather than measuring it again. An entry outside the governed folder reads open there too, since provenance is scoped to the standard stating it and a clean list there is a scan that never ran. The other two stay open beside every entry, because whether a domain is still one domain is a judgment about its subject and recognizing reproduced content needs a reader who knows what those commands emit. Omitting them would read as an entry nothing found rather than one nothing checked.
|
|
45
|
+
|
|
46
|
+
It reports and never gates, which the standard settles rather than the usual judgment-against-fact line: there is no hard cap, so an entry that answers all three and is still long is a correct outcome. The JSON record carries the joined findings as `length`, each with a `causes` array in the standard's order, and the jurisdiction the join reads as `entries[].governed`. That flag is what parts a provenance list measured and empty from one never scanned, which the count alone cannot say. The join is published rather than left to a consumer, since deriving it means restating which question the provenance count answers.
|
|
47
|
+
|
|
42
48
|
Depth and bullet weight are quoted from `.claude/standards/markdown.md`, which states both over every markdown file rather than over a context entry, so `aitk markdown audit` measures them and this command no longer does. They share `renderedHeight` with the length measure, since the two checkpoints sit in one section of that standard and a reader compares them. What the split costs is that a session wanting both numbers for one entry runs two commands, and what it buys is that either number can be had for a file in a folder this audit refuses to resolve.
|
|
43
49
|
|
|
44
50
|
## Tables
|
package/package.json
CHANGED
package/src/commands/context.ts
CHANGED
|
@@ -3,6 +3,10 @@ import type { Command } from 'commander'
|
|
|
3
3
|
import {
|
|
4
4
|
type EntryReport,
|
|
5
5
|
governsContent,
|
|
6
|
+
type LengthCause,
|
|
7
|
+
type LengthFinding,
|
|
8
|
+
lengthFindings,
|
|
9
|
+
type LengthQuestion,
|
|
6
10
|
LENGTH_CHECKPOINT,
|
|
7
11
|
matchesSiblings,
|
|
8
12
|
measureFolders,
|
|
@@ -200,6 +204,7 @@ async function runAudit(
|
|
|
200
204
|
)
|
|
201
205
|
const drift = gateOnly ? [] : await auditIndexes(folders)
|
|
202
206
|
const sections = gateOnly ? [] : missingSections(root, folders, entries)
|
|
207
|
+
const length = gateOnly ? undefined : lengthFindings(entries)
|
|
203
208
|
|
|
204
209
|
if (gateOnly) {
|
|
205
210
|
reportGate(citations)
|
|
@@ -209,7 +214,7 @@ async function runAudit(
|
|
|
209
214
|
reportCitations(citations, cited)
|
|
210
215
|
reportReferenceForm(entries, folders)
|
|
211
216
|
reportSections(sections, folders)
|
|
212
|
-
reportLength(
|
|
217
|
+
reportLength(length ?? [])
|
|
213
218
|
reportTables(entries)
|
|
214
219
|
reportProvenance(entries, folders)
|
|
215
220
|
reportNarration(entries, folders, narration)
|
|
@@ -234,6 +239,16 @@ async function runAudit(
|
|
|
234
239
|
unresolved: citations.unresolved,
|
|
235
240
|
},
|
|
236
241
|
entries,
|
|
242
|
+
// The join is published rather than left to a consumer, since deriving
|
|
243
|
+
// it from `entries` means restating which question the provenance count
|
|
244
|
+
// answers, and one wrong restatement is a cause reported against the
|
|
245
|
+
// wrong entry.
|
|
246
|
+
//
|
|
247
|
+
// Absent rather than empty under `--citations-only`, for the reason
|
|
248
|
+
// `checkpoints.narration` below carries. That mode measures no entry,
|
|
249
|
+
// so an empty array here reads as a corpus with nothing past the
|
|
250
|
+
// checkpoint rather than as a run that never looked.
|
|
251
|
+
length,
|
|
237
252
|
missingSections: sections,
|
|
238
253
|
indexDrift: drift,
|
|
239
254
|
checkpoints: {
|
|
@@ -475,7 +490,32 @@ function reportSections(
|
|
|
475
490
|
)
|
|
476
491
|
}
|
|
477
492
|
|
|
478
|
-
|
|
493
|
+
/** How each question reads in the report, in the standard's own order. */
|
|
494
|
+
const QUESTION_LABEL: Record<LengthQuestion, string> = {
|
|
495
|
+
domain: 'one domain',
|
|
496
|
+
reproduced: 'reproduced content',
|
|
497
|
+
history: 'own history',
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function readCause(cause: LengthCause): string {
|
|
501
|
+
const label = QUESTION_LABEL[cause.question]
|
|
502
|
+
|
|
503
|
+
if (cause.state === 'unanswered') return `${label}: open`
|
|
504
|
+
if (cause.state === 'no') return `${label}: no`
|
|
505
|
+
|
|
506
|
+
return `${label}: yes, ${plural(cause.markers ?? 0, 'marker')}`
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Names each entry past the checkpoint with the three questions the standard
|
|
511
|
+
* asks of it, rather than a count a reader has to cross-reference by eye.
|
|
512
|
+
*
|
|
513
|
+
* The checkpoint is not a cap, so the legend says what the list is for. An
|
|
514
|
+
* entry that answers all three and is still long is a correct outcome under a
|
|
515
|
+
* standard stating there is no hard cap, and a run reporting success as a
|
|
516
|
+
* smaller count would have adopted the number the standard declines.
|
|
517
|
+
*/
|
|
518
|
+
function reportLength(over: readonly LengthFinding[]): void {
|
|
479
519
|
logStep('Length')
|
|
480
520
|
logInfo(
|
|
481
521
|
`Entries measure rendered lines at ${RENDER_WIDTH} columns, counting frontmatter and fenced blocks.`,
|
|
@@ -484,19 +524,26 @@ function reportLength(entries: readonly EntryReport[]): void {
|
|
|
484
524
|
'A reference-heavy entry therefore ranks by its examples, which the depth check in `aitk markdown audit` excludes.',
|
|
485
525
|
)
|
|
486
526
|
|
|
487
|
-
const over = entries
|
|
488
|
-
.filter((entry) => entry.lines > LENGTH_CHECKPOINT)
|
|
489
|
-
.sort((a, b) => b.lines - a.lines)
|
|
490
|
-
|
|
491
527
|
if (over.length === 0) {
|
|
492
528
|
logInfo(`No entry past the ${LENGTH_CHECKPOINT}-line checkpoint.`)
|
|
493
529
|
return
|
|
494
530
|
}
|
|
495
531
|
|
|
496
532
|
logWarn(`${over.length} past the ${LENGTH_CHECKPOINT}-line checkpoint`)
|
|
533
|
+
logInfo(
|
|
534
|
+
'The checkpoint is not a cap. Each entry carries the three questions the standard asks past it, and the fix goes to whichever is true.',
|
|
535
|
+
)
|
|
536
|
+
logInfo(
|
|
537
|
+
'Own history is answered from the provenance markers below. The other two are judgments no measure settles, so they stay open for a reader.',
|
|
538
|
+
)
|
|
497
539
|
pipeOutput(
|
|
498
540
|
over
|
|
499
|
-
.map(
|
|
541
|
+
.map(
|
|
542
|
+
(finding) =>
|
|
543
|
+
`${finding.rel} ${finding.lines} rendered lines\n ${finding.causes
|
|
544
|
+
.map(readCause)
|
|
545
|
+
.join(' ')}`,
|
|
546
|
+
)
|
|
500
547
|
.join('\n'),
|
|
501
548
|
)
|
|
502
549
|
}
|
package/src/context/audit.ts
CHANGED
|
@@ -166,6 +166,52 @@ export interface EntryReport {
|
|
|
166
166
|
* exempt from owing sections rather than from being well formed.
|
|
167
167
|
*/
|
|
168
168
|
readonly stub: boolean
|
|
169
|
+
/**
|
|
170
|
+
* Whether a content rule reached this entry, which is what parts an empty
|
|
171
|
+
* `provenance` list that was measured from one that was never scanned. The
|
|
172
|
+
* length finding reads it, since reporting a clean history for an entry
|
|
173
|
+
* outside the governed folder answers a question nothing asked.
|
|
174
|
+
*/
|
|
175
|
+
readonly governed: boolean
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The three questions `standards/context.md` asks of an entry past the
|
|
180
|
+
* checkpoint, in the order it states them.
|
|
181
|
+
*
|
|
182
|
+
* The standard calls no entry over the checkpoint wrong. It asks whether the
|
|
183
|
+
* entry still covers a single domain, whether it has filled with content `ls`
|
|
184
|
+
* or `--help` reproduces, and whether it has accumulated the history of its own
|
|
185
|
+
* changes, then directs a fix at whichever is true. A report naming the count
|
|
186
|
+
* alone leaves all three unasked, which is why the finding carries them.
|
|
187
|
+
*/
|
|
188
|
+
export type LengthQuestion = 'domain' | 'reproduced' | 'history'
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* What the audit can say about one question for one entry.
|
|
192
|
+
*
|
|
193
|
+
* `unanswered` is a state rather than an omission. Two of the three questions
|
|
194
|
+
* are judgments no measure settles, and dropping them would read as an entry
|
|
195
|
+
* nothing found rather than one nothing checked.
|
|
196
|
+
*/
|
|
197
|
+
export type QuestionState = 'yes' | 'no' | 'unanswered'
|
|
198
|
+
|
|
199
|
+
export interface LengthCause {
|
|
200
|
+
readonly question: LengthQuestion
|
|
201
|
+
readonly state: QuestionState
|
|
202
|
+
/**
|
|
203
|
+
* Markers behind a `yes`, and absent wherever nothing was counted. It cites
|
|
204
|
+
* the provenance finding rather than restating it, so the two sections of the
|
|
205
|
+
* report describe the same measurement once.
|
|
206
|
+
*/
|
|
207
|
+
readonly markers?: number
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface LengthFinding {
|
|
211
|
+
readonly rel: string
|
|
212
|
+
readonly lines: number
|
|
213
|
+
/** One entry per question in `LengthQuestion` order, never empty. */
|
|
214
|
+
readonly causes: readonly LengthCause[]
|
|
169
215
|
}
|
|
170
216
|
|
|
171
217
|
export interface SectionFinding {
|
|
@@ -481,9 +527,52 @@ export function measureEntry(
|
|
|
481
527
|
bareReferences: bareReferences(lines, siblings),
|
|
482
528
|
sections: governsContent ? declaredSections(lines) : [],
|
|
483
529
|
stub: isStubSeed(source),
|
|
530
|
+
governed: governsContent,
|
|
484
531
|
}
|
|
485
532
|
}
|
|
486
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Names each entry past the checkpoint with the standard's three questions
|
|
536
|
+
* answered as far as anything measures them.
|
|
537
|
+
*
|
|
538
|
+
* Only accumulated history is mechanical, and it is already measured by the
|
|
539
|
+
* provenance check, so this joins that count rather than counting again. The
|
|
540
|
+
* other two are read by a person: whether an entry still covers one domain is a
|
|
541
|
+
* judgment about its subject, and recognizing content `ls` or `--help`
|
|
542
|
+
* reproduces needs a reader who knows what those emit.
|
|
543
|
+
*
|
|
544
|
+
* An entry outside the governed folder has no measured question at all, since
|
|
545
|
+
* provenance is scoped to the standard stating it, and reporting `no` there
|
|
546
|
+
* would answer from a scan that never ran.
|
|
547
|
+
*
|
|
548
|
+
* Sorted longest first, which is the order the report already printed and the
|
|
549
|
+
* order the questions are worth asking in.
|
|
550
|
+
*/
|
|
551
|
+
export function lengthFindings(
|
|
552
|
+
entries: readonly EntryReport[],
|
|
553
|
+
): LengthFinding[] {
|
|
554
|
+
return entries
|
|
555
|
+
.filter((entry) => entry.lines > LENGTH_CHECKPOINT)
|
|
556
|
+
.sort((a, b) => b.lines - a.lines)
|
|
557
|
+
.map((entry) => ({
|
|
558
|
+
rel: entry.rel,
|
|
559
|
+
lines: entry.lines,
|
|
560
|
+
causes: [
|
|
561
|
+
{ question: 'domain', state: 'unanswered' },
|
|
562
|
+
{ question: 'reproduced', state: 'unanswered' },
|
|
563
|
+
historyCause(entry),
|
|
564
|
+
] satisfies LengthCause[],
|
|
565
|
+
}))
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function historyCause(entry: EntryReport): LengthCause {
|
|
569
|
+
if (!entry.governed) return { question: 'history', state: 'unanswered' }
|
|
570
|
+
|
|
571
|
+
return entry.provenance.length > 0
|
|
572
|
+
? { question: 'history', state: 'yes', markers: entry.provenance.length }
|
|
573
|
+
: { question: 'history', state: 'no' }
|
|
574
|
+
}
|
|
575
|
+
|
|
487
576
|
/**
|
|
488
577
|
* Measures every entry in the audited folders. A generated `index.md` is not
|
|
489
578
|
* among them, since its body is rewritten on every regen and no checkpoint
|