@erclx/canon 5.62.0 → 5.64.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/CHANGELOG.md +26 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/references/adopted.md +1 -1
- package/claude/skills/context-fold/REQUIREMENT.md +2 -3
- package/claude/skills/context-fold/SKILL.md +1 -11
- package/claude/skills/context-fold/references/completion-report.md +8 -9
- package/claude/skills/create-skill/references/skill-practice.md +17 -1
- package/claude/skills/design-record/references/design.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +2 -1
- package/claude/skills/design-taste/SKILL.md +6 -5
- package/claude/skills/design-taste/references/adopted.md +56 -8
- package/claude/skills/design-taste/references/craft.md +1 -1
- package/claude/skills/design-taste/references/kinds.md +8 -2
- package/claude/skills/design-taste/references/preflight.md +3 -3
- package/claude/skills/design-taste/references/tells.md +26 -5
- package/claude/skills/document-health/REQUIREMENT.md +1 -1
- package/claude/skills/document-health/SKILL.md +3 -14
- package/claude/skills/document-health/references/axes.md +14 -16
- package/claude/skills/plan-feature/SKILL.md +2 -2
- package/claude/skills/plan-feature/references/plan.md +24 -3
- package/claude/skills/review-craft/SKILL.md +1 -1
- package/claude/skills/role-worker/SKILL.md +2 -1
- package/claude/skills/ui-checklist/SKILL.md +1 -1
- package/claude/skills/ui-craft/REQUIREMENT.md +29 -0
- package/claude/skills/ui-craft/SKILL.md +44 -0
- package/{governance/rules/ui → claude/skills/ui-craft/references}/a11y.md +0 -10
- package/claude/skills/ui-craft/references/adopted.md +27 -0
- package/{governance/rules/ui/surface-capture.md → claude/skills/ui-craft/references/capture.md} +0 -10
- package/{governance/rules/ui/ux-controls.md → claude/skills/ui-craft/references/controls.md} +3 -10
- package/claude/skills/ui-craft/references/copy.md +45 -0
- package/{governance/rules/ui → claude/skills/ui-craft/references}/forms.md +0 -9
- package/{governance/rules/ui → claude/skills/ui-craft/references}/keyboard.md +0 -10
- package/{governance/rules/ui → claude/skills/ui-craft/references}/motion.md +0 -10
- package/{governance/rules/ui → claude/skills/ui-craft/references}/pointer.md +0 -10
- package/{governance/rules/ui/ux-completeness.md → claude/skills/ui-craft/references/states.md} +0 -10
- package/claude/skills/ux-audit/SKILL.md +1 -1
- package/claude/skills/write-human/references/adopted.md +4 -1
- package/docs/agents/audits.md +2 -6
- package/docs/agents/context-audit.md +1 -1
- package/docs/agents/install-and-sync.md +2 -2
- package/docs/target/migrations.md +3 -3
- package/docs/target/sync.md +3 -2
- package/docs/workflow/skill-map.md +1 -0
- package/docs/workflow/visual-design-workflow.md +2 -2
- package/governance/detect.toml +1 -45
- package/governance/ledger/lib/test-scope.md +1 -1
- package/governance/ledger/skills/api-design/SKILL.md +62 -0
- package/governance/ledger/skills/code-craft/SKILL.md +53 -0
- package/governance/ledger/skills/deprecation-migration/SKILL.md +68 -0
- package/governance/ledger/skills/design-taste/SKILL.md +64 -0
- package/governance/ledger/skills/design-taste/craft.md +66 -0
- package/governance/ledger/skills/design-taste/kinds.md +58 -0
- package/governance/ledger/skills/design-taste/systems.md +42 -0
- package/governance/ledger/skills/design-taste/tells.md +103 -0
- package/governance/ledger/{ui → skills/ui-craft}/a11y.md +3 -3
- package/governance/ledger/{ui/surface-capture.md → skills/ui-craft/capture.md} +3 -3
- package/governance/ledger/{ui/ux-controls.md → skills/ui-craft/controls.md} +12 -7
- package/governance/ledger/skills/ui-craft/copy.md +102 -0
- package/governance/ledger/{ui → skills/ui-craft}/forms.md +3 -3
- package/governance/ledger/{ui → skills/ui-craft}/keyboard.md +3 -3
- package/governance/ledger/{ui → skills/ui-craft}/motion.md +3 -3
- package/governance/ledger/{ui → skills/ui-craft}/pointer.md +3 -3
- package/governance/ledger/{ui/ux-completeness.md → skills/ui-craft/states.md} +3 -3
- package/governance/ledger/ui/craft.md +19 -0
- package/governance/renames.toml +17 -3
- package/governance/rules/lib/test-scope.md +1 -1
- package/governance/rules/ui/craft.md +22 -0
- package/governance/splits.toml +0 -2
- package/package.json +1 -1
- package/src/audits/catalog.ts +0 -52
- package/src/claude/cases/misc.ts +5 -0
- package/src/claude/skills-audit.ts +226 -12
- package/src/cli/retired.ts +8 -0
- package/src/commands/claude.ts +15 -1
- package/src/commands/context.ts +0 -425
- package/src/commands/tooling.ts +4 -1
- package/src/context/citations.ts +9 -3
- package/src/gate/measures.ts +4 -3
- package/src/git/files.ts +8 -3
- package/src/gov/audit.ts +6 -1
- package/src/inventory/subjects.ts +1 -1
- package/src/markdown/gated.ts +1 -1
- package/src/markdown/scope.ts +0 -1
- package/src/tooling/package.ts +22 -13
- package/tooling/web/reference.md +3 -1
- package/claude/skills/context-fold/references/classify.md +0 -87
- package/governance/ledger/ui/copy.md +0 -49
- package/governance/ledger/ui/design-taste.md +0 -19
- package/governance/ledger/ui/interface-casing.md +0 -47
- package/governance/ledger/ui/link-behavior.md +0 -29
- package/governance/rules/ui/copy.md +0 -28
- package/governance/rules/ui/design-taste.md +0 -18
- package/governance/rules/ui/interface-casing.md +0 -29
- package/governance/rules/ui/link-behavior.md +0 -19
- package/src/context/classify/extract.ts +0 -443
- package/src/context/classify/ollama.ts +0 -172
- package/src/context/classify/patterns.ts +0 -75
- package/src/context/classify/prompts.ts +0 -74
- package/src/context/classify/run.ts +0 -348
- package/src/context/classify/settings.ts +0 -196
- /package/tooling/web/{configs → seeds}/e2e/home.spec.ts +0 -0
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
|
-
import { dirname, join } from 'node:path'
|
|
2
|
+
import { dirname, join, relative } from 'node:path'
|
|
3
3
|
import { isFamilyKey } from '@/claude/skills-families'
|
|
4
|
+
import { plural } from '@/cli/ui'
|
|
5
|
+
import {
|
|
6
|
+
compareLedger,
|
|
7
|
+
countLedgerRows,
|
|
8
|
+
type HeadingCount,
|
|
9
|
+
type LedgerMismatch,
|
|
10
|
+
} from '@/gov/audit'
|
|
4
11
|
import { parseFrontmatter, readField } from '@/indexes/frontmatter'
|
|
5
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
type BodyLine,
|
|
14
|
+
bodyLines,
|
|
15
|
+
maskCodeSpans,
|
|
16
|
+
maskDisplayed,
|
|
17
|
+
} from '@/markdown/scan'
|
|
6
18
|
|
|
7
19
|
/** Returned when a skill folder carries no `REQUIREMENT.md`, the gating check. */
|
|
8
20
|
export const EXIT_MISSING_REQUIREMENT = 2
|
|
@@ -69,8 +81,52 @@ export const LEDGER_SKILLS: readonly string[] = [
|
|
|
69
81
|
join(SHIPPED_CORPUS, 'design-taste'),
|
|
70
82
|
join(SHIPPED_CORPUS, 'write-human'),
|
|
71
83
|
join(SHIPPED_CORPUS, 'test-first'),
|
|
84
|
+
join(SHIPPED_CORPUS, 'ui-craft'),
|
|
72
85
|
]
|
|
73
86
|
|
|
87
|
+
function references(...names: string[]): string[] {
|
|
88
|
+
return names.map((name) => join('references', `${name}.md`))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface BulletLedgerScope {
|
|
92
|
+
/** References left off, each a checklist or glossary naming no new source. */
|
|
93
|
+
readonly exempt: readonly string[]
|
|
94
|
+
/** True when `SKILL.md` carries directives of its own to ledger. */
|
|
95
|
+
readonly body: boolean
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The ledger-listed skills whose every reference but the source record carries
|
|
100
|
+
* a ledger of its own, one table row per bullet under each H2, the way a rule
|
|
101
|
+
* in a practice folder does. A reference is ledgered unless exempted by name,
|
|
102
|
+
* so one added later fails until its rows land rather than passing unread. The
|
|
103
|
+
* ledgers sit outside the plugin as rule ledgers do, so a target pays nothing
|
|
104
|
+
* for them.
|
|
105
|
+
*/
|
|
106
|
+
export const BULLET_LEDGER_SKILLS: Readonly<Record<string, BulletLedgerScope>> =
|
|
107
|
+
{
|
|
108
|
+
[join(SHIPPED_CORPUS, 'ui-craft')]: { exempt: [], body: false },
|
|
109
|
+
[join(SHIPPED_CORPUS, 'api-design')]: { exempt: [], body: true },
|
|
110
|
+
[join(SHIPPED_CORPUS, 'code-craft')]: { exempt: [], body: true },
|
|
111
|
+
[join(SHIPPED_CORPUS, 'deprecation-migration')]: { exempt: [], body: true },
|
|
112
|
+
[join(SHIPPED_CORPUS, 'design-taste')]: {
|
|
113
|
+
exempt: references('preflight', 'vocabulary'),
|
|
114
|
+
body: true,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Where a per-bullet ledger lives, as `<skill>/<file>.md` below it. */
|
|
119
|
+
export const SKILL_LEDGERS = join('governance', 'ledger', 'skills')
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The ledger a ledgered file pairs with, keyed by the file's own name, so a
|
|
123
|
+
* reference drops its `references/` segment and a `SKILL.md` keeps its name.
|
|
124
|
+
*/
|
|
125
|
+
export function skillLedgerPath(folder: string, file: string): string {
|
|
126
|
+
const inside = relative('references', file)
|
|
127
|
+
return join(SKILL_LEDGERS, folder, inside.startsWith('..') ? file : inside)
|
|
128
|
+
}
|
|
129
|
+
|
|
74
130
|
/** The closing H2s a practice skill carries, matched exactly outside fences. */
|
|
75
131
|
export const PRACTICE_SECTIONS: readonly string[] = [
|
|
76
132
|
'Excuses and rebuttals',
|
|
@@ -78,9 +134,32 @@ export const PRACTICE_SECTIONS: readonly string[] = [
|
|
|
78
134
|
'Before handing over',
|
|
79
135
|
]
|
|
80
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The H2s that end a listed body's concern groups. The closing sections
|
|
139
|
+
* restate the groups' rules as excuses, signs, and checks, and the delegates
|
|
140
|
+
* section routes, so neither states a rule of its own to source.
|
|
141
|
+
*/
|
|
142
|
+
export const BODY_CLOSERS: readonly string[] = [
|
|
143
|
+
...PRACTICE_SECTIONS,
|
|
144
|
+
'What this delegates',
|
|
145
|
+
]
|
|
146
|
+
|
|
81
147
|
/** The source ledger every practice skill carries beside its body. */
|
|
82
148
|
export const PRACTICE_LEDGER = join('references', 'adopted.md')
|
|
83
149
|
|
|
150
|
+
/**
|
|
151
|
+
* A ledger cites a source by linking a page outside the project, as an inline
|
|
152
|
+
* link or an autolink. A relative link points back into the skill and cites
|
|
153
|
+
* nothing a reader could check the rule against.
|
|
154
|
+
*/
|
|
155
|
+
const EXTERNAL_LINK = /\]\(\s*https?:\/\/|<https?:\/\//
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A table cell opening on the marker the governance ledgers already use for a
|
|
159
|
+
* rule the toolkit chose, so one check reads every ledger alike.
|
|
160
|
+
*/
|
|
161
|
+
const HOUSE_CHOICE = /\|\s*House choice\b/
|
|
162
|
+
|
|
84
163
|
/**
|
|
85
164
|
* The line count past which a reference opens with a contents list, from
|
|
86
165
|
* Anthropic's skill authoring guidance. Counted over the whole file the way
|
|
@@ -118,6 +197,12 @@ const ISSUE_NUMBER = /(?<![\w&/#])#\d+\b/g
|
|
|
118
197
|
|
|
119
198
|
const TOP_LEVEL_BULLET = /^(?:[-*+]|\d+[.)])\s/
|
|
120
199
|
|
|
200
|
+
/**
|
|
201
|
+
* A line a skill ledger pairs with a row: a top-level `- ` bullet as a rule
|
|
202
|
+
* writes one, or a top-level ordered step, which states a directive as surely.
|
|
203
|
+
*/
|
|
204
|
+
const LEDGERED_LINE = /^(?:- |\d+[.)]\s)/
|
|
205
|
+
|
|
121
206
|
const WORD = /\S*[\p{L}\p{N}]\S*/gu
|
|
122
207
|
|
|
123
208
|
export interface SkillFinding {
|
|
@@ -166,13 +251,25 @@ interface SkillSource {
|
|
|
166
251
|
readonly requirement: string | undefined
|
|
167
252
|
/** Every H2 in `SKILL.md` outside a fence, for the practice shape. */
|
|
168
253
|
readonly sections: readonly string[]
|
|
169
|
-
|
|
254
|
+
/** Undefined when the folder carries no ledger at all. */
|
|
255
|
+
readonly ledger: string | undefined
|
|
170
256
|
/**
|
|
171
257
|
* `SKILL.md` and every reference below the folder, keyed by the path a
|
|
172
258
|
* finding names. `REQUIREMENT.md` and `EVAL.md` are left out, since the
|
|
173
259
|
* requirement is where the standard sends a date.
|
|
174
260
|
*/
|
|
175
261
|
readonly bodies: readonly SkillText[]
|
|
262
|
+
/** One entry per ledgered file of a skill on the per-bullet ledger list. */
|
|
263
|
+
readonly referenceLedgers: readonly ReferenceLedger[]
|
|
264
|
+
/** Exempted references the skill no longer carries, which exempt nothing. */
|
|
265
|
+
readonly staleExemptions: readonly string[]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
interface ReferenceLedger {
|
|
269
|
+
readonly rel: string
|
|
270
|
+
readonly ledgerRel: string
|
|
271
|
+
/** Undefined when the ledger file is absent. */
|
|
272
|
+
readonly mismatches: readonly LedgerMismatch[] | undefined
|
|
176
273
|
}
|
|
177
274
|
|
|
178
275
|
interface SkillText {
|
|
@@ -195,7 +292,7 @@ export async function auditSkills(root: string): Promise<SkillsAudit> {
|
|
|
195
292
|
)
|
|
196
293
|
|
|
197
294
|
const perCorpus = await Promise.all(
|
|
198
|
-
present.map((corpus) => readCorpus(corpus.rel, corpus.dir)),
|
|
295
|
+
present.map((corpus) => readCorpus(root, corpus.rel, corpus.dir)),
|
|
199
296
|
)
|
|
200
297
|
const sources = perCorpus.flat()
|
|
201
298
|
|
|
@@ -247,21 +344,29 @@ export function auditExitCode(report: SkillsAudit): number {
|
|
|
247
344
|
return report.missingRequirement.length > 0 ? EXIT_MISSING_REQUIREMENT : 0
|
|
248
345
|
}
|
|
249
346
|
|
|
250
|
-
async function readCorpus(
|
|
347
|
+
async function readCorpus(
|
|
348
|
+
root: string,
|
|
349
|
+
rel: string,
|
|
350
|
+
dir: string,
|
|
351
|
+
): Promise<SkillSource[]> {
|
|
251
352
|
const paths = [
|
|
252
353
|
...new Bun.Glob('*/SKILL.md').scanSync({ cwd: dir, onlyFiles: true }),
|
|
253
354
|
].sort()
|
|
254
355
|
|
|
255
|
-
return Promise.all(
|
|
356
|
+
return Promise.all(
|
|
357
|
+
paths.map((path) => readSkill(root, rel, dir, dirname(path))),
|
|
358
|
+
)
|
|
256
359
|
}
|
|
257
360
|
|
|
258
361
|
async function readSkill(
|
|
362
|
+
root: string,
|
|
259
363
|
rel: string,
|
|
260
364
|
dir: string,
|
|
261
365
|
folder: string,
|
|
262
366
|
): Promise<SkillSource> {
|
|
263
367
|
const skillDir = join(dir, folder)
|
|
264
368
|
const requirementPath = join(skillDir, 'REQUIREMENT.md')
|
|
369
|
+
const ledgerPath = join(skillDir, PRACTICE_LEDGER)
|
|
265
370
|
|
|
266
371
|
const references = [
|
|
267
372
|
...new Bun.Glob('references/**/*.md').scanSync({
|
|
@@ -270,11 +375,14 @@ async function readSkill(
|
|
|
270
375
|
}),
|
|
271
376
|
].sort()
|
|
272
377
|
|
|
273
|
-
const [body, requirement, referenceTexts] = await Promise.all([
|
|
378
|
+
const [body, requirement, ledger, referenceTexts] = await Promise.all([
|
|
274
379
|
Bun.file(join(skillDir, 'SKILL.md')).text(),
|
|
275
380
|
existsSync(requirementPath)
|
|
276
381
|
? Bun.file(requirementPath).text()
|
|
277
382
|
: Promise.resolve(undefined),
|
|
383
|
+
existsSync(ledgerPath)
|
|
384
|
+
? Bun.file(ledgerPath).text()
|
|
385
|
+
: Promise.resolve(undefined),
|
|
278
386
|
Promise.all(
|
|
279
387
|
references.map((path) => Bun.file(join(skillDir, path)).text()),
|
|
280
388
|
),
|
|
@@ -282,6 +390,22 @@ async function readSkill(
|
|
|
282
390
|
|
|
283
391
|
const fields = parseFrontmatter(body)
|
|
284
392
|
const skillRel = join(rel, folder)
|
|
393
|
+
const scope = BULLET_LEDGER_SKILLS[skillRel]
|
|
394
|
+
const referenceLedgers =
|
|
395
|
+
scope === undefined
|
|
396
|
+
? []
|
|
397
|
+
: await readReferenceLedgers(root, skillRel, folder, [
|
|
398
|
+
...(scope.body ? [{ path: 'SKILL.md', text: body }] : []),
|
|
399
|
+
...references
|
|
400
|
+
.map((path, index) => ({ path, text: referenceTexts[index] ?? '' }))
|
|
401
|
+
.filter(
|
|
402
|
+
({ path }) =>
|
|
403
|
+
path !== PRACTICE_LEDGER && !scope.exempt.includes(path),
|
|
404
|
+
),
|
|
405
|
+
])
|
|
406
|
+
const staleExemptions = (scope?.exempt ?? [])
|
|
407
|
+
.filter((path) => !references.includes(path))
|
|
408
|
+
.map((path) => join(skillRel, path))
|
|
285
409
|
|
|
286
410
|
return {
|
|
287
411
|
rel: skillRel,
|
|
@@ -294,7 +418,7 @@ async function readSkill(
|
|
|
294
418
|
requirement === undefined ? undefined : headings(requirement),
|
|
295
419
|
requirement,
|
|
296
420
|
sections: sectionsOf(body),
|
|
297
|
-
|
|
421
|
+
ledger,
|
|
298
422
|
bodies: [
|
|
299
423
|
{ rel: join(skillRel, 'SKILL.md'), text: body, isReference: false },
|
|
300
424
|
...references.map((path, index) => ({
|
|
@@ -303,9 +427,62 @@ async function readSkill(
|
|
|
303
427
|
isReference: true,
|
|
304
428
|
})),
|
|
305
429
|
],
|
|
430
|
+
referenceLedgers,
|
|
431
|
+
staleExemptions,
|
|
306
432
|
}
|
|
307
433
|
}
|
|
308
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Pairs each ledgered file's directives with its ledger's rows through the rule
|
|
437
|
+
* audit's own comparison, so a skill file and a rule mismatch alike.
|
|
438
|
+
*/
|
|
439
|
+
async function readReferenceLedgers(
|
|
440
|
+
root: string,
|
|
441
|
+
skillRel: string,
|
|
442
|
+
folder: string,
|
|
443
|
+
files: readonly { path: string; text: string }[],
|
|
444
|
+
): Promise<ReferenceLedger[]> {
|
|
445
|
+
return Promise.all(
|
|
446
|
+
files.map(async ({ path, text }) => {
|
|
447
|
+
const ledgerRel = skillLedgerPath(folder, path)
|
|
448
|
+
const ledgerPath = join(root, ledgerRel)
|
|
449
|
+
const mismatches = existsSync(ledgerPath)
|
|
450
|
+
? compareLedger(
|
|
451
|
+
ledgerGroups(text, path === 'SKILL.md'),
|
|
452
|
+
countLedgerRows(await Bun.file(ledgerPath).text()),
|
|
453
|
+
)
|
|
454
|
+
: undefined
|
|
455
|
+
return { rel: join(skillRel, path), ledgerRel, mismatches }
|
|
456
|
+
}),
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Directives per H2, `null` for any above the first. A contents list repeats
|
|
462
|
+
* the headings rather than stating a rule, so `## Contents` takes no rows. A
|
|
463
|
+
* nested bullet elaborates its parent, so only a top-level line counts. A body
|
|
464
|
+
* stops at its first closer, and a ledger table under one then reads as rows
|
|
465
|
+
* with no bullets rather than passing unread.
|
|
466
|
+
*/
|
|
467
|
+
function ledgerGroups(text: string, isBody: boolean): HeadingCount[] {
|
|
468
|
+
const counts = new Map<string | null, number>()
|
|
469
|
+
let heading: string | null = null
|
|
470
|
+
|
|
471
|
+
for (const line of bodyLines(text)) {
|
|
472
|
+
if (line.fenced) continue
|
|
473
|
+
const section = SECTION.exec(line.text)?.[1]
|
|
474
|
+
if (section !== undefined) {
|
|
475
|
+
if (isBody && BODY_CLOSERS.includes(section)) break
|
|
476
|
+
heading = section
|
|
477
|
+
continue
|
|
478
|
+
}
|
|
479
|
+
if (heading === CONTENTS_SECTION || !LEDGERED_LINE.test(line.text)) continue
|
|
480
|
+
counts.set(heading, (counts.get(heading) ?? 0) + 1)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return [...counts].map(([key, count]) => ({ heading: key, count }))
|
|
484
|
+
}
|
|
485
|
+
|
|
309
486
|
/**
|
|
310
487
|
* A key carrying an empty value declares nothing, so blank reads as absent
|
|
311
488
|
* rather than as a name that disagrees with every folder.
|
|
@@ -493,11 +670,48 @@ function practiceFindings(source: SkillSource): SkillFinding[] {
|
|
|
493
670
|
rel: source.rel,
|
|
494
671
|
detail: `missing section: ${section}`,
|
|
495
672
|
}))
|
|
496
|
-
const ledger = source.hasLedger
|
|
497
|
-
? []
|
|
498
|
-
: [{ rel: source.rel, detail: `missing ledger: ${PRACTICE_LEDGER}` }]
|
|
499
673
|
|
|
500
|
-
return [
|
|
674
|
+
return [
|
|
675
|
+
...sections,
|
|
676
|
+
...ledgerFindings(source),
|
|
677
|
+
...source.referenceLedgers.flatMap(referenceLedgerFindings),
|
|
678
|
+
...source.staleExemptions.map((rel) => ({
|
|
679
|
+
rel,
|
|
680
|
+
detail: 'missing file: exempt from a ledger',
|
|
681
|
+
})),
|
|
682
|
+
]
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function referenceLedgerFindings(entry: ReferenceLedger): SkillFinding[] {
|
|
686
|
+
if (entry.mismatches === undefined) {
|
|
687
|
+
return [{ rel: entry.rel, detail: `missing ledger: ${entry.ledgerRel}` }]
|
|
688
|
+
}
|
|
689
|
+
return entry.mismatches.map((mismatch) => ({
|
|
690
|
+
rel: entry.rel,
|
|
691
|
+
detail: `${mismatch.heading === null ? 'above the first H2' : `## ${mismatch.heading}`}: ${plural(mismatch.bullets, 'bullet')}, ${plural(mismatch.rows, 'ledger row')}`,
|
|
692
|
+
}))
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* A ledger present but citing nothing passed for as long as the check read the
|
|
697
|
+
* file's existence alone, so the contents are read too. A sample block shows a
|
|
698
|
+
* reader the shape of a row and cites nothing, so fences and code spans are
|
|
699
|
+
* left out.
|
|
700
|
+
*/
|
|
701
|
+
function ledgerFindings(source: SkillSource): SkillFinding[] {
|
|
702
|
+
if (source.ledger === undefined) {
|
|
703
|
+
return [{ rel: source.rel, detail: `missing ledger: ${PRACTICE_LEDGER}` }]
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const cites = bodyLines(source.ledger)
|
|
707
|
+
.filter((line) => !line.fenced)
|
|
708
|
+
.map((line) => maskCodeSpans(line.text))
|
|
709
|
+
.some((text) => EXTERNAL_LINK.test(text) || HOUSE_CHOICE.test(text))
|
|
710
|
+
if (cites) return []
|
|
711
|
+
|
|
712
|
+
return [
|
|
713
|
+
{ rel: source.rel, detail: `ledger cites nothing: ${PRACTICE_LEDGER}` },
|
|
714
|
+
]
|
|
501
715
|
}
|
|
502
716
|
|
|
503
717
|
/**
|
package/src/cli/retired.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface RetiredVerb {
|
|
|
9
9
|
const DESIGN_MOVED =
|
|
10
10
|
'the design values now live in the stylesheet your canon/DESIGN.md lead names'
|
|
11
11
|
|
|
12
|
+
const CONTEXT_MOVED = '`canon context audit`, which reports placement findings'
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* Every verb a release dropped, with the release and where the work went. The
|
|
14
16
|
* router reads this ahead of the parser, and `canon migrate status` reads it to
|
|
@@ -41,6 +43,12 @@ export const RETIRED_VERBS: readonly RetiredVerb[] = [
|
|
|
41
43
|
release: '5.53.0',
|
|
42
44
|
movedTo: 'a tool built for composed video',
|
|
43
45
|
},
|
|
46
|
+
{ path: ['context', 'classify'], release: '5.64.0', movedTo: CONTEXT_MOVED },
|
|
47
|
+
{
|
|
48
|
+
path: ['context', 'classifier'],
|
|
49
|
+
release: '5.64.0',
|
|
50
|
+
movedTo: CONTEXT_MOVED,
|
|
51
|
+
},
|
|
44
52
|
]
|
|
45
53
|
|
|
46
54
|
/**
|
package/src/commands/claude.ts
CHANGED
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
DESCRIPTION_LIMIT,
|
|
31
31
|
LEDGER_SKILLS,
|
|
32
32
|
PRACTICE_LEDGER,
|
|
33
|
+
BULLET_LEDGER_SKILLS,
|
|
34
|
+
SKILL_LEDGERS,
|
|
33
35
|
PRACTICE_SECTIONS,
|
|
34
36
|
PRACTICE_SKILLS,
|
|
35
37
|
REQUIREMENT_LIMITS,
|
|
@@ -166,6 +168,15 @@ interface RoutingOptions {
|
|
|
166
168
|
const SEEDED_FILES: readonly string[] = ['REQUIREMENTS.md', 'DESIGN.md']
|
|
167
169
|
const SEEDED_DIRS: readonly string[] = ['tasks']
|
|
168
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Where a `SEEDED_FILES` entry resolves for the sync presence check, at either
|
|
173
|
+
* surface root, so a target holding its records under `canon/` is not sent to
|
|
174
|
+
* `canon claude init`.
|
|
175
|
+
*/
|
|
176
|
+
export function seededFilePath(resolved: string, name: string): string {
|
|
177
|
+
return surfaceDir(resolved, name)
|
|
178
|
+
}
|
|
179
|
+
|
|
169
180
|
/**
|
|
170
181
|
* Where a `SEEDED_DIRS` entry resolves for the sync presence check.
|
|
171
182
|
*
|
|
@@ -677,7 +688,7 @@ async function runSync(target: string): Promise<number> {
|
|
|
677
688
|
// already holds.
|
|
678
689
|
logStep('Seeded')
|
|
679
690
|
for (const name of SEEDED_FILES) {
|
|
680
|
-
if (existsSync(
|
|
691
|
+
if (existsSync(seededFilePath(resolved, name))) logInfo(name)
|
|
681
692
|
else logWarn(`${name} missing. Run \`canon claude init\``)
|
|
682
693
|
}
|
|
683
694
|
for (const name of SEEDED_DIRS) {
|
|
@@ -1768,6 +1779,9 @@ function reportPracticeShape(report: SkillsAudit): void {
|
|
|
1768
1779
|
logInfo(
|
|
1769
1780
|
`A listed practice skill closes with ${PRACTICE_SECTIONS.map((section) => `## ${section}`).join(', ')}, and every skill on the wider ledger list carries ${PRACTICE_LEDGER}.`,
|
|
1770
1781
|
)
|
|
1782
|
+
logInfo(
|
|
1783
|
+
`A skill on the per-bullet list, ${plural(Object.keys(BULLET_LEDGER_SKILLS).length, 'skill')} here, also matches each reference it does not exempt, and a listed body, to a ledger under ${SKILL_LEDGERS}, one row per bullet under each H2.`,
|
|
1784
|
+
)
|
|
1771
1785
|
logInfo(
|
|
1772
1786
|
`Both lists are kept here and name ${plural(PRACTICE_SKILLS.length, 'practice skill')} and ${plural(LEDGER_SKILLS.length, 'ledger skill')}, so a skill outside ${SHIPPED_CORPUS} is never read.`,
|
|
1773
1787
|
)
|