@erclx/canon 5.38.0 → 5.40.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 +5 -5
- 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 +37 -18
- 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 +9 -7
- package/claude/skills/draft-slides/references/deck.md +183 -0
- 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-pr/references/evidence.md +2 -2
- package/claude/skills/git-pr/references/final-command.md +1 -1
- package/claude/skills/git-pr/references/labels.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- 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 +63 -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-orchestrator/references/orchestrator-handback.md +1 -1
- 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/board.md +1 -1
- package/claude/skills/task-board/references/create-report.md +36 -0
- package/claude/skills/task-board/references/tasks.md +1 -1
- 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 +23 -19
- 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/audits.md +61 -60
- package/docs/agents/context-audit.md +40 -58
- package/docs/agents/index.md +11 -61
- package/docs/agents/indexes.md +25 -29
- package/docs/agents/install-and-sync.md +125 -235
- package/docs/agents/markdown-audit.md +54 -96
- 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/records.md +37 -79
- package/docs/agents/sessions-transfer.md +26 -70
- package/docs/agents/targets.md +25 -77
- package/docs/target/index.md +0 -1
- package/docs/target/migrations.md +57 -66
- package/docs/target/projects.md +36 -78
- package/docs/target/scenarios.md +9 -19
- package/docs/target/sync.md +27 -68
- package/docs/workflow/ai-workflow.md +22 -68
- package/docs/workflow/operating-model.md +32 -186
- package/docs/workflow/parallel-features.md +29 -66
- package/docs/workflow/skill-map-on-request.md +22 -24
- package/docs/workflow/skill-map.md +72 -76
- package/docs/workflow/visual-design-workflow.md +25 -118
- package/docs/workflow/zshrc-aliases.md +18 -37
- package/package.json +1 -1
- package/src/canvas/client/inspector/fields.css +4 -0
- package/src/canvas/index.html +6 -0
- package/src/claude/skills-reach.ts +2 -2
- package/src/cli/ui.ts +3 -1
- package/src/commands/canvas.ts +23 -0
- package/src/commands/capture.ts +14 -0
- package/src/commands/claude.ts +14 -1
- package/src/commands/context.ts +106 -7
- package/src/commands/demo.ts +22 -0
- package/src/commands/deps.ts +6 -0
- package/src/commands/driver.ts +28 -2
- package/src/commands/gov.ts +52 -18
- package/src/commands/hooks.ts +63 -0
- package/src/commands/intake.ts +17 -0
- package/src/commands/labels.ts +38 -5
- package/src/commands/markdown.ts +169 -32
- package/src/commands/pr.ts +160 -43
- package/src/commands/records.ts +8 -0
- package/src/commands/secrets.ts +6 -0
- package/src/commands/sessions.ts +8 -0
- package/src/commands/tasks.ts +52 -0
- package/src/commands/teach.ts +13 -0
- package/src/commands/upstream.ts +25 -1
- package/src/commands/worktrees.ts +5 -0
- package/src/context/audit.ts +274 -5
- package/src/context/classify/prompts.ts +1 -1
- package/src/context/folders.ts +1 -1
- package/src/context/gate.ts +23 -7
- package/src/context/shape-gated.ts +30 -0
- package/src/counts/scan.ts +1 -1
- package/src/design/components.ts +1 -1
- package/src/design/document.ts +1 -1
- package/src/design/tokens.ts +1 -1
- package/src/gate/boundaries.ts +4 -1
- package/src/gate/measures.ts +16 -191
- package/src/gate/stages.ts +14 -30
- package/src/git/env.ts +6 -0
- 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 +162 -0
- package/src/markdown/structure.ts +9 -59
- package/src/pr/lift.ts +153 -19
- package/src/pr/painted.ts +87 -0
- package/src/roots/record.ts +18 -1
- package/src/sandbox/census.ts +1 -1
- package/src/sandbox/headless/run.ts +3 -2
- package/src/sandbox/headless/watch.ts +3 -4
- package/src/shipped/references.ts +1 -1
- package/src/slides/convert/master.ts +3 -0
- package/src/sync/format.ts +46 -0
- package/src/sync/stamp.ts +6 -0
- package/tooling/astro/reference.md +2 -2
- package/tooling/base/reference.md +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/tooling/go/reference.md +1 -1
- package/tooling/nextjs/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +1 -1
- package/tooling/vite-react/reference.md +1 -1
- package/tooling/web/reference.md +1 -1
- package/docs/agents/canvas.md +0 -86
- package/docs/agents/capture.md +0 -65
- package/docs/agents/census.md +0 -23
- package/docs/agents/commands.md +0 -154
- package/docs/agents/comments.md +0 -34
- package/docs/agents/context-audit-checks.md +0 -90
- package/docs/agents/context-classify.md +0 -101
- package/docs/agents/counts.md +0 -74
- package/docs/agents/demo.md +0 -140
- package/docs/agents/docs.md +0 -27
- package/docs/agents/drift-surfaces.md +0 -201
- package/docs/agents/driver.md +0 -103
- package/docs/agents/gate.md +0 -90
- package/docs/agents/gov-detect.md +0 -47
- package/docs/agents/hooks.md +0 -58
- package/docs/agents/intake.md +0 -81
- package/docs/agents/key-changes.md +0 -116
- package/docs/agents/label-coverage.md +0 -86
- package/docs/agents/markdown-audit-weight.md +0 -81
- package/docs/agents/pr-evidence-layout.md +0 -70
- package/docs/agents/pr-evidence.md +0 -273
- package/docs/agents/pr-frames.md +0 -131
- package/docs/agents/pr-lift.md +0 -132
- package/docs/agents/pr-local.md +0 -111
- package/docs/agents/pr-preview.md +0 -155
- package/docs/agents/pr-reads.md +0 -111
- package/docs/agents/pr-tick.md +0 -72
- package/docs/agents/records-push.md +0 -61
- package/docs/agents/records-stale.md +0 -40
- package/docs/agents/records-validate.md +0 -57
- package/docs/agents/restated.md +0 -83
- package/docs/agents/routing.md +0 -61
- package/docs/agents/rule-citations.md +0 -90
- package/docs/agents/sandbox.md +0 -126
- package/docs/agents/scripting.md +0 -142
- package/docs/agents/sessions.md +0 -128
- package/docs/agents/skills-audit.md +0 -120
- package/docs/agents/skills-reach.md +0 -64
- package/docs/agents/slides.md +0 -176
- package/docs/agents/state-scoped-risk.md +0 -105
- package/docs/agents/superseded.md +0 -85
- package/docs/agents/tasks-archive.md +0 -83
- package/docs/agents/tasks-plans.md +0 -152
- package/docs/agents/tasks-validate.md +0 -133
- package/docs/agents/tasks.md +0 -123
- package/docs/agents/teach-authoring.md +0 -118
- package/docs/agents/teach.md +0 -157
- package/docs/agents/test-order.md +0 -58
- package/docs/agents/upstream.md +0 -94
- package/docs/agents/worktrees.md +0 -118
- package/docs/target/standards-moved.md +0 -34
- 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,7 +75,46 @@ 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'],
|
|
79
|
+
}
|
|
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
|
+
|
|
107
|
+
/** The run-command table H3 only the development entry carries under `Layout`. */
|
|
108
|
+
export const SCRIPTS_HEADING = 'Scripts'
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Whether an entry path is the development entry, being the flat entry, the
|
|
112
|
+
* overview of a split one a target may still carry, or this repository's
|
|
113
|
+
* `local-loop` entry. The standard exempts the
|
|
114
|
+
* `Scripts` H3 for that entry alone, so the path is what tells.
|
|
115
|
+
*/
|
|
116
|
+
function isDevelopmentEntry(rel: string): boolean {
|
|
117
|
+
return /(?:^|\/)development(?:\/(?:overview|local-loop))?\.md$/.test(rel)
|
|
80
118
|
}
|
|
81
119
|
|
|
82
120
|
/**
|
|
@@ -210,6 +248,34 @@ export interface BareReferenceFinding {
|
|
|
210
248
|
readonly name: string
|
|
211
249
|
}
|
|
212
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Each way an entry breaks the section shape the context standard states.
|
|
253
|
+
*
|
|
254
|
+
* `retired-section` is a `Decisions` H2. The three `gotcha-*` kinds are a
|
|
255
|
+
* `Gotchas` section past its bullet cap, holding a heading, or holding a
|
|
256
|
+
* paragraph. The three `layout-*` kinds are a Layout H3 titled neither a path
|
|
257
|
+
* nor `Other folders`, a folder H3 past its bullet or paragraph cap, and a
|
|
258
|
+
* heading below H3 under `Layout`.
|
|
259
|
+
*/
|
|
260
|
+
export type ShapeKind =
|
|
261
|
+
| 'retired-section'
|
|
262
|
+
| 'gotcha-count'
|
|
263
|
+
| 'gotcha-heading'
|
|
264
|
+
| 'gotcha-paragraph'
|
|
265
|
+
| 'layout-heading'
|
|
266
|
+
| 'layout-bullets'
|
|
267
|
+
| 'layout-paragraphs'
|
|
268
|
+
| 'layout-depth'
|
|
269
|
+
|
|
270
|
+
export interface ShapeIssue {
|
|
271
|
+
readonly line: number
|
|
272
|
+
readonly kind: ShapeKind
|
|
273
|
+
/** The heading or line as written, so a report names where to look. */
|
|
274
|
+
readonly text: string
|
|
275
|
+
/** Bullets or paragraphs counted, on the two cap kinds alone. */
|
|
276
|
+
readonly count?: number
|
|
277
|
+
}
|
|
278
|
+
|
|
213
279
|
export interface EntryReport {
|
|
214
280
|
readonly rel: string
|
|
215
281
|
/**
|
|
@@ -247,6 +313,12 @@ export interface EntryReport {
|
|
|
247
313
|
* all is `extraSections`.
|
|
248
314
|
*/
|
|
249
315
|
readonly otherSections: readonly string[]
|
|
316
|
+
/**
|
|
317
|
+
* Breaks of the section shape, in document order, and empty outside the
|
|
318
|
+
* governed folder. Whether one gates depends on where the entry sits, which
|
|
319
|
+
* `shapeFindings` decides rather than the measure.
|
|
320
|
+
*/
|
|
321
|
+
readonly shape: readonly ShapeIssue[]
|
|
250
322
|
/**
|
|
251
323
|
* Whether the file declares itself a skeleton, which excludes it from the
|
|
252
324
|
* section check alone. Every other measure still reads it, since a stub is
|
|
@@ -483,6 +555,7 @@ const H2_TEXT = /^##\s+(.+?)\s*$/
|
|
|
483
555
|
function otherSections(
|
|
484
556
|
lines: readonly BodyLine[],
|
|
485
557
|
allowed: readonly string[],
|
|
558
|
+
retired: readonly string[],
|
|
486
559
|
): string[] {
|
|
487
560
|
if (allowed.length === 0) return []
|
|
488
561
|
|
|
@@ -492,12 +565,166 @@ function otherSections(
|
|
|
492
565
|
if (line.fenced) continue
|
|
493
566
|
|
|
494
567
|
const match = line.text.match(H2_TEXT)
|
|
495
|
-
if (match && !allowed.includes(match[1])
|
|
568
|
+
if (match && !allowed.includes(match[1]) && !retired.includes(match[1])) {
|
|
569
|
+
found.push(match[1])
|
|
570
|
+
}
|
|
496
571
|
}
|
|
497
572
|
|
|
498
573
|
return found
|
|
499
574
|
}
|
|
500
575
|
|
|
576
|
+
const HEADING_LEVEL = /^(#{1,6})\s+(.+?)\s*$/
|
|
577
|
+
const LIST_ITEM = /^\s*(?:[-*+]|\d+[.)])\s+/
|
|
578
|
+
const INDENTED = /^\s+\S/
|
|
579
|
+
const FOLDER_TITLE = /^`[^`]+`$/
|
|
580
|
+
|
|
581
|
+
interface FolderHeading {
|
|
582
|
+
readonly line: number
|
|
583
|
+
readonly text: string
|
|
584
|
+
bullets: number
|
|
585
|
+
paragraphs: number
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Finds where the entry breaks the section shape.
|
|
590
|
+
*
|
|
591
|
+
* Bullets are counted at any indent and across any paragraph, so a list split
|
|
592
|
+
* by a bold label or nested one level down still counts against the cap it
|
|
593
|
+
* would otherwise slip under. A line continuing a list item, lazily or
|
|
594
|
+
* indented after a blank, belongs to the item rather than opening a paragraph,
|
|
595
|
+
* which is what keeps a wrapped bullet from reading as prose.
|
|
596
|
+
*
|
|
597
|
+
* A cap reports once per section or folder heading however far past it the
|
|
598
|
+
* count runs, since the fix is one cut either way. `Other folders` is exempt
|
|
599
|
+
* from both folder caps, its one-line bullets being held by the bullet run
|
|
600
|
+
* limit `canon markdown audit` already gates. The development entry's `Scripts`
|
|
601
|
+
* H3 is exempt from the heading rule and both caps the same way, its table
|
|
602
|
+
* being a run-command surface rather than a folder.
|
|
603
|
+
*/
|
|
604
|
+
function shapeIssues(
|
|
605
|
+
rel: string,
|
|
606
|
+
lines: readonly BodyLine[],
|
|
607
|
+
retired: readonly string[],
|
|
608
|
+
): ShapeIssue[] {
|
|
609
|
+
const issues: ShapeIssue[] = []
|
|
610
|
+
const exempt = new Set([OTHER_FOLDERS])
|
|
611
|
+
if (isDevelopmentEntry(rel)) exempt.add(SCRIPTS_HEADING)
|
|
612
|
+
let section: string | undefined
|
|
613
|
+
let gotchas: { line: number; bullets: number } | undefined
|
|
614
|
+
let folder: FolderHeading | undefined
|
|
615
|
+
let inItem = false
|
|
616
|
+
let inParagraph = false
|
|
617
|
+
let afterBlank = false
|
|
618
|
+
|
|
619
|
+
const closeFolder = (): void => {
|
|
620
|
+
if (folder && !exempt.has(folder.text)) {
|
|
621
|
+
if (folder.paragraphs > LAYOUT_PARAGRAPH_CAP) {
|
|
622
|
+
issues.push({
|
|
623
|
+
line: folder.line,
|
|
624
|
+
kind: 'layout-paragraphs',
|
|
625
|
+
text: folder.text,
|
|
626
|
+
count: folder.paragraphs,
|
|
627
|
+
})
|
|
628
|
+
}
|
|
629
|
+
if (folder.bullets > LAYOUT_BULLET_CAP) {
|
|
630
|
+
issues.push({
|
|
631
|
+
line: folder.line,
|
|
632
|
+
kind: 'layout-bullets',
|
|
633
|
+
text: folder.text,
|
|
634
|
+
count: folder.bullets,
|
|
635
|
+
})
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
folder = undefined
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const closeGotchas = (): void => {
|
|
642
|
+
if (gotchas && gotchas.bullets > GOTCHA_CAP) {
|
|
643
|
+
issues.push({
|
|
644
|
+
line: gotchas.line,
|
|
645
|
+
kind: 'gotcha-count',
|
|
646
|
+
text: 'Gotchas',
|
|
647
|
+
count: gotchas.bullets,
|
|
648
|
+
})
|
|
649
|
+
}
|
|
650
|
+
gotchas = undefined
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
for (const line of lines) {
|
|
654
|
+
if (line.fenced) continue
|
|
655
|
+
|
|
656
|
+
const heading = line.text.match(HEADING_LEVEL)
|
|
657
|
+
if (heading) {
|
|
658
|
+
const level = heading[1].length
|
|
659
|
+
const text = heading[2]
|
|
660
|
+
inItem = false
|
|
661
|
+
inParagraph = false
|
|
662
|
+
afterBlank = false
|
|
663
|
+
|
|
664
|
+
if (level <= 2) {
|
|
665
|
+
closeFolder()
|
|
666
|
+
closeGotchas()
|
|
667
|
+
section = level === 2 ? text : undefined
|
|
668
|
+
if (level === 2 && retired.includes(text)) {
|
|
669
|
+
issues.push({ line: line.number, kind: 'retired-section', text })
|
|
670
|
+
}
|
|
671
|
+
if (section === 'Gotchas') gotchas = { line: line.number, bullets: 0 }
|
|
672
|
+
continue
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (section === 'Gotchas') {
|
|
676
|
+
issues.push({ line: line.number, kind: 'gotcha-heading', text })
|
|
677
|
+
} else if (section === 'Layout' && level === 3) {
|
|
678
|
+
closeFolder()
|
|
679
|
+
folder = { line: line.number, text, bullets: 0, paragraphs: 0 }
|
|
680
|
+
if (!FOLDER_TITLE.test(text) && !exempt.has(text)) {
|
|
681
|
+
issues.push({ line: line.number, kind: 'layout-heading', text })
|
|
682
|
+
}
|
|
683
|
+
} else if (section === 'Layout') {
|
|
684
|
+
issues.push({ line: line.number, kind: 'layout-depth', text })
|
|
685
|
+
}
|
|
686
|
+
continue
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (line.text.trim() === '') {
|
|
690
|
+
inParagraph = false
|
|
691
|
+
afterBlank = true
|
|
692
|
+
continue
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (LIST_ITEM.test(line.text)) {
|
|
696
|
+
inItem = true
|
|
697
|
+
inParagraph = false
|
|
698
|
+
afterBlank = false
|
|
699
|
+
if (gotchas) gotchas.bullets++
|
|
700
|
+
if (folder) folder.bullets++
|
|
701
|
+
continue
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const continues = inItem && (!afterBlank || INDENTED.test(line.text))
|
|
705
|
+
afterBlank = false
|
|
706
|
+
if (continues) continue
|
|
707
|
+
|
|
708
|
+
inItem = false
|
|
709
|
+
if (inParagraph) continue
|
|
710
|
+
inParagraph = true
|
|
711
|
+
|
|
712
|
+
if (gotchas) {
|
|
713
|
+
issues.push({
|
|
714
|
+
line: line.number,
|
|
715
|
+
kind: 'gotcha-paragraph',
|
|
716
|
+
text: line.text.trim(),
|
|
717
|
+
})
|
|
718
|
+
}
|
|
719
|
+
if (folder) folder.paragraphs++
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
closeFolder()
|
|
723
|
+
closeGotchas()
|
|
724
|
+
|
|
725
|
+
return issues.sort((a, b) => a.line - b.line)
|
|
726
|
+
}
|
|
727
|
+
|
|
501
728
|
/**
|
|
502
729
|
* Finds the references naming a sibling entry by bare filename.
|
|
503
730
|
*
|
|
@@ -557,6 +784,9 @@ export function measureEntry(
|
|
|
557
784
|
siblings: readonly string[] = [],
|
|
558
785
|
required: readonly string[] = governsContent ? REQUIRED_SECTIONS : [],
|
|
559
786
|
allowed: readonly string[] = [],
|
|
787
|
+
retired: readonly string[] = governsContent
|
|
788
|
+
? RETIRED_SECTIONS_BY_FOLDER.context
|
|
789
|
+
: [],
|
|
560
790
|
): EntryReport {
|
|
561
791
|
const lines = bodyLines(source)
|
|
562
792
|
|
|
@@ -567,7 +797,8 @@ export function measureEntry(
|
|
|
567
797
|
provenance: governsContent ? provenance(lines) : [],
|
|
568
798
|
bareReferences: bareReferences(lines, siblings),
|
|
569
799
|
sections: declaredSections(lines, required),
|
|
570
|
-
otherSections: otherSections(lines, allowed),
|
|
800
|
+
otherSections: otherSections(lines, allowed, retired),
|
|
801
|
+
shape: governsContent ? shapeIssues(rel, lines, retired) : [],
|
|
571
802
|
stub: isStubSeed(source),
|
|
572
803
|
governed: governsContent,
|
|
573
804
|
}
|
|
@@ -648,6 +879,7 @@ export async function measureFolders(
|
|
|
648
879
|
names.filter((name) => name !== self),
|
|
649
880
|
requiredSections(folder),
|
|
650
881
|
allowedSections(folder),
|
|
882
|
+
retiredSections(folder),
|
|
651
883
|
),
|
|
652
884
|
)
|
|
653
885
|
}
|
|
@@ -675,6 +907,13 @@ export function allowedSections(folder: AuditedFolder): readonly string[] {
|
|
|
675
907
|
: []
|
|
676
908
|
}
|
|
677
909
|
|
|
910
|
+
/** Names the H2 sections the folder's standard retired, empty where it retired none. */
|
|
911
|
+
export function retiredSections(folder: AuditedFolder): readonly string[] {
|
|
912
|
+
return Object.hasOwn(RETIRED_SECTIONS_BY_FOLDER, folder.name)
|
|
913
|
+
? RETIRED_SECTIONS_BY_FOLDER[folder.name]
|
|
914
|
+
: []
|
|
915
|
+
}
|
|
916
|
+
|
|
678
917
|
/**
|
|
679
918
|
* Reports whether a bare sibling name here is a reference by construction.
|
|
680
919
|
*
|
|
@@ -771,3 +1010,33 @@ export function extraSections(
|
|
|
771
1010
|
.filter((entry) => !entry.stub && entry.otherSections.length > 0)
|
|
772
1011
|
.map((entry) => ({ rel: entry.rel, headings: entry.otherSections }))
|
|
773
1012
|
}
|
|
1013
|
+
|
|
1014
|
+
export interface ShapeFinding {
|
|
1015
|
+
/** Repo-relative path of the entry breaking the shape. */
|
|
1016
|
+
readonly rel: string
|
|
1017
|
+
/** Whether the entry sits where the shape gates under `--gate`. */
|
|
1018
|
+
readonly gated: boolean
|
|
1019
|
+
/** Every break in the entry, never empty. */
|
|
1020
|
+
readonly issues: readonly ShapeIssue[]
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/**
|
|
1024
|
+
* Names each entry breaking the section shape, and whether it gates.
|
|
1025
|
+
*
|
|
1026
|
+
* Where it gates is the caller's predicate rather than a list read here, since
|
|
1027
|
+
* the folders rebuilt to the shape are named by path from the repository root
|
|
1028
|
+
* and `measureFolders` holds only paths from the audit root. A stub seed is
|
|
1029
|
+
* dropped for the reason `extraSections` drops one.
|
|
1030
|
+
*/
|
|
1031
|
+
export function shapeFindings(
|
|
1032
|
+
entries: readonly EntryReport[],
|
|
1033
|
+
isGated: (rel: string) => boolean,
|
|
1034
|
+
): ShapeFinding[] {
|
|
1035
|
+
return entries
|
|
1036
|
+
.filter((entry) => !entry.stub && entry.shape.length > 0)
|
|
1037
|
+
.map((entry) => ({
|
|
1038
|
+
rel: entry.rel,
|
|
1039
|
+
gated: isGated(entry.rel),
|
|
1040
|
+
issues: entry.shape,
|
|
1041
|
+
}))
|
|
1042
|
+
}
|
|
@@ -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/folders.ts
CHANGED
|
@@ -56,7 +56,7 @@ export interface AuditedFolder {
|
|
|
56
56
|
* The requested folder name this was resolved under, which is what says
|
|
57
57
|
* which standard governs the entries. A nested split folder carries the name
|
|
58
58
|
* of the folder it sits beneath rather than its own, so
|
|
59
|
-
* `canon/context
|
|
59
|
+
* `canon/context/<domain>` is governed as `context`.
|
|
60
60
|
*/
|
|
61
61
|
readonly name: string
|
|
62
62
|
/**
|
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,30 @@
|
|
|
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/development/',
|
|
19
|
+
'canon/context/features/',
|
|
20
|
+
'canon/context/surfaces/',
|
|
21
|
+
'canon/context/workflow/',
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
/** Whether a repository-relative entry path sits under a gated prefix. */
|
|
25
|
+
export function isShapeGated(
|
|
26
|
+
rel: string,
|
|
27
|
+
prefixes: readonly string[] = SHAPE_GATED_FOLDERS,
|
|
28
|
+
): boolean {
|
|
29
|
+
return prefixes.some((prefix) => rel.startsWith(prefix))
|
|
30
|
+
}
|
package/src/counts/scan.ts
CHANGED
|
@@ -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. A context entry asserting `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.
|
package/src/design/components.ts
CHANGED
|
@@ -250,7 +250,7 @@ const TEACH_CHROME: Component = {
|
|
|
250
250
|
'navigation shared by every teach page. Recovered from two gitignored',
|
|
251
251
|
'course.css files that predate a regression that dropped this layer from the',
|
|
252
252
|
"generator, and rewritten onto this module's tokens rather than the",
|
|
253
|
-
'incompatible palette they carried. See canon/context/features/teach
|
|
253
|
+
'incompatible palette they carried. See canon/context/features/teach.md',
|
|
254
254
|
'for the shape.',
|
|
255
255
|
].join('\n '),
|
|
256
256
|
reads: [
|
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/features/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/features/design.md`.
|
|
63
63
|
*/
|
|
64
64
|
export interface DesignTokens {
|
|
65
65
|
readonly personality: string
|
package/src/gate/boundaries.ts
CHANGED
|
@@ -211,7 +211,7 @@ function collectCommands(value: unknown, into: string[] = []): string[] {
|
|
|
211
211
|
/**
|
|
212
212
|
* A hook, a workflow, or a husky script reaching one side of the seed or config
|
|
213
213
|
* boundary and not the other is a capability withheld with no recorded reason,
|
|
214
|
-
* per the criterion in `canon/context/tooling
|
|
214
|
+
* per the criterion in `canon/context/surfaces/tooling.md`.
|
|
215
215
|
*
|
|
216
216
|
* Presence on either side clears a name, and a `canon-no-seed:` comment in the
|
|
217
217
|
* file is the recorded reason. The same marker on a destination clears a
|
|
@@ -434,6 +434,9 @@ function grepLines(
|
|
|
434
434
|
* seed itself installs, so every other spelling reports. No seed root, or roots
|
|
435
435
|
* carrying no markdown, is unmeasured rather than a pass, which agrees with
|
|
436
436
|
* `seedStandards` on the no-root case.
|
|
437
|
+
*
|
|
438
|
+
* The token matches as a substring, so `canonical` reports as `canon` does. The
|
|
439
|
+
* walk covers the seed roots alone, so a shipped rule citing the CLI passes.
|
|
437
440
|
*/
|
|
438
441
|
export const seedIndependence: Measure = async (ctx) => {
|
|
439
442
|
if (!isDirectory(join(ctx.root, 'tooling'))) {
|