@erclx/canon 4.78.0 → 4.80.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/README.md +9 -9
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canon-operator/SKILL.md +1 -1
- package/claude/skills/canon-rollout/SKILL.md +1 -1
- package/claude/skills/create-standard/SKILL.md +1 -1
- package/claude/skills/design-extract/REQUIREMENT.md +4 -4
- package/claude/skills/design-extract/SKILL.md +11 -11
- package/claude/skills/docs-fold/SKILL.md +14 -14
- package/claude/skills/docs-fold/references/anchor-sweep.md +1 -1
- package/claude/skills/docs-fold/references/wireframe-sweep.md +4 -4
- package/claude/skills/draft-context/REQUIREMENT.md +6 -6
- package/claude/skills/draft-context/SKILL.md +8 -6
- package/claude/skills/draft-diagram/SKILL.md +5 -5
- package/claude/skills/draft-identity/SKILL.md +3 -3
- package/claude/skills/draft-readme/REQUIREMENT.md +1 -1
- package/claude/skills/draft-screencast/SKILL.md +1 -1
- package/claude/skills/draft-wireframes/REQUIREMENT.md +7 -7
- package/claude/skills/draft-wireframes/SKILL.md +7 -5
- package/claude/skills/git-pr/SKILL.md +3 -3
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/memory-capture/REQUIREMENT.md +1 -1
- package/claude/skills/memory-capture/SKILL.md +6 -6
- package/claude/skills/memory-review/SKILL.md +1 -1
- package/claude/skills/migration-claude-md/REQUIREMENT.md +3 -3
- package/claude/skills/migration-claude-md/SKILL.md +7 -5
- package/claude/skills/migration-context/REQUIREMENT.md +3 -3
- package/claude/skills/migration-context/SKILL.md +11 -9
- package/claude/skills/plan-feature/SKILL.md +4 -4
- package/claude/skills/plan-groundwork/SKILL.md +2 -2
- package/claude/skills/plan-intake/SKILL.md +2 -2
- package/claude/skills/project-commands/SKILL.md +2 -2
- package/claude/skills/review-branch/SKILL.md +3 -3
- package/claude/skills/review-pr/SKILL.md +2 -2
- package/claude/skills/role-orchestrator/SKILL.md +1 -1
- package/claude/skills/role-planner/SKILL.md +1 -1
- package/claude/skills/session-relay/REQUIREMENT.md +1 -1
- package/claude/skills/session-resume/SKILL.md +1 -1
- package/claude/skills/setup-gov/SKILL.md +1 -1
- package/claude/skills/setup-init/SKILL.md +1 -1
- package/claude/skills/teach-workspace/references/promotion.md +5 -5
- package/claude/skills/ux-audit/SKILL.md +5 -5
- package/claude/skills/ux-measure/SKILL.md +1 -1
- package/docs/agents/audits.md +2 -2
- package/docs/agents/commands.md +7 -2
- package/docs/agents/context-audit-checks.md +3 -3
- package/docs/agents/context-audit.md +2 -2
- package/docs/agents/counts.md +3 -3
- package/docs/agents/design-board.md +3 -3
- package/docs/agents/docs.md +3 -3
- package/docs/agents/indexes.md +1 -1
- package/docs/agents/install-and-sync.md +4 -4
- package/docs/agents/label-coverage.md +1 -1
- package/docs/agents/review-classification.md +1 -1
- package/docs/agents/routing.md +2 -2
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/sandbox.md +1 -1
- package/docs/agents/scripting.md +1 -1
- package/docs/agents/skills-audit.md +1 -1
- package/docs/agents/skills-reach.md +4 -4
- package/docs/target-projects.md +11 -11
- package/docs/workflow/ai-workflow.md +15 -13
- package/docs/workflow/operating-model.md +2 -2
- package/docs/workflow/visual-design-workflow.md +8 -8
- package/governance/rules/claude/510-context.md +3 -2
- package/governance/rules/claude/520-wireframes.md +3 -2
- package/governance/rules/claude/530-requirements.md +2 -1
- package/governance/rules/claude/540-architecture.md +2 -1
- package/governance/rules/claude/550-design.md +2 -1
- package/governance/rules/core/025-indexes.md +1 -1
- package/governance/rules/core/045-memory.md +1 -1
- package/package.json +1 -1
- package/scripts/core/check-ignore-parity.sh +1 -1
- package/scripts/core/check-seed-independence.sh +5 -2
- package/scripts/core/install-check.sh +1 -1
- package/scripts/core/list-seed-roots.sh +3 -3
- package/scripts/core/regen-hero.sh +1 -1
- package/scripts/docs/list.sh +3 -3
- package/scripts/lib/sandbox-git.sh +1 -1
- package/scripts/lib/tooling.sh +6 -4
- package/snippets/claude/figma-steps.md +1 -1
- package/src/audits/baseline.ts +17 -5
- package/src/audits/catalog.ts +2 -2
- package/src/autoship/paths.ts +1 -1
- package/src/claude/cases/authoring.ts +2 -2
- package/src/claude/cases/setup.ts +1 -1
- package/src/claude/routing.ts +1 -1
- package/src/claude/seeds-list.ts +1 -1
- package/src/claude/seeds.ts +62 -28
- package/src/claude/skills-reach.ts +3 -3
- package/src/commands/claude.ts +1 -1
- package/src/commands/context.ts +8 -8
- package/src/commands/design.ts +1 -1
- package/src/commands/gov.ts +1 -1
- package/src/commands/migrate.ts +182 -1
- package/src/commands/sync.ts +1 -1
- package/src/context/audit.ts +1 -1
- package/src/context/citations.ts +7 -5
- package/src/context/folders.ts +2 -2
- package/src/counts/scan.ts +4 -4
- package/src/design/board.ts +5 -5
- package/src/design/components.ts +2 -2
- package/src/design/css.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/regen.ts +1 -1
- package/src/design/render.ts +1 -1
- package/src/design/tokens.ts +2 -2
- package/src/docs/read.ts +9 -3
- package/src/gate/measures.ts +14 -11
- package/src/gate/stages.ts +4 -4
- package/src/labels/map.ts +19 -5
- package/src/legacy-path.ts +15 -0
- package/src/markdown/scan.ts +1 -1
- package/src/migrate/plan.ts +1 -0
- package/src/migrate/surface-roots.ts +457 -0
- package/src/record-root.ts +6 -4
- package/src/sandbox/census.ts +1 -1
- package/src/shipped/references.ts +3 -3
- package/src/surface-root.ts +33 -8
- package/src/sync/layout.ts +10 -6
- package/src/sync/reverse.ts +1 -1
- package/src/sync/stamp.ts +22 -17
- package/src/targets/sweep.ts +6 -2
- package/src/tooling/inject.ts +5 -3
- package/src/tooling/scan.ts +4 -1
- package/standards/architecture.md +4 -4
- package/standards/context.md +7 -7
- package/standards/design.md +3 -3
- package/standards/diagrams.md +6 -6
- package/standards/index.md +5 -5
- package/standards/markdown.md +2 -2
- package/standards/publish.md +3 -3
- package/standards/readme.md +1 -1
- package/standards/requirements.md +4 -4
- package/standards/skill.md +2 -2
- package/standards/tasks.md +1 -1
- package/standards/wiki.md +1 -1
- package/standards/wireframes.md +8 -8
- package/tooling/astro/reference.md +3 -3
- package/tooling/base/reference.md +3 -3
- package/tooling/claude/reference.md +7 -3
- package/tooling/claude/seeds/CLAUDE.md +9 -9
- package/tooling/nextjs/reference.md +2 -2
- package/tooling/python/reference.md +1 -1
- package/tooling/vite-react/reference.md +2 -2
- package/tooling/web/reference.md +2 -2
- /package/tooling/base/seeds/{.claude → canon}/context/ci.md +0 -0
- /package/tooling/base/seeds/{.claude → canon}/context/development.md +0 -0
- /package/tooling/base/seeds/{.claude → canon}/context/index.md +0 -0
- /package/tooling/claude/seeds/{.claude → canon}/ARCHITECTURE.md +0 -0
- /package/tooling/claude/seeds/{.claude → canon}/DESIGN.md +0 -0
- /package/tooling/claude/seeds/{.claude → canon}/REQUIREMENTS.md +0 -0
- /package/tooling/claude/seeds/{.claude → canon}/wireframes/index.md +0 -0
package/src/commands/migrate.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs'
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import type { Command } from 'commander'
|
|
4
4
|
import { listRepositoryFiles } from '@/git-files'
|
|
@@ -26,6 +26,10 @@ import {
|
|
|
26
26
|
walkFlatRules,
|
|
27
27
|
} from '@/migrate/rule-layout'
|
|
28
28
|
import { SKILL_NAME_MAP, SKILL_NAME_RULES } from '@/migrate/skill-names'
|
|
29
|
+
import {
|
|
30
|
+
planSurfaceRootsMove,
|
|
31
|
+
type SurfaceRootsPlan,
|
|
32
|
+
} from '@/migrate/surface-roots'
|
|
29
33
|
import {
|
|
30
34
|
applyScratchEvidence,
|
|
31
35
|
planScratchEvidence,
|
|
@@ -396,6 +400,143 @@ function toRecordsRecord(
|
|
|
396
400
|
}
|
|
397
401
|
}
|
|
398
402
|
|
|
403
|
+
/**
|
|
404
|
+
* Moves the tracked toolkit surfaces to `canon/` and repoints what cites them.
|
|
405
|
+
*
|
|
406
|
+
* Every path here is tracked, so the write goes through `applyRename`'s
|
|
407
|
+
* `git mv` rather than the records move's plain rename, and the history
|
|
408
|
+
* follows each file. A destination already on disk refuses the whole plan,
|
|
409
|
+
* since `git mv` onto an existing path fails per file and leaves a tree split
|
|
410
|
+
* across both roots.
|
|
411
|
+
*/
|
|
412
|
+
async function runSurfaceRoots(opts: SweepOptions): Promise<number> {
|
|
413
|
+
const root = opts.root ?? process.cwd()
|
|
414
|
+
|
|
415
|
+
const files = await listRepositoryFiles(root)
|
|
416
|
+
if (files === undefined) {
|
|
417
|
+
logError(`Could not list files under ${root}. Is it a git repository?`)
|
|
418
|
+
return 1
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const toSweep = files.filter((path) => !isRecordArtifact(path))
|
|
422
|
+
const plan = planSurfaceRootsMove(await readSources(root, toSweep))
|
|
423
|
+
const taken = plan.entries
|
|
424
|
+
.map((entry) => entry.movesTo)
|
|
425
|
+
.filter((to): to is string => to !== undefined)
|
|
426
|
+
.filter((to) => existsSync(join(root, to)))
|
|
427
|
+
|
|
428
|
+
if (opts.json) {
|
|
429
|
+
process.stdout.write(
|
|
430
|
+
`${JSON.stringify(toSurfaceRootsRecord(plan, taken, opts.write))}\n`,
|
|
431
|
+
)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
reportSurfaceRoots(plan)
|
|
435
|
+
|
|
436
|
+
if (taken.length > 0) {
|
|
437
|
+
logError(
|
|
438
|
+
`${plural(taken.length, 'destination')} already exist under canon/. Merging two copies of a surface is not a call this verb takes.`,
|
|
439
|
+
)
|
|
440
|
+
for (const path of taken) logError(` ${path}`)
|
|
441
|
+
return 1
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (plan.entries.length === 0) return 0
|
|
445
|
+
|
|
446
|
+
if (!opts.write) {
|
|
447
|
+
logWarn('Nothing was written. Pass --write to apply this plan.')
|
|
448
|
+
return 2
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const applied = await applyRename(root, {
|
|
452
|
+
entries: plan.entries.map((entry) => ({
|
|
453
|
+
path: entry.path,
|
|
454
|
+
...(entry.movesTo === undefined ? {} : { movesTo: entry.movesTo }),
|
|
455
|
+
...(entry.text === undefined ? {} : { text: entry.text }),
|
|
456
|
+
renamed: entry.rewritten,
|
|
457
|
+
protectedCount: entry.kept,
|
|
458
|
+
})),
|
|
459
|
+
excluded: plan.excluded,
|
|
460
|
+
renamed: plan.rewritten,
|
|
461
|
+
protectedCount: plan.kept,
|
|
462
|
+
moves: plan.moves,
|
|
463
|
+
})
|
|
464
|
+
logStep(
|
|
465
|
+
`Rewrote ${plural(applied.written, 'file')} and moved ${plural(applied.moved, 'file')}.`,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
if (applied.failed.length > 0) {
|
|
469
|
+
logError(`Could not move ${plural(applied.failed.length, 'path')}.`)
|
|
470
|
+
for (const path of applied.failed) logError(` ${path}`)
|
|
471
|
+
return 1
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return 0
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function reportSurfaceRoots(plan: SurfaceRootsPlan): void {
|
|
478
|
+
logInfo(`${plural(plan.moves, 'file')} to move.`)
|
|
479
|
+
logInfo(
|
|
480
|
+
`${plural(plan.entries.filter((entry) => entry.text !== undefined).length, 'file')} to change, ${plural(plan.rewritten, 'citation')} to rewrite.`,
|
|
481
|
+
)
|
|
482
|
+
logInfo(`${plural(plan.kept, 'citation')} marked to keep the old root.`)
|
|
483
|
+
|
|
484
|
+
if (plan.excluded.length > 0) {
|
|
485
|
+
logInfo(`${plural(plan.excluded.length, 'file')} excluded from the sweep:`)
|
|
486
|
+
for (const path of plan.excluded) logInfo(` ${path}`)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (plan.coupled.length > 0) {
|
|
490
|
+
logInfo(
|
|
491
|
+
`${plural(plan.coupled.length, 'file')} couple to an excluded path:`,
|
|
492
|
+
)
|
|
493
|
+
for (const path of plan.coupled) logInfo(` ${path}`)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
for (const [label, held] of [
|
|
497
|
+
['a frontmatter paths: glob', plan.frontmatterGlobs],
|
|
498
|
+
['a citation into another repository', plan.crossRepoCitations],
|
|
499
|
+
['a dated citation', plan.datedCitations],
|
|
500
|
+
] as const) {
|
|
501
|
+
if (held.length === 0) continue
|
|
502
|
+
logInfo(`${plural(held.length, 'file')} carry ${label}, left alone:`)
|
|
503
|
+
for (const entry of held) {
|
|
504
|
+
for (const line of entry.lines) {
|
|
505
|
+
logInfo(` ${entry.path}:${line.line} ${excerpt(line.text)}`)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function toSurfaceRootsRecord(
|
|
512
|
+
plan: SurfaceRootsPlan,
|
|
513
|
+
collisions: readonly string[],
|
|
514
|
+
wrote: boolean | undefined,
|
|
515
|
+
): unknown {
|
|
516
|
+
return {
|
|
517
|
+
ok: collisions.length === 0,
|
|
518
|
+
wrote: wrote === true,
|
|
519
|
+
moves: plan.moves,
|
|
520
|
+
collisions,
|
|
521
|
+
files: plan.entries.length,
|
|
522
|
+
rewritten: plan.rewritten,
|
|
523
|
+
kept: plan.kept,
|
|
524
|
+
excluded: plan.excluded,
|
|
525
|
+
coupled: plan.coupled,
|
|
526
|
+
frontmatterGlobs: plan.frontmatterGlobs,
|
|
527
|
+
crossRepoCitations: plan.crossRepoCitations,
|
|
528
|
+
datedCitations: plan.datedCitations,
|
|
529
|
+
globs: plan.globs,
|
|
530
|
+
crossRepo: plan.crossRepo,
|
|
531
|
+
dated: plan.dated,
|
|
532
|
+
paths: plan.entries.map((entry) => ({
|
|
533
|
+
path: entry.path,
|
|
534
|
+
...(entry.movesTo === undefined ? {} : { movesTo: entry.movesTo }),
|
|
535
|
+
rewritten: entry.rewritten,
|
|
536
|
+
})),
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
399
540
|
interface RecordTreeOptions {
|
|
400
541
|
readonly json?: boolean
|
|
401
542
|
readonly write?: boolean
|
|
@@ -763,6 +904,46 @@ export function register(program: Command): void {
|
|
|
763
904
|
process.exitCode = await runRecords(opts)
|
|
764
905
|
})
|
|
765
906
|
|
|
907
|
+
migrate
|
|
908
|
+
.command('surface-roots')
|
|
909
|
+
.description('Move the tracked toolkit surfaces to canon/')
|
|
910
|
+
.helpOption('-h, --help', 'Show this help message')
|
|
911
|
+
.option('--json', 'Add a machine-readable record on stdout')
|
|
912
|
+
.option('--write', 'Apply the plan rather than reporting it')
|
|
913
|
+
.option(
|
|
914
|
+
'--root <path>',
|
|
915
|
+
'Project root, defaulting to the working directory',
|
|
916
|
+
)
|
|
917
|
+
.addHelpText(
|
|
918
|
+
'after',
|
|
919
|
+
[
|
|
920
|
+
'',
|
|
921
|
+
'Exit codes:',
|
|
922
|
+
' 0 nothing to move, or --write applied the whole plan',
|
|
923
|
+
' 1 refused, or a move failed',
|
|
924
|
+
' 2 a plan exists and --write was not passed',
|
|
925
|
+
'',
|
|
926
|
+
'Moves canon/context/, canon/wireframes/, and the ARCHITECTURE.md,',
|
|
927
|
+
'REQUIREMENTS.md, and DESIGN.md documents to canon/ with git mv, and',
|
|
928
|
+
'rewrites every tracked citation of them. .claude/canon/, rules/,',
|
|
929
|
+
'skills/, hooks/, and settings.json stay where the vendor reads them.',
|
|
930
|
+
'',
|
|
931
|
+
'A line carrying canon-keep-surface-root, or the nearest non-blank line',
|
|
932
|
+
'above it, keeps the old root. A frontmatter paths: glob, a citation',
|
|
933
|
+
'into another repository, and one inside a dated paragraph are reported',
|
|
934
|
+
'and left alone rather than rewritten.',
|
|
935
|
+
'',
|
|
936
|
+
'Examples:',
|
|
937
|
+
' canon migrate surface-roots',
|
|
938
|
+
' canon migrate surface-roots --write',
|
|
939
|
+
' canon migrate surface-roots --json',
|
|
940
|
+
'',
|
|
941
|
+
].join('\n'),
|
|
942
|
+
)
|
|
943
|
+
.action(async (opts: SweepOptions) => {
|
|
944
|
+
process.exitCode = await runSurfaceRoots(opts)
|
|
945
|
+
})
|
|
946
|
+
|
|
766
947
|
migrate
|
|
767
948
|
.command('record-tree')
|
|
768
949
|
.description('Repoint old-root citations inside the records themselves')
|
package/src/commands/sync.ts
CHANGED
|
@@ -101,7 +101,7 @@ function renderCheck(report: CheckReport): void {
|
|
|
101
101
|
|
|
102
102
|
if (report.stampAtLegacyPath) {
|
|
103
103
|
logWarn(
|
|
104
|
-
'Stamp found at a retired path. Move it to
|
|
104
|
+
'Stamp found at a retired path. Move it to canon/config/config.json.',
|
|
105
105
|
)
|
|
106
106
|
}
|
|
107
107
|
|
package/src/context/audit.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { isStubSeed } from '@/seed-marker'
|
|
|
19
19
|
* Every reason `canon context audit` refuses for.
|
|
20
20
|
*
|
|
21
21
|
* `no-folders` is the one ordinary absence: a project that never adopted
|
|
22
|
-
*
|
|
22
|
+
* `canon/context/`, `.canon/diagrams/`, or `canon/wireframes/` names no
|
|
23
23
|
* corpus this audit can measure, the same state `no-skills` reads for the
|
|
24
24
|
* skill corpora. The other four are a malformed invocation or a checkout git
|
|
25
25
|
* cannot read, which stay a break rather than an absence.
|
package/src/context/citations.ts
CHANGED
|
@@ -84,13 +84,15 @@ export function isFixture(rel: string): boolean {
|
|
|
84
84
|
* A dotted root cannot be a suffix of a path segment, since a segment boundary
|
|
85
85
|
* is a slash and a slash can never sit inside the dot itself, so a preceding
|
|
86
86
|
* slash is not a false match to guard against and the boundary admits it. That
|
|
87
|
-
* is what a relative link needs
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
87
|
+
* is what a relative link into a dotted root needs. A bare root has no such
|
|
88
|
+
* protection: it is a suffix of a dotted root's own name and of any
|
|
89
|
+
* `/<root>/` path segment, such as a seed tree's own `canon/` folder, so its
|
|
90
|
+
* boundary rejects a name character, a dot, and a slash, except a slash that
|
|
91
|
+
* closes a `..` segment. That one exception is a relative link climbing out of
|
|
92
|
+
* a folder into the bare root, which is the form a moved docs page writes.
|
|
91
93
|
*/
|
|
92
94
|
function rootBoundary(root: string): string {
|
|
93
|
-
return root.startsWith('.') ? '(?<![\\w.])' : '(?<![\\w
|
|
95
|
+
return root.startsWith('.') ? '(?<![\\w.])' : '(?<![\\w.])(?<!(?<!\\.\\.)/)'
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
/**
|
package/src/context/folders.ts
CHANGED
|
@@ -61,7 +61,7 @@ export interface AuditedFolder {
|
|
|
61
61
|
* The requested folder name this was resolved under, which is what says
|
|
62
62
|
* which standard governs the entries. A nested split folder carries the name
|
|
63
63
|
* of the folder it sits beneath rather than its own, so
|
|
64
|
-
*
|
|
64
|
+
* `canon/context/claude-plugin` is governed as `context`.
|
|
65
65
|
*/
|
|
66
66
|
readonly name: string
|
|
67
67
|
/**
|
|
@@ -90,7 +90,7 @@ export interface AuditedFolder {
|
|
|
90
90
|
* Names the requested record-root folders that actually exist, which is the
|
|
91
91
|
* citation check's scope.
|
|
92
92
|
*
|
|
93
|
-
* A skill or seed pointing into
|
|
93
|
+
* A skill or seed pointing into `canon/wireframes/` is a live instruction for
|
|
94
94
|
* a project that carries the folder and says nothing about one that does not.
|
|
95
95
|
* Checking a path into an absent folder would fail eight shipped references
|
|
96
96
|
* here for the sole reason that this repository has no wireframes.
|
package/src/counts/scan.ts
CHANGED
|
@@ -35,7 +35,7 @@ export type CountsReport =
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* A calendar date, which reads a sentence as a historical record rather than
|
|
38
|
-
* a live claim about the tree.
|
|
38
|
+
* a live claim about the tree. `canon/ARCHITECTURE.md` and the context
|
|
39
39
|
* entries carry a figure this way deliberately, and every one of them stays
|
|
40
40
|
* correct forever, so a sentence carrying one is read past rather than
|
|
41
41
|
* matched.
|
|
@@ -80,7 +80,7 @@ function sentencesOf(line: string): string[] {
|
|
|
80
80
|
* the 290 showed removing all of them while keeping that one.
|
|
81
81
|
*
|
|
82
82
|
* `authors` joined the list on a corpus measurement rather than on the shape
|
|
83
|
-
* argument above.
|
|
83
|
+
* argument above. `canon/context/development/regeneration.md` states `the
|
|
84
84
|
* toolkit authors 69 rules under governance/rules/`, which is the assertion
|
|
85
85
|
* shape this list was built for with a verb the list did not carry, and the
|
|
86
86
|
* word costs one further sentence across the whole corpus and no finding.
|
|
@@ -129,7 +129,7 @@ const QUANTIFIERS = ['all']
|
|
|
129
129
|
* of the number.
|
|
130
130
|
*
|
|
131
131
|
* Found on this module's own first review, against a live instance the verb
|
|
132
|
-
* gate alone reported clean:
|
|
132
|
+
* gate alone reported clean: `canon/context/development/gates.md` stated
|
|
133
133
|
* `the twelve audits` while the tree held 20, sitting inside the plausibility
|
|
134
134
|
* bound this design already carries, so nothing but the missing shape kept it
|
|
135
135
|
* from reporting.
|
|
@@ -142,7 +142,7 @@ const QUANTIFIERS = ['all']
|
|
|
142
142
|
* match at all. No syntactic rule tells a qualifier that narrows a catalog
|
|
143
143
|
* from one that only restates it, and the sentence was true when written, so
|
|
144
144
|
* the fix was not a narrower matcher: the clause was dated as a past state,
|
|
145
|
-
* the way
|
|
145
|
+
* the way `canon/ARCHITECTURE.md` dates its own figures, which is the class
|
|
146
146
|
* the date exclusion below already reads past.
|
|
147
147
|
*/
|
|
148
148
|
const ARTICLES = ['the', 'a', 'an']
|
package/src/design/board.ts
CHANGED
|
@@ -14,12 +14,12 @@ import { colorValue } from '@/design/tokens'
|
|
|
14
14
|
import { recordDir } from '@/record-root'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* The one named site for this repository's wireframe corpus
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* The one named site for this repository's wireframe corpus, so the surface
|
|
18
|
+
* move retargets a single constant rather than a literal repeated per panel.
|
|
19
|
+
* The board reads this checkout alone, which has moved, so the new root is
|
|
20
|
+
* spelled directly rather than resolved.
|
|
21
21
|
*/
|
|
22
|
-
export const WIREFRAME_DIR = join('
|
|
22
|
+
export const WIREFRAME_DIR = join('canon', 'wireframes')
|
|
23
23
|
|
|
24
24
|
/** Landing page for the built site the surfaces panel iframes when present. */
|
|
25
25
|
const WEB_DIST = join('web', 'dist')
|
package/src/design/components.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `buildDesignCss`'s `components` option, which is what widening that option
|
|
16
16
|
* from a boolean to `boolean | readonly Component[]` bought.
|
|
17
17
|
*
|
|
18
|
-
* It does not live in
|
|
18
|
+
* It does not live in `canon/DESIGN.md`. `standards/design.md` keeps CSS class
|
|
19
19
|
* names out of that record and says they live in code, which is here.
|
|
20
20
|
*/
|
|
21
21
|
|
|
@@ -207,7 +207,7 @@ const TEACH_CHROME: Component = {
|
|
|
207
207
|
'navigation shared by every teach page. Recovered from two gitignored',
|
|
208
208
|
'course.css files that predate a regression that dropped this layer from the',
|
|
209
209
|
"generator, and rewritten onto this module's tokens rather than the",
|
|
210
|
-
'incompatible palette they carried. See
|
|
210
|
+
'incompatible palette they carried. See canon/wireframes/teach/chrome.md for',
|
|
211
211
|
'the shape.',
|
|
212
212
|
].join('\n '),
|
|
213
213
|
reads: [
|
package/src/design/css.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { TOKENS } from '@/design/tokens'
|
|
|
12
12
|
*
|
|
13
13
|
* The property names match the ones `@/design/render` already emits from a
|
|
14
14
|
* parsed document, so the toolkit's own source and a target's hand-authored
|
|
15
|
-
*
|
|
15
|
+
* `canon/DESIGN.md` produce one vocabulary rather than two.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/** A role name as it appears in a custom property. */
|
package/src/design/document.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
import { TOKENS } from '@/design/tokens'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Renders
|
|
11
|
+
* Renders `canon/DESIGN.md` from the token source.
|
|
12
12
|
*
|
|
13
13
|
* The document is the view and `@/design/tokens` is the fact, which is the one
|
|
14
14
|
* thing that changed when this record stopped being a transcript of two other
|
package/src/design/regen.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { renderDesignDocument } from '@/design/document'
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/** The rendered record, at the path `standards/design.md` fixes for it. */
|
|
18
|
-
export const DESIGN_DOCUMENT = join('
|
|
18
|
+
export const DESIGN_DOCUMENT = join('canon', 'DESIGN.md')
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* The base stylesheet a target installs. It sits inside `src/` because that is
|
package/src/design/render.ts
CHANGED
|
@@ -219,7 +219,7 @@ ${previewChrome()}
|
|
|
219
219
|
</head>
|
|
220
220
|
<body>
|
|
221
221
|
<h1>Design tokens</h1>
|
|
222
|
-
<p class="note">Generated from <code
|
|
222
|
+
<p class="note">Generated from <code>canon/DESIGN.md</code> by <code>canon design render</code>. Token preview only, not a screen mock.</p>${summary}
|
|
223
223
|
${sections.join('\n')}
|
|
224
224
|
</body>
|
|
225
225
|
</html>
|
package/src/design/tokens.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The design system's one source of values.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* `canon/DESIGN.md` is rendered from this module rather than read by it, so
|
|
5
5
|
* the document a person opens is a view and this file is the fact. Every
|
|
6
6
|
* rendering surface reads from here in the form it can take: a CSS surface
|
|
7
7
|
* takes custom properties through `@/design/css`, and the slide renderer takes
|
|
@@ -94,7 +94,7 @@ export const TOKENS: DesignTokens = {
|
|
|
94
94
|
'',
|
|
95
95
|
'The two rendered captures read it as well. `scripts/core/regen-hero.sh` fills `assets/captures/hero.html.tmpl` and `assets/captures/install.html.tmpl` with what `canon design css --no-components` emits, so both frames now carry the custom properties rather than their own copies of the hex, and a value moved here moves what the next capture renders.',
|
|
96
96
|
'',
|
|
97
|
-
'The terminal framing is the one surface left holding its own values, and that is a decision rather than a gap. `scripts/lib/ui.sh` and `src/ui.ts` each spell six escape constants, and
|
|
97
|
+
'The terminal framing is the one surface left holding its own values, and that is a decision rather than a gap. `scripts/lib/ui.sh` and `src/ui.ts` each spell six escape constants, and `canon/ARCHITECTURE.md` records one color source per language with a check behind each, so generating a third spelling from here would break the rule those two checks enforce. What the record is still incomplete about is the other half of those six: `WHITE` and `GREY` name no role below, so the terminal palette is described here in part rather than in whole.',
|
|
98
98
|
].join('\n'),
|
|
99
99
|
|
|
100
100
|
personality: [
|
package/src/docs/read.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs'
|
|
2
2
|
import { basename, join } from 'node:path'
|
|
3
3
|
import { stripFrontmatter } from '@/frontmatter'
|
|
4
|
+
import { SURFACE_ROOTS } from '@/surface-root'
|
|
4
5
|
|
|
5
6
|
const INDEX_TOPIC = 'index'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* The
|
|
9
|
-
* consumer-facing reference and
|
|
9
|
+
* The roots a topic resolves against, in precedence order. `docs/` holds
|
|
10
|
+
* consumer-facing reference and the context folder holds per-domain internal
|
|
10
11
|
* narrative, so a name present in both resolves to the consumer-facing copy.
|
|
12
|
+
* The context folder is read at every surface root, so a checkout the surface
|
|
13
|
+
* move has not reached still resolves its own entries.
|
|
11
14
|
*/
|
|
12
|
-
const ROOTS: readonly string[] = [
|
|
15
|
+
const ROOTS: readonly string[] = [
|
|
16
|
+
'docs',
|
|
17
|
+
...SURFACE_ROOTS.map((root) => join(root, 'context')),
|
|
18
|
+
]
|
|
13
19
|
|
|
14
20
|
export interface ResolvedTopic {
|
|
15
21
|
readonly path: string
|
package/src/gate/measures.ts
CHANGED
|
@@ -126,11 +126,11 @@ export const SANDBOX_ASSERTED_FLOOR = 26
|
|
|
126
126
|
* Where the retained counts the audit stage compares each run against are
|
|
127
127
|
* read, relative to `root`.
|
|
128
128
|
*
|
|
129
|
-
* A function rather than a spelled constant, since the read
|
|
129
|
+
* A function rather than a spelled constant, since the read resolves at
|
|
130
130
|
* either surface root and a constant naming one of them would be believed of
|
|
131
|
-
* a project that has moved. `canon audits run` owns writing it,
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* a project that has moved. `canon audits run` owns writing it, at
|
|
132
|
+
* `canon/config/baseline.json` now that the write has flipped, falling back to
|
|
133
|
+
* `.claude/canon/baseline.json` for a target that has not moved.
|
|
134
134
|
*/
|
|
135
135
|
export function auditsBaselineRel(root: string): string {
|
|
136
136
|
return relative(root, surfaceDir(root, 'canon', 'baseline.json'))
|
|
@@ -369,7 +369,8 @@ export const seedStandards: Measure = async (ctx) => {
|
|
|
369
369
|
if (seedRoots.length === 0) {
|
|
370
370
|
return {
|
|
371
371
|
emissions: [],
|
|
372
|
-
unmeasured:
|
|
372
|
+
unmeasured:
|
|
373
|
+
'No seed root carries .claude/ or canon/, so nothing was measured.',
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
376
|
|
|
@@ -387,11 +388,13 @@ export const seedStandards: Measure = async (ctx) => {
|
|
|
387
388
|
|
|
388
389
|
// The audit separates 1 from 2 and they mean opposite things. 2 is a seed
|
|
389
390
|
// breaking the standard it seeds. 1 is the audit refusing, which a seed
|
|
390
|
-
// root carrying a
|
|
391
|
-
//
|
|
391
|
+
// root carrying a root but no audited folder produces, and reporting that
|
|
392
|
+
// as a violation sends a reader hunting one that does not exist.
|
|
392
393
|
if (run.exitCode === 1) {
|
|
393
394
|
emissions.push(
|
|
394
|
-
warn(
|
|
395
|
+
warn(
|
|
396
|
+
`${seedRoot}: no audited folder under either root, nothing measured`,
|
|
397
|
+
),
|
|
395
398
|
)
|
|
396
399
|
continue
|
|
397
400
|
}
|
|
@@ -581,8 +584,8 @@ export const shippedReferences: Measure = async (ctx) => {
|
|
|
581
584
|
),
|
|
582
585
|
failure:
|
|
583
586
|
found.length === 1
|
|
584
|
-
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, rewrite a bare standards/ path under claude/skills/ as \${CLAUDE_SKILL_DIR}/../../standards/<name>.md, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning
|
|
585
|
-
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, rewrite a bare standards/ path under claude/skills/ as \${CLAUDE_SKILL_DIR}/../../standards/<name>.md, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning
|
|
587
|
+
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, rewrite a bare standards/ path under claude/skills/ as \${CLAUDE_SKILL_DIR}/../../standards/<name>.md, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning canon/context/ entry, state the fact a shipped skill body's cited rule enforces instead of its path, or mark the line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`
|
|
588
|
+
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, rewrite a bare standards/ path under claude/skills/ as \${CLAUDE_SKILL_DIR}/../../standards/<name>.md, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning canon/context/ entry, state the fact a shipped skill body's cited rule enforces instead of its path, or mark each line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`,
|
|
586
589
|
}
|
|
587
590
|
}
|
|
588
591
|
|
|
@@ -1011,7 +1014,7 @@ export const pluginManifests: Measure = async (ctx) => {
|
|
|
1011
1014
|
return {
|
|
1012
1015
|
emissions: [],
|
|
1013
1016
|
unmeasured:
|
|
1014
|
-
'claude is on PATH and claude --version fails, so the install brought down no platform-native binary and no manifest was read. Raise or lower the pinned version at the Install Plugin CLI step in .github/workflows/verify.yml, and record the move in
|
|
1017
|
+
'claude is on PATH and claude --version fails, so the install brought down no platform-native binary and no manifest was read. Raise or lower the pinned version at the Install Plugin CLI step in .github/workflows/verify.yml, and record the move in canon/context/ci.md.',
|
|
1015
1018
|
}
|
|
1016
1019
|
}
|
|
1017
1020
|
|
package/src/gate/stages.ts
CHANGED
|
@@ -79,7 +79,7 @@ export interface Stage {
|
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* Corpora a `src/` test asserts over from outside `src/`, censused in
|
|
82
|
-
*
|
|
82
|
+
* `canon/context/development/verification.md`. This list and that census are
|
|
83
83
|
* two copies of one set with nothing comparing them, so a corpus joining the
|
|
84
84
|
* census joins this list in the same change. The first four are directory
|
|
85
85
|
* prefixes because their tests walk the tree whole, which is what reaches a
|
|
@@ -221,7 +221,7 @@ export const STAGES: readonly Stage[] = [
|
|
|
221
221
|
success: 'Tooling paths clean',
|
|
222
222
|
},
|
|
223
223
|
{
|
|
224
|
-
//
|
|
224
|
+
// `canon/DESIGN.md`, the base stylesheet, the web stylesheet, and the
|
|
225
225
|
// tab icon are all written from `src/design/tokens.ts` and none is edited
|
|
226
226
|
// by hand. Four artifacts from one source is the cost of the token move,
|
|
227
227
|
// and a render step that has to run is only safe while something fails
|
|
@@ -236,9 +236,9 @@ export const STAGES: readonly Stage[] = [
|
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
kind: 'drift',
|
|
239
|
-
pathspec: '
|
|
239
|
+
pathspec: 'canon/DESIGN.md',
|
|
240
240
|
failure:
|
|
241
|
-
'The design record drifted from the token source. Run bun run check and commit
|
|
241
|
+
'The design record drifted from the token source. Run bun run check and commit canon/DESIGN.md.',
|
|
242
242
|
},
|
|
243
243
|
{
|
|
244
244
|
kind: 'drift',
|
package/src/labels/map.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
+
import { resolveExisting } from '@/legacy-path'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Where a project declares its pull request label map
|
|
6
|
+
* Where a project declares its pull request label map.
|
|
6
7
|
*
|
|
7
8
|
* The file has already moved once, and that relocation rewrote every mention
|
|
8
|
-
* across four surfaces.
|
|
9
|
-
*
|
|
9
|
+
* across four surfaces. `LEGACY_MAP_REL` below is the one other place code
|
|
10
|
+
* spells it, kept only as a read fallback, so the next move is still one edit
|
|
11
|
+
* to the write path rather than a sweep.
|
|
10
12
|
*/
|
|
11
|
-
export const MAP_REL = join('
|
|
13
|
+
export const MAP_REL = join('canon', 'config', 'pr-labels.toml')
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Where the map lived before this move, `.claude/canon/pr-labels.toml`.
|
|
17
|
+
* `readLabelMap` falls back to it so a project that has not moved still reads
|
|
18
|
+
* its declared map.
|
|
19
|
+
*/
|
|
20
|
+
const LEGACY_MAP_REL = join('.claude', 'canon', 'pr-labels.toml')
|
|
12
21
|
|
|
13
22
|
/** A label name and the path prefixes that earn it, in the map's own order. */
|
|
14
23
|
export interface DomainRow {
|
|
@@ -90,9 +99,14 @@ export function parseLabelMap(source: string): LabelMap {
|
|
|
90
99
|
|
|
91
100
|
/** Reads the map a project declares at `root`, or says why it could not. */
|
|
92
101
|
export function readLabelMap(root: string): LabelMap {
|
|
102
|
+
const path = resolveExisting([
|
|
103
|
+
join(root, MAP_REL),
|
|
104
|
+
join(root, LEGACY_MAP_REL),
|
|
105
|
+
])
|
|
106
|
+
|
|
93
107
|
let source: string
|
|
94
108
|
try {
|
|
95
|
-
source = readFileSync(
|
|
109
|
+
source = readFileSync(path, 'utf8')
|
|
96
110
|
} catch {
|
|
97
111
|
return { kind: 'refused', reason: 'no-map' }
|
|
98
112
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where a fallback chain resolves for reading: the first path that exists,
|
|
5
|
+
* current first, or the first candidate when none exists yet, which is
|
|
6
|
+
* always the write target.
|
|
7
|
+
*
|
|
8
|
+
* `src/audits/baseline.ts`, `src/labels/map.ts`, and `src/sync/stamp.ts` each
|
|
9
|
+
* trail a file that moved, and each used to reason about its own chain
|
|
10
|
+
* separately. The tuple type keeps a caller from passing an empty list, since
|
|
11
|
+
* every chain here has a write target to fall back to.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveExisting(paths: readonly [string, ...string[]]): string {
|
|
14
|
+
return paths.find((path) => existsSync(path)) ?? paths[0]
|
|
15
|
+
}
|
package/src/markdown/scan.ts
CHANGED
|
@@ -206,7 +206,7 @@ export function maskDisplayed(text: string): string {
|
|
|
206
206
|
* A code span is walked around rather than through, since keeping it counted
|
|
207
207
|
* and then dropping spans from inside it takes back the decision. The
|
|
208
208
|
* placeholders this repository writes are the case: a reader is shown all of
|
|
209
|
-
*
|
|
209
|
+
* `canon/context/<domain>.md` and the autolink pattern reaches the angle
|
|
210
210
|
* brackets in the middle of it.
|
|
211
211
|
*/
|
|
212
212
|
export function visibleText(text: string): string {
|