@erclx/canon 5.38.0 → 5.39.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 +1 -1
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/SKILL.md +12 -9
- package/claude/skills/auto-ship/SKILL.md +43 -23
- package/claude/skills/backlog-triage/SKILL.md +62 -53
- package/claude/skills/backlog-triage/references/apply.md +69 -0
- package/claude/skills/build-in-slices/SKILL.md +8 -4
- package/claude/skills/canon-cli/SKILL.md +5 -1
- package/claude/skills/canon-feedback/SKILL.md +18 -8
- package/claude/skills/canon-feedback-triage/SKILL.md +10 -3
- package/claude/skills/canvas-board/SKILL.md +36 -17
- package/claude/skills/ci-workflow/SKILL.md +5 -4
- package/claude/skills/code-craft/SKILL.md +10 -6
- package/claude/skills/codebase-layout/SKILL.md +3 -5
- package/claude/skills/context-fold/REQUIREMENT.md +4 -4
- package/claude/skills/context-fold/SKILL.md +42 -23
- package/claude/skills/context-fold/references/context-refresh.md +4 -3
- package/claude/skills/context-fold/references/context.md +79 -64
- package/claude/skills/create-rule/SKILL.md +7 -3
- package/claude/skills/create-skill/SKILL.md +27 -7
- package/claude/skills/create-skill/references/skill-reference.md +41 -17
- package/claude/skills/decision-escalate/SKILL.md +8 -5
- package/claude/skills/deploy-app/SKILL.md +1 -1
- package/claude/skills/deprecation-migration/SKILL.md +12 -4
- package/claude/skills/design-taste/SKILL.md +17 -11
- package/claude/skills/docs-sync/REQUIREMENT.md +1 -0
- package/claude/skills/docs-sync/SKILL.md +17 -6
- package/claude/skills/docs-sync/references/docs.md +54 -21
- package/claude/skills/docs-sync/references/readme.md +32 -8
- package/claude/skills/document-health/SKILL.md +3 -4
- package/claude/skills/draft-and-pick/SKILL.md +73 -47
- package/claude/skills/draft-doc/SKILL.md +9 -4
- package/claude/skills/draft-doc/references/context.md +4 -4
- package/claude/skills/draft-doc/references/docs.md +1 -0
- package/claude/skills/draft-doc/references/wiki.md +3 -2
- package/claude/skills/draft-figure/SKILL.md +9 -7
- package/claude/skills/draft-identity/SKILL.md +24 -52
- package/claude/skills/draft-identity/references/card-route.md +46 -0
- package/claude/skills/draft-ready/SKILL.md +19 -7
- package/claude/skills/draft-screencast/SKILL.md +6 -112
- package/claude/skills/draft-screencast/references/beats-template.md +119 -0
- package/claude/skills/draft-slides/SKILL.md +7 -4
- package/claude/skills/git-branch/SKILL.md +3 -3
- package/claude/skills/git-commit/SKILL.md +5 -3
- package/claude/skills/git-followup/SKILL.md +16 -9
- package/claude/skills/git-issue/SKILL.md +6 -5
- package/claude/skills/git-pr/SKILL.md +56 -44
- package/claude/skills/git-ship/SKILL.md +43 -34
- package/claude/skills/git-ship/references/ci-watch.md +31 -0
- package/claude/skills/git-split/SKILL.md +28 -100
- package/claude/skills/git-split/references/independent.md +40 -0
- package/claude/skills/git-split/references/stacked.md +73 -0
- package/claude/skills/git-stage/SKILL.md +9 -7
- package/claude/skills/git-worktree/SKILL.md +27 -49
- package/claude/skills/git-worktree/references/cleanup.md +55 -0
- package/claude/skills/markdown-craft/SKILL.md +2 -2
- package/claude/skills/markdown-craft/references/markdown.md +82 -53
- package/claude/skills/plan-feature/SKILL.md +52 -46
- package/claude/skills/plan-feature/references/batches.md +46 -0
- package/claude/skills/plan-groundwork/SKILL.md +60 -74
- package/claude/skills/plan-groundwork/references/output.md +48 -0
- package/claude/skills/plan-intake/SKILL.md +28 -21
- package/claude/skills/plan-intake-answer/SKILL.md +14 -13
- package/claude/skills/read-frames/SKILL.md +2 -0
- package/claude/skills/record-screencast/SKILL.md +31 -11
- package/claude/skills/review-address/SKILL.md +76 -158
- package/claude/skills/review-address/references/rebase-only.md +34 -0
- package/claude/skills/review-craft/SKILL.md +27 -11
- package/claude/skills/review-pr/SKILL.md +91 -40
- package/claude/skills/review-ui/SKILL.md +62 -37
- package/claude/skills/review-ui/references/hosted-preview.md +30 -0
- package/claude/skills/role-orchestrator/SKILL.md +105 -74
- package/claude/skills/role-orchestrator/references/orchestrator-filing.md +26 -0
- package/claude/skills/role-planner/SKILL.md +66 -64
- package/claude/skills/role-reviewer/SKILL.md +57 -46
- package/claude/skills/role-worker/SKILL.md +68 -63
- package/claude/skills/search-craft/SKILL.md +7 -5
- package/claude/skills/session-compact/SKILL.md +16 -11
- package/claude/skills/session-map/SKILL.md +31 -10
- package/claude/skills/session-relay/SKILL.md +32 -65
- package/claude/skills/session-resume/SKILL.md +18 -13
- package/claude/skills/session-worktree/SKILL.md +70 -42
- package/claude/skills/systematic-debugging/SKILL.md +6 -1
- package/claude/skills/target-check/SKILL.md +13 -6
- package/claude/skills/target-setup/SKILL.md +39 -27
- package/claude/skills/task-board/SKILL.md +68 -60
- package/claude/skills/task-board/references/create-report.md +36 -0
- package/claude/skills/task-complete/SKILL.md +20 -18
- package/claude/skills/teach-workspace/SKILL.md +61 -33
- package/claude/skills/test-craft/SKILL.md +2 -1
- package/claude/skills/test-first/SKILL.md +15 -9
- package/claude/skills/ui-checklist/SKILL.md +18 -16
- package/claude/skills/ux-audit/SKILL.md +9 -10
- package/claude/skills/ux-measure/SKILL.md +22 -18
- package/claude/skills/video-craft/SKILL.md +15 -7
- package/claude/skills/write-human/SKILL.md +9 -1
- package/claude/skills/youtube-transcripts/SKILL.md +2 -1
- package/docs/agents/context-audit-checks.md +19 -1
- package/docs/agents/context-audit.md +2 -2
- package/docs/agents/driver.md +1 -1
- package/docs/agents/hooks.md +21 -1
- package/docs/agents/index.md +10 -14
- package/docs/agents/indexes.md +25 -29
- package/docs/agents/install-and-sync.md +124 -235
- package/docs/agents/markdown-audit-density.md +39 -0
- package/docs/agents/markdown-audit-weight.md +9 -7
- package/docs/agents/markdown-audit.md +18 -14
- package/docs/agents/migrate.md +41 -16
- package/docs/agents/output-shape.md +60 -45
- package/docs/agents/overview.md +36 -12
- package/docs/agents/routing.md +1 -1
- package/docs/agents/rule-citations.md +1 -1
- package/docs/agents/skills-reach.md +1 -1
- package/docs/agents/targets.md +25 -77
- package/docs/target/migrations.md +1 -1
- package/docs/target/projects.md +2 -2
- package/docs/workflow/ai-workflow.md +2 -2
- package/docs/workflow/parallel-features.md +2 -2
- package/docs/workflow/skill-map.md +1 -1
- package/docs/workflow/visual-design-workflow.md +1 -1
- package/package.json +1 -1
- package/src/commands/claude.ts +1 -1
- package/src/commands/context.ts +106 -7
- package/src/commands/hooks.ts +63 -0
- package/src/commands/markdown.ts +169 -32
- package/src/context/audit.ts +256 -5
- package/src/context/classify/prompts.ts +1 -1
- package/src/context/gate.ts +23 -7
- package/src/context/shape-gated.ts +28 -0
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/measures.ts +15 -190
- package/src/gate/stages.ts +7 -23
- package/src/hooks/reply-links.ts +110 -0
- package/src/markdown/density.ts +258 -0
- package/src/markdown/gate.ts +57 -18
- package/src/markdown/reference-shape.ts +103 -0
- package/src/markdown/scope.ts +174 -0
- package/src/markdown/structure.ts +9 -59
- package/src/shipped/references.ts +1 -1
- package/tooling/claude/seeds/.claude/hooks/reply-links.sh +72 -0
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +22 -1
- package/tooling/claude/seeds/.claude/settings.json +4 -0
- package/docs/agents/commands.md +0 -154
- package/docs/agents/docs.md +0 -27
- package/docs/agents/drift-surfaces.md +0 -201
- package/docs/agents/gov-detect.md +0 -47
- package/docs/agents/scripting.md +0 -142
- package/src/markdown/growth.ts +0 -43
package/src/context/audit.ts
CHANGED
|
@@ -67,8 +67,7 @@ export const REQUIRED_SECTIONS_BY_FOLDER: Readonly<
|
|
|
67
67
|
* The closed set of H2 sections per audited folder name, which a required
|
|
68
68
|
* section belongs to. An entry carrying another H2 is a finding under
|
|
69
69
|
* `--gate`, since the set is what makes a context entry cheap to find a fact
|
|
70
|
-
* in: a domain's
|
|
71
|
-
* `Decisions` and `Gotchas`.
|
|
70
|
+
* in: a domain's folders go one level down as H3 items under `Layout`.
|
|
72
71
|
*
|
|
73
72
|
* Held beside the required lists for the same reason they are held here, and
|
|
74
73
|
* keyed on the folder whose standard states it.
|
|
@@ -76,9 +75,35 @@ export const REQUIRED_SECTIONS_BY_FOLDER: Readonly<
|
|
|
76
75
|
export const ALLOWED_SECTIONS_BY_FOLDER: Readonly<
|
|
77
76
|
Record<string, readonly string[]>
|
|
78
77
|
> = {
|
|
79
|
-
context: ['Overview', 'Layout', '
|
|
78
|
+
context: ['Overview', 'Layout', 'Gotchas'],
|
|
80
79
|
}
|
|
81
80
|
|
|
81
|
+
/**
|
|
82
|
+
* H2 sections the standard once allowed and now forbids, per audited folder.
|
|
83
|
+
*
|
|
84
|
+
* Kept apart from the extra sections because every unrebuilt entry still
|
|
85
|
+
* carries one, and an extra section gates under `--gate` everywhere. A retired
|
|
86
|
+
* section is a shape finding instead, which gates only where the folder was
|
|
87
|
+
* rebuilt, per `SHAPE_GATED_FOLDERS`.
|
|
88
|
+
*/
|
|
89
|
+
export const RETIRED_SECTIONS_BY_FOLDER: Readonly<
|
|
90
|
+
Record<string, readonly string[]>
|
|
91
|
+
> = {
|
|
92
|
+
context: ['Decisions'],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Bullets a `Gotchas` section may hold, counted at any indent. */
|
|
96
|
+
export const GOTCHA_CAP = 5
|
|
97
|
+
|
|
98
|
+
/** Bullets a folder H3 under `Layout` may hold, counted at any indent. */
|
|
99
|
+
export const LAYOUT_BULLET_CAP = 5
|
|
100
|
+
|
|
101
|
+
/** Paragraphs a folder H3 under `Layout` may hold, being its one lead. */
|
|
102
|
+
export const LAYOUT_PARAGRAPH_CAP = 1
|
|
103
|
+
|
|
104
|
+
/** The one Layout H3 not titled with a path, holding the trivial folders. */
|
|
105
|
+
export const OTHER_FOLDERS = 'Other folders'
|
|
106
|
+
|
|
82
107
|
/**
|
|
83
108
|
* The context standard's list, kept as its own export because the audit record
|
|
84
109
|
* has published it flat under `checkpoints.requiredSections` since before any
|
|
@@ -210,6 +235,34 @@ export interface BareReferenceFinding {
|
|
|
210
235
|
readonly name: string
|
|
211
236
|
}
|
|
212
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Each way an entry breaks the section shape the context standard states.
|
|
240
|
+
*
|
|
241
|
+
* `retired-section` is a `Decisions` H2. The three `gotcha-*` kinds are a
|
|
242
|
+
* `Gotchas` section past its bullet cap, holding a heading, or holding a
|
|
243
|
+
* paragraph. The three `layout-*` kinds are a Layout H3 titled neither a path
|
|
244
|
+
* nor `Other folders`, a folder H3 past its bullet or paragraph cap, and a
|
|
245
|
+
* heading below H3 under `Layout`.
|
|
246
|
+
*/
|
|
247
|
+
export type ShapeKind =
|
|
248
|
+
| 'retired-section'
|
|
249
|
+
| 'gotcha-count'
|
|
250
|
+
| 'gotcha-heading'
|
|
251
|
+
| 'gotcha-paragraph'
|
|
252
|
+
| 'layout-heading'
|
|
253
|
+
| 'layout-bullets'
|
|
254
|
+
| 'layout-paragraphs'
|
|
255
|
+
| 'layout-depth'
|
|
256
|
+
|
|
257
|
+
export interface ShapeIssue {
|
|
258
|
+
readonly line: number
|
|
259
|
+
readonly kind: ShapeKind
|
|
260
|
+
/** The heading or line as written, so a report names where to look. */
|
|
261
|
+
readonly text: string
|
|
262
|
+
/** Bullets or paragraphs counted, on the two cap kinds alone. */
|
|
263
|
+
readonly count?: number
|
|
264
|
+
}
|
|
265
|
+
|
|
213
266
|
export interface EntryReport {
|
|
214
267
|
readonly rel: string
|
|
215
268
|
/**
|
|
@@ -247,6 +300,12 @@ export interface EntryReport {
|
|
|
247
300
|
* all is `extraSections`.
|
|
248
301
|
*/
|
|
249
302
|
readonly otherSections: readonly string[]
|
|
303
|
+
/**
|
|
304
|
+
* Breaks of the section shape, in document order, and empty outside the
|
|
305
|
+
* governed folder. Whether one gates depends on where the entry sits, which
|
|
306
|
+
* `shapeFindings` decides rather than the measure.
|
|
307
|
+
*/
|
|
308
|
+
readonly shape: readonly ShapeIssue[]
|
|
250
309
|
/**
|
|
251
310
|
* Whether the file declares itself a skeleton, which excludes it from the
|
|
252
311
|
* section check alone. Every other measure still reads it, since a stub is
|
|
@@ -483,6 +542,7 @@ const H2_TEXT = /^##\s+(.+?)\s*$/
|
|
|
483
542
|
function otherSections(
|
|
484
543
|
lines: readonly BodyLine[],
|
|
485
544
|
allowed: readonly string[],
|
|
545
|
+
retired: readonly string[],
|
|
486
546
|
): string[] {
|
|
487
547
|
if (allowed.length === 0) return []
|
|
488
548
|
|
|
@@ -492,12 +552,161 @@ function otherSections(
|
|
|
492
552
|
if (line.fenced) continue
|
|
493
553
|
|
|
494
554
|
const match = line.text.match(H2_TEXT)
|
|
495
|
-
if (match && !allowed.includes(match[1])
|
|
555
|
+
if (match && !allowed.includes(match[1]) && !retired.includes(match[1])) {
|
|
556
|
+
found.push(match[1])
|
|
557
|
+
}
|
|
496
558
|
}
|
|
497
559
|
|
|
498
560
|
return found
|
|
499
561
|
}
|
|
500
562
|
|
|
563
|
+
const HEADING_LEVEL = /^(#{1,6})\s+(.+?)\s*$/
|
|
564
|
+
const LIST_ITEM = /^\s*(?:[-*+]|\d+[.)])\s+/
|
|
565
|
+
const INDENTED = /^\s+\S/
|
|
566
|
+
const FOLDER_TITLE = /^`[^`]+`$/
|
|
567
|
+
|
|
568
|
+
interface FolderHeading {
|
|
569
|
+
readonly line: number
|
|
570
|
+
readonly text: string
|
|
571
|
+
bullets: number
|
|
572
|
+
paragraphs: number
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Finds where the entry breaks the section shape.
|
|
577
|
+
*
|
|
578
|
+
* Bullets are counted at any indent and across any paragraph, so a list split
|
|
579
|
+
* by a bold label or nested one level down still counts against the cap it
|
|
580
|
+
* would otherwise slip under. A line continuing a list item, lazily or
|
|
581
|
+
* indented after a blank, belongs to the item rather than opening a paragraph,
|
|
582
|
+
* which is what keeps a wrapped bullet from reading as prose.
|
|
583
|
+
*
|
|
584
|
+
* A cap reports once per section or folder heading however far past it the
|
|
585
|
+
* count runs, since the fix is one cut either way. `Other folders` is exempt
|
|
586
|
+
* from both folder caps, its one-line bullets being held by the bullet run
|
|
587
|
+
* limit `canon markdown audit` already gates.
|
|
588
|
+
*/
|
|
589
|
+
function shapeIssues(
|
|
590
|
+
lines: readonly BodyLine[],
|
|
591
|
+
retired: readonly string[],
|
|
592
|
+
): ShapeIssue[] {
|
|
593
|
+
const issues: ShapeIssue[] = []
|
|
594
|
+
let section: string | undefined
|
|
595
|
+
let gotchas: { line: number; bullets: number } | undefined
|
|
596
|
+
let folder: FolderHeading | undefined
|
|
597
|
+
let inItem = false
|
|
598
|
+
let inParagraph = false
|
|
599
|
+
let afterBlank = false
|
|
600
|
+
|
|
601
|
+
const closeFolder = (): void => {
|
|
602
|
+
if (folder && folder.text !== OTHER_FOLDERS) {
|
|
603
|
+
if (folder.paragraphs > LAYOUT_PARAGRAPH_CAP) {
|
|
604
|
+
issues.push({
|
|
605
|
+
line: folder.line,
|
|
606
|
+
kind: 'layout-paragraphs',
|
|
607
|
+
text: folder.text,
|
|
608
|
+
count: folder.paragraphs,
|
|
609
|
+
})
|
|
610
|
+
}
|
|
611
|
+
if (folder.bullets > LAYOUT_BULLET_CAP) {
|
|
612
|
+
issues.push({
|
|
613
|
+
line: folder.line,
|
|
614
|
+
kind: 'layout-bullets',
|
|
615
|
+
text: folder.text,
|
|
616
|
+
count: folder.bullets,
|
|
617
|
+
})
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
folder = undefined
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const closeGotchas = (): void => {
|
|
624
|
+
if (gotchas && gotchas.bullets > GOTCHA_CAP) {
|
|
625
|
+
issues.push({
|
|
626
|
+
line: gotchas.line,
|
|
627
|
+
kind: 'gotcha-count',
|
|
628
|
+
text: 'Gotchas',
|
|
629
|
+
count: gotchas.bullets,
|
|
630
|
+
})
|
|
631
|
+
}
|
|
632
|
+
gotchas = undefined
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
for (const line of lines) {
|
|
636
|
+
if (line.fenced) continue
|
|
637
|
+
|
|
638
|
+
const heading = line.text.match(HEADING_LEVEL)
|
|
639
|
+
if (heading) {
|
|
640
|
+
const level = heading[1].length
|
|
641
|
+
const text = heading[2]
|
|
642
|
+
inItem = false
|
|
643
|
+
inParagraph = false
|
|
644
|
+
afterBlank = false
|
|
645
|
+
|
|
646
|
+
if (level <= 2) {
|
|
647
|
+
closeFolder()
|
|
648
|
+
closeGotchas()
|
|
649
|
+
section = level === 2 ? text : undefined
|
|
650
|
+
if (level === 2 && retired.includes(text)) {
|
|
651
|
+
issues.push({ line: line.number, kind: 'retired-section', text })
|
|
652
|
+
}
|
|
653
|
+
if (section === 'Gotchas') gotchas = { line: line.number, bullets: 0 }
|
|
654
|
+
continue
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (section === 'Gotchas') {
|
|
658
|
+
issues.push({ line: line.number, kind: 'gotcha-heading', text })
|
|
659
|
+
} else if (section === 'Layout' && level === 3) {
|
|
660
|
+
closeFolder()
|
|
661
|
+
folder = { line: line.number, text, bullets: 0, paragraphs: 0 }
|
|
662
|
+
if (!FOLDER_TITLE.test(text) && text !== OTHER_FOLDERS) {
|
|
663
|
+
issues.push({ line: line.number, kind: 'layout-heading', text })
|
|
664
|
+
}
|
|
665
|
+
} else if (section === 'Layout') {
|
|
666
|
+
issues.push({ line: line.number, kind: 'layout-depth', text })
|
|
667
|
+
}
|
|
668
|
+
continue
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (line.text.trim() === '') {
|
|
672
|
+
inParagraph = false
|
|
673
|
+
afterBlank = true
|
|
674
|
+
continue
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (LIST_ITEM.test(line.text)) {
|
|
678
|
+
inItem = true
|
|
679
|
+
inParagraph = false
|
|
680
|
+
afterBlank = false
|
|
681
|
+
if (gotchas) gotchas.bullets++
|
|
682
|
+
if (folder) folder.bullets++
|
|
683
|
+
continue
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const continues = inItem && (!afterBlank || INDENTED.test(line.text))
|
|
687
|
+
afterBlank = false
|
|
688
|
+
if (continues) continue
|
|
689
|
+
|
|
690
|
+
inItem = false
|
|
691
|
+
if (inParagraph) continue
|
|
692
|
+
inParagraph = true
|
|
693
|
+
|
|
694
|
+
if (gotchas) {
|
|
695
|
+
issues.push({
|
|
696
|
+
line: line.number,
|
|
697
|
+
kind: 'gotcha-paragraph',
|
|
698
|
+
text: line.text.trim(),
|
|
699
|
+
})
|
|
700
|
+
}
|
|
701
|
+
if (folder) folder.paragraphs++
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
closeFolder()
|
|
705
|
+
closeGotchas()
|
|
706
|
+
|
|
707
|
+
return issues.sort((a, b) => a.line - b.line)
|
|
708
|
+
}
|
|
709
|
+
|
|
501
710
|
/**
|
|
502
711
|
* Finds the references naming a sibling entry by bare filename.
|
|
503
712
|
*
|
|
@@ -557,6 +766,9 @@ export function measureEntry(
|
|
|
557
766
|
siblings: readonly string[] = [],
|
|
558
767
|
required: readonly string[] = governsContent ? REQUIRED_SECTIONS : [],
|
|
559
768
|
allowed: readonly string[] = [],
|
|
769
|
+
retired: readonly string[] = governsContent
|
|
770
|
+
? RETIRED_SECTIONS_BY_FOLDER.context
|
|
771
|
+
: [],
|
|
560
772
|
): EntryReport {
|
|
561
773
|
const lines = bodyLines(source)
|
|
562
774
|
|
|
@@ -567,7 +779,8 @@ export function measureEntry(
|
|
|
567
779
|
provenance: governsContent ? provenance(lines) : [],
|
|
568
780
|
bareReferences: bareReferences(lines, siblings),
|
|
569
781
|
sections: declaredSections(lines, required),
|
|
570
|
-
otherSections: otherSections(lines, allowed),
|
|
782
|
+
otherSections: otherSections(lines, allowed, retired),
|
|
783
|
+
shape: governsContent ? shapeIssues(lines, retired) : [],
|
|
571
784
|
stub: isStubSeed(source),
|
|
572
785
|
governed: governsContent,
|
|
573
786
|
}
|
|
@@ -648,6 +861,7 @@ export async function measureFolders(
|
|
|
648
861
|
names.filter((name) => name !== self),
|
|
649
862
|
requiredSections(folder),
|
|
650
863
|
allowedSections(folder),
|
|
864
|
+
retiredSections(folder),
|
|
651
865
|
),
|
|
652
866
|
)
|
|
653
867
|
}
|
|
@@ -675,6 +889,13 @@ export function allowedSections(folder: AuditedFolder): readonly string[] {
|
|
|
675
889
|
: []
|
|
676
890
|
}
|
|
677
891
|
|
|
892
|
+
/** Names the H2 sections the folder's standard retired, empty where it retired none. */
|
|
893
|
+
export function retiredSections(folder: AuditedFolder): readonly string[] {
|
|
894
|
+
return Object.hasOwn(RETIRED_SECTIONS_BY_FOLDER, folder.name)
|
|
895
|
+
? RETIRED_SECTIONS_BY_FOLDER[folder.name]
|
|
896
|
+
: []
|
|
897
|
+
}
|
|
898
|
+
|
|
678
899
|
/**
|
|
679
900
|
* Reports whether a bare sibling name here is a reference by construction.
|
|
680
901
|
*
|
|
@@ -771,3 +992,33 @@ export function extraSections(
|
|
|
771
992
|
.filter((entry) => !entry.stub && entry.otherSections.length > 0)
|
|
772
993
|
.map((entry) => ({ rel: entry.rel, headings: entry.otherSections }))
|
|
773
994
|
}
|
|
995
|
+
|
|
996
|
+
export interface ShapeFinding {
|
|
997
|
+
/** Repo-relative path of the entry breaking the shape. */
|
|
998
|
+
readonly rel: string
|
|
999
|
+
/** Whether the entry sits where the shape gates under `--gate`. */
|
|
1000
|
+
readonly gated: boolean
|
|
1001
|
+
/** Every break in the entry, never empty. */
|
|
1002
|
+
readonly issues: readonly ShapeIssue[]
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Names each entry breaking the section shape, and whether it gates.
|
|
1007
|
+
*
|
|
1008
|
+
* Where it gates is the caller's predicate rather than a list read here, since
|
|
1009
|
+
* the folders rebuilt to the shape are named by path from the repository root
|
|
1010
|
+
* and `measureFolders` holds only paths from the audit root. A stub seed is
|
|
1011
|
+
* dropped for the reason `extraSections` drops one.
|
|
1012
|
+
*/
|
|
1013
|
+
export function shapeFindings(
|
|
1014
|
+
entries: readonly EntryReport[],
|
|
1015
|
+
isGated: (rel: string) => boolean,
|
|
1016
|
+
): ShapeFinding[] {
|
|
1017
|
+
return entries
|
|
1018
|
+
.filter((entry) => !entry.stub && entry.shape.length > 0)
|
|
1019
|
+
.map((entry) => ({
|
|
1020
|
+
rel: entry.rel,
|
|
1021
|
+
gated: isGated(entry.rel),
|
|
1022
|
+
issues: entry.shape,
|
|
1023
|
+
}))
|
|
1024
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* mode) and `sweep-v1.md` (sweep mode). The diff prompt is verbatim. The sweep
|
|
6
6
|
* prompt was tuned past `sweep-v1.md` with one decision check, measured
|
|
7
7
|
* against the groundwork's labelled set in
|
|
8
|
-
* `canon/context/context-model
|
|
8
|
+
* `canon/context/context-model.md`.
|
|
9
9
|
*
|
|
10
10
|
* Each prompt already states its own rules per canonical doc type inside one
|
|
11
11
|
* string, which is the shape the spike measured, so there is one prompt per
|
package/src/context/gate.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ExtraSectionFinding,
|
|
3
|
+
SectionFinding,
|
|
4
|
+
ShapeFinding,
|
|
5
|
+
} from '@/context/audit'
|
|
2
6
|
import type { FolderDrift } from '@/context/index-drift'
|
|
3
7
|
|
|
4
8
|
export interface GateInput {
|
|
@@ -15,6 +19,11 @@ export interface GateInput {
|
|
|
15
19
|
readonly sections: readonly SectionFinding[]
|
|
16
20
|
/** H2 headings outside a folder's closed set, gating under `--gate` alone. */
|
|
17
21
|
readonly extraSections: readonly ExtraSectionFinding[]
|
|
22
|
+
/**
|
|
23
|
+
* Section shape breaks, gating under `--gate` only where the finding is
|
|
24
|
+
* marked gated, so an unrebuilt entry reports and never fails a push.
|
|
25
|
+
*/
|
|
26
|
+
readonly shape: readonly ShapeFinding[]
|
|
18
27
|
readonly drift: readonly FolderDrift[]
|
|
19
28
|
/**
|
|
20
29
|
* Whether the caller asked for the widened gate. False leaves a missing
|
|
@@ -36,17 +45,19 @@ export function hasDrift(drift: readonly FolderDrift[]): boolean {
|
|
|
36
45
|
*
|
|
37
46
|
* An unresolved citation is a broken pointer and gates unconditionally, and so
|
|
38
47
|
* does a requirements record past its own word cap: the record states its rule
|
|
39
|
-
* for itself, which makes it a fact rather than a threshold a reader weighs.
|
|
40
|
-
* the ones answerable from the file itself: a
|
|
41
|
-
* declare, an H2 outside the closed set,
|
|
42
|
-
*
|
|
43
|
-
*
|
|
48
|
+
* for itself, which makes it a fact rather than a threshold a reader weighs.
|
|
49
|
+
* The findings `--gate` adds are the ones answerable from the file itself: a
|
|
50
|
+
* required section it does not declare, an H2 outside the closed set, a shape
|
|
51
|
+
* break in a folder rebuilt to the shape, and an index disagreeing with its
|
|
52
|
+
* folder. Entry length, depth, bullet, table, provenance, and the record's
|
|
53
|
+
* claim coverage are judgments, so they stay out under both modes.
|
|
44
54
|
*/
|
|
45
55
|
export function isGating({
|
|
46
56
|
unresolvedCitations,
|
|
47
57
|
requirementsOverWords,
|
|
48
58
|
sections,
|
|
49
59
|
extraSections,
|
|
60
|
+
shape,
|
|
50
61
|
drift,
|
|
51
62
|
widened,
|
|
52
63
|
}: GateInput): boolean {
|
|
@@ -54,5 +65,10 @@ export function isGating({
|
|
|
54
65
|
if (requirementsOverWords) return true
|
|
55
66
|
if (!widened) return false
|
|
56
67
|
|
|
57
|
-
return
|
|
68
|
+
return (
|
|
69
|
+
sections.length > 0 ||
|
|
70
|
+
extraSections.length > 0 ||
|
|
71
|
+
shape.some((finding) => finding.gated) ||
|
|
72
|
+
hasDrift(drift)
|
|
73
|
+
)
|
|
58
74
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path prefixes, from the repository root, whose context entries gate on the
|
|
3
|
+
* section shape under `--gate`.
|
|
4
|
+
*
|
|
5
|
+
* Every other entry still reports its shape findings and never fails a push,
|
|
6
|
+
* since the entries are rebuilt to the shape a batch at a time and an unrebuilt
|
|
7
|
+
* one carries `## Decisions` by construction. A batch rebuilding a folder adds
|
|
8
|
+
* its prefix here in the same change, which is what holds the folder to the
|
|
9
|
+
* shape from then on.
|
|
10
|
+
*
|
|
11
|
+
* Read against the repository root rather than the audit root, so a seed tree
|
|
12
|
+
* audited at its own root is listed by its path in the toolkit, such as
|
|
13
|
+
* `tooling/base/seeds/`, and never collides with the entry a target installs
|
|
14
|
+
* from it.
|
|
15
|
+
*/
|
|
16
|
+
export const SHAPE_GATED_FOLDERS: readonly string[] = [
|
|
17
|
+
'canon/context/context-model.md',
|
|
18
|
+
'canon/context/design.md',
|
|
19
|
+
'canon/context/web.md',
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
/** Whether a repository-relative entry path sits under a gated prefix. */
|
|
23
|
+
export function isShapeGated(
|
|
24
|
+
rel: string,
|
|
25
|
+
prefixes: readonly string[] = SHAPE_GATED_FOLDERS,
|
|
26
|
+
): boolean {
|
|
27
|
+
return prefixes.some((prefix) => rel.startsWith(prefix))
|
|
28
|
+
}
|
package/src/design/document.ts
CHANGED
|
@@ -95,7 +95,7 @@ function borderRows(tokens: readonly BorderToken[]): string[][] {
|
|
|
95
95
|
*/
|
|
96
96
|
const LEAD = [
|
|
97
97
|
'Authoring guidance: `claude/skills/canvas-board/references/design.md`. Rendered from `src/design/tokens.ts` by `canon design regen`, so edit the module and never this file.',
|
|
98
|
-
'Why each value was chosen lives in `canon/context/design
|
|
98
|
+
'Why each value was chosen lives in `canon/context/design.md`.',
|
|
99
99
|
].join(' ')
|
|
100
100
|
|
|
101
101
|
function bullets(rules: readonly string[]): string {
|
package/src/design/tokens.ts
CHANGED
|
@@ -59,7 +59,7 @@ export interface BorderToken {
|
|
|
59
59
|
/**
|
|
60
60
|
* Every prose slot is a list of one-line rules rather than a string, so a
|
|
61
61
|
* bullet is the unit the renderer emits and the prose budget counts, and a
|
|
62
|
-
* value needing a paragraph of why goes to `canon/context/design
|
|
62
|
+
* value needing a paragraph of why goes to `canon/context/design.md`.
|
|
63
63
|
*/
|
|
64
64
|
export interface DesignTokens {
|
|
65
65
|
readonly personality: string
|