@erclx/canon 5.34.0 → 5.36.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 +15 -17
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/api-design/REQUIREMENT.md +1 -1
- package/claude/skills/auto-ship/references/draft-mark.md +1 -1
- package/claude/skills/canon-cli/REQUIREMENT.md +2 -2
- package/claude/skills/canon-cli/SKILL.md +12 -12
- package/claude/skills/canvas-board/REQUIREMENT.md +1 -1
- package/claude/skills/canvas-board/SKILL.md +2 -2
- package/claude/skills/canvas-board/references/design.md +53 -36
- package/claude/skills/codebase-layout/REQUIREMENT.md +1 -1
- package/claude/skills/codebase-layout/SKILL.md +2 -2
- package/claude/skills/create-rule/REQUIREMENT.md +6 -8
- package/claude/skills/create-rule/SKILL.md +8 -8
- package/claude/skills/create-rule/references/rule.md +11 -23
- package/claude/skills/create-skill/SKILL.md +2 -1
- package/claude/skills/create-skill/references/skill-paths.md +12 -15
- package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
- package/claude/skills/create-skill/references/skill.md +9 -3
- package/claude/skills/design-taste/REQUIREMENT.md +6 -6
- package/claude/skills/design-taste/SKILL.md +2 -2
- package/claude/skills/design-taste/references/craft.md +1 -1
- package/claude/skills/design-taste/references/preflight.md +2 -2
- package/claude/skills/design-taste/references/tells.md +1 -1
- package/claude/skills/draft-and-pick/SKILL.md +1 -1
- package/claude/skills/draft-identity/SKILL.md +3 -2
- package/claude/skills/git-commit/references/versioning.md +13 -13
- package/claude/skills/git-pr/SKILL.md +1 -1
- package/claude/skills/git-pr/references/publish.md +1 -1
- package/claude/skills/markdown-craft/references/markdown.md +3 -2
- package/claude/skills/plan-feature/SKILL.md +1 -1
- package/claude/skills/review-address/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +2 -2
- package/claude/skills/review-craft/references/ui.md +1 -1
- package/claude/skills/review-pr/SKILL.md +3 -3
- package/claude/skills/review-pr/references/post.md +1 -1
- package/claude/skills/review-ui/REQUIREMENT.md +1 -1
- package/claude/skills/review-ui/SKILL.md +1 -1
- package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
- package/claude/skills/role-orchestrator/SKILL.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
- package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
- package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
- package/claude/skills/role-orchestrator/references/orchestrator-launch.md +2 -2
- package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
- package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
- package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
- package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
- package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
- package/claude/skills/role-reviewer/SKILL.md +17 -10
- package/claude/skills/role-worker/REQUIREMENT.md +1 -1
- package/claude/skills/role-worker/SKILL.md +6 -6
- package/claude/skills/session-compact/references/handoff-note.md +1 -1
- package/claude/skills/target-setup/SKILL.md +2 -2
- package/claude/skills/target-setup/references/detect.md +3 -3
- package/claude/skills/test-craft/SKILL.md +2 -2
- package/claude/skills/ux-audit/REQUIREMENT.md +1 -1
- package/claude/skills/ux-audit/SKILL.md +2 -2
- package/claude/skills/ux-measure/REQUIREMENT.md +1 -1
- package/claude/skills/write-human/references/machine-tells.md +12 -0
- package/docs/agents/audits.md +1 -1
- package/docs/agents/commands.md +14 -13
- package/docs/agents/comments.md +1 -1
- package/docs/agents/counts.md +5 -6
- package/docs/agents/drift-surfaces.md +15 -19
- package/docs/agents/gov-detect.md +1 -1
- package/docs/agents/index.md +2 -3
- package/docs/agents/install-and-sync.md +10 -34
- package/docs/agents/markdown-audit.md +22 -12
- package/docs/agents/migrate.md +1 -1
- package/docs/agents/output-shape.md +1 -1
- package/docs/agents/overview.md +2 -2
- package/docs/agents/records-stale.md +1 -1
- package/docs/agents/records-validate.md +11 -23
- package/docs/agents/records.md +1 -1
- package/docs/agents/routing.md +1 -1
- package/docs/agents/rule-citations.md +9 -17
- package/docs/agents/scripting.md +2 -20
- package/docs/agents/state-scoped-risk.md +1 -1
- package/docs/agents/tasks-validate.md +1 -1
- package/docs/agents/teach.md +1 -1
- package/docs/target/index.md +1 -0
- package/docs/target/migrations.md +4 -0
- package/docs/target/projects.md +10 -10
- package/docs/target/scenarios.md +5 -6
- package/docs/target/standards-moved.md +32 -0
- package/docs/target/sync.md +5 -5
- package/docs/workflow/ai-workflow.md +1 -1
- package/docs/workflow/operating-model.md +16 -18
- package/docs/workflow/skill-map.md +1 -1
- package/docs/workflow/visual-design-workflow.md +3 -3
- package/governance/detect.toml +32 -36
- package/governance/renames.toml +44 -1
- package/governance/rules/lib/{305-e2e-reliability.md → e2e-reliability.md} +1 -1
- package/governance/rules/lib/{306-test-scope.md → test-scope.md} +2 -2
- package/governance/rules/ui/{410-a11y.md → a11y.md} +1 -1
- package/governance/rules/ui/{440-surface-capture.md → surface-capture.md} +3 -3
- package/governance/stacks/base.toml +15 -8
- package/governance/stacks/go.toml +2 -0
- package/governance/stacks/node.toml +1 -1
- package/governance/stacks/php.toml +2 -0
- package/governance/stacks/python.toml +1 -1
- package/package.json +1 -4
- package/scripts/core/install-check.sh +4 -10
- package/src/audits/catalog.ts +0 -9
- package/src/capture/frame-values.ts +3 -6
- package/src/claude/routing.ts +1 -1
- package/src/claude/skills-reach.ts +4 -6
- package/src/cli/help-all.ts +96 -0
- package/src/cli/help.ts +29 -29
- package/src/cli.ts +8 -3
- package/src/commands/claude.ts +29 -0
- package/src/commands/design.ts +22 -1
- package/src/commands/gov.ts +29 -11
- package/src/commands/init.ts +10 -1
- package/src/commands/labels.ts +4 -44
- package/src/commands/markdown.ts +96 -3
- package/src/commands/migrate.ts +1 -1
- package/src/commands/records.ts +5 -21
- package/src/commands/sync.ts +11 -0
- package/src/commands/tooling.ts +10 -4
- package/src/counts/catalogs.ts +0 -6
- package/src/design/budget.ts +14 -10
- package/src/design/document.ts +5 -5
- package/src/design/parse.ts +21 -4
- package/src/design/render.ts +3 -3
- package/src/gate/boundaries.ts +2 -2
- package/src/gate/measures.ts +7 -29
- package/src/gate/sequencer.ts +51 -2
- package/src/gate/stages.ts +0 -18
- package/src/gov/citations.ts +16 -71
- package/src/gov/stacks.ts +3 -3
- package/src/inventory/subjects.ts +1 -1
- package/src/markdown/bans.ts +1 -1
- package/src/markdown/ceiling.ts +6 -1
- package/src/markdown/colons.ts +86 -0
- package/src/markdown/gate.ts +15 -0
- package/src/markdown/heading-case.ts +74 -0
- package/src/migrate/scratch-evidence.ts +2 -2
- package/src/records/validate.ts +6 -194
- package/src/sandbox/provision.ts +0 -5
- package/src/secrets/shipped.ts +1 -2
- package/src/serve/static.ts +1 -2
- package/src/shipped/references.ts +42 -93
- package/src/sync/check.ts +18 -14
- package/src/sync/copy.ts +4 -5
- package/src/tooling/inject.ts +6 -37
- package/src/tooling/read.ts +2 -3
- package/src/tooling/scan.ts +4 -17
- package/src/tooling/subfolder.ts +2 -66
- package/tooling/astro/manifest.toml +0 -1
- package/tooling/astro/reference.md +0 -8
- package/tooling/base/configs/scripts/verify.json +1 -1
- package/tooling/base/configs/scripts/verify.ts +22 -17
- package/tooling/base/manifest.toml +7 -7
- package/tooling/base/reference.md +19 -32
- package/tooling/base/seeds/.github/workflows/verify.yml +0 -9
- package/tooling/base/seeds/.lintstagedrc +2 -4
- package/tooling/base/seeds/canon/context/ci.md +4 -6
- package/tooling/base/seeds/canon/context/development.md +2 -2
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
- package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +1 -1
- package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +27 -2
- package/tooling/claude/seeds/CLAUDE.md +1 -1
- package/tooling/claude/seeds/canon/DESIGN.md +24 -37
- package/tooling/go/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +2 -2
- package/tooling/shell/configs/.github/workflows/shell.yml +30 -0
- package/tooling/shell/manifest.toml +12 -0
- package/tooling/shell/reference.md +46 -0
- package/tooling/vite-react/manifest.toml +0 -1
- package/tooling/vite-react/reference.md +0 -8
- package/tooling/web/configs/.github/workflows/readme-screenshot.yml +3 -5
- package/tooling/web/configs/.github/workflows/verify.yml +0 -9
- package/tooling/web/manifest.toml +1 -2
- package/tooling/web/reference.md +4 -6
- package/docs/agents/standards-audit.md +0 -40
- package/governance/rules/tooling/765-spelling.md +0 -19
- package/governance/stacks/astro.toml +0 -2
- package/governance/stacks/nextjs.toml +0 -2
- package/governance/stacks/node-server.toml +0 -2
- package/governance/stacks/python-fastapi.toml +0 -2
- package/governance/stacks/react.toml +0 -2
- package/src/commands/standards.ts +0 -278
- package/src/labels/spelling.ts +0 -120
- package/src/standards/audit.ts +0 -146
- package/src/standards/list.ts +0 -33
- package/src/standards/read.ts +0 -177
- package/src/standards/scope.ts +0 -56
- package/standards/index.md +0 -10
- package/tooling/base/configs/.vscode/extensions.json +0 -9
- package/tooling/base/configs/.vscode/settings.json +0 -3
- package/tooling/base/configs/scripts/verify.sh +0 -2
- package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
- package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
- package/tooling/base/seeds/cspell.json +0 -20
- package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
- package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
- package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
- package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
- package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
- package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
- package/tooling/web/configs/.vscode/extensions.json +0 -13
- package/tooling/web/configs/.vscode/settings.json +0 -10
- package/tooling/web/seeds/.cspell/tech-stack.txt +0 -21
- /package/governance/rules/canon/{606-git.md → git.md} +0 -0
- /package/governance/rules/canon/{611-indexes.md → indexes.md} +0 -0
- /package/governance/rules/canon/{604-scratch.md → scratch.md} +0 -0
- /package/governance/rules/canon/{602-tasks.md → task-scope.md} +0 -0
- /package/governance/rules/canon/{605-worktrees.md → worktrees.md} +0 -0
- /package/governance/rules/claude/{566-output.md → output.md} +0 -0
- /package/governance/rules/code/{000-code.md → routing.md} +0 -0
- /package/governance/rules/code/{010-testing.md → testing.md} +0 -0
- /package/governance/rules/framework/{210-astro.md → astro.md} +0 -0
- /package/governance/rules/framework/{220-fastapi.md → fastapi.md} +0 -0
- /package/governance/rules/framework/{230-nextjs.md → nextjs.md} +0 -0
- /package/governance/rules/framework/{200-react.md → react.md} +0 -0
- /package/governance/rules/framework/{260-shadcn.md → shadcn.md} +0 -0
- /package/governance/rules/framework/{250-tailwind.md → tailwind.md} +0 -0
- /package/governance/rules/lang/{130-go.md → go.md} +0 -0
- /package/governance/rules/lang/{140-php.md → php.md} +0 -0
- /package/governance/rules/lang/{110-python.md → python.md} +0 -0
- /package/governance/rules/lang/{100-typescript.md → typescript.md} +0 -0
- /package/governance/rules/lib/{370-database.md → database.md} +0 -0
- /package/governance/rules/lib/{340-pydantic.md → pydantic.md} +0 -0
- /package/governance/rules/lib/{360-security-server.md → security-server.md} +0 -0
- /package/governance/rules/lib/{350-security-web.md → security-web.md} +0 -0
- /package/governance/rules/lib/{320-tanstack-query.md → tanstack-query.md} +0 -0
- /package/governance/rules/lib/{335-testing-go.md → testing-go.md} +0 -0
- /package/governance/rules/lib/{336-testing-php.md → testing-php.md} +0 -0
- /package/governance/rules/lib/{330-testing-py.md → testing-py.md} +0 -0
- /package/governance/rules/lib/{300-testing-ts.md → testing-ts.md} +0 -0
- /package/governance/rules/lib/{310-zod.md → zod.md} +0 -0
- /package/governance/rules/tooling/{700-ci-workflow.md → ci-workflow.md} +0 -0
- /package/governance/rules/tooling/{790-code-comments.md → code-comments.md} +0 -0
- /package/governance/rules/tooling/{780-config-comments.md → config-comments.md} +0 -0
- /package/governance/rules/tooling/{775-dependencies.md → dependencies.md} +0 -0
- /package/governance/rules/ui/{480-canvas.md → canvas.md} +0 -0
- /package/governance/rules/ui/{400-ui.md → copy.md} +0 -0
- /package/governance/rules/ui/{460-design-taste.md → design-taste.md} +0 -0
- /package/governance/rules/ui/{420-forms.md → forms.md} +0 -0
- /package/governance/rules/ui/{465-interface-casing.md → interface-casing.md} +0 -0
- /package/governance/rules/ui/{450-link-behavior.md → link-behavior.md} +0 -0
- /package/governance/rules/ui/{470-motion.md → motion.md} +0 -0
- /package/governance/rules/ui/{430-ux-completeness.md → ux-completeness.md} +0 -0
- /package/tooling/{base → shell}/configs/.editorconfig +0 -0
- /package/tooling/{base → shell}/configs/.shellcheckrc +0 -0
package/src/commands/markdown.ts
CHANGED
|
@@ -3,9 +3,14 @@ import { readFile } from 'node:fs/promises'
|
|
|
3
3
|
import { dirname, relative, resolve } from 'node:path'
|
|
4
4
|
import type { Command } from 'commander'
|
|
5
5
|
import { BAN_SETS, emptyBanSets } from '@/markdown/bans'
|
|
6
|
-
import { lengthExemption } from '@/markdown/ceiling'
|
|
6
|
+
import { isChangelog, lengthExemption } from '@/markdown/ceiling'
|
|
7
|
+
import { type ColonFinding, findColons } from '@/markdown/colons'
|
|
7
8
|
import { type MarkdownAuditRefusal, resolveMarkdown } from '@/markdown/files'
|
|
8
9
|
import { isGating } from '@/markdown/gate'
|
|
10
|
+
import {
|
|
11
|
+
findHeadingCase,
|
|
12
|
+
type HeadingCaseFinding,
|
|
13
|
+
} from '@/markdown/heading-case'
|
|
9
14
|
import { decodePath, findBrokenLinks, type LinkFinding } from '@/markdown/links'
|
|
10
15
|
import {
|
|
11
16
|
type BanFinding,
|
|
@@ -52,6 +57,8 @@ interface FileReport {
|
|
|
52
57
|
readonly rel: string
|
|
53
58
|
readonly bans: readonly BanFinding[]
|
|
54
59
|
readonly links: readonly LinkFinding[]
|
|
60
|
+
readonly colons: readonly ColonFinding[]
|
|
61
|
+
readonly headingCase: readonly HeadingCaseFinding[]
|
|
55
62
|
readonly structure: StructureReport
|
|
56
63
|
readonly renderedLines: number
|
|
57
64
|
/** The stated reason, or null where the document is not exempt. */
|
|
@@ -90,6 +97,10 @@ export function register(program: Command): void {
|
|
|
90
97
|
'Bullet, paragraph, and depth weight are judgments a reader settles,',
|
|
91
98
|
'so all three report and none of them fails a run.',
|
|
92
99
|
'',
|
|
100
|
+
'A mid-sentence colon and a heading with a capital after its first word',
|
|
101
|
+
'report the same way. A proper noun is the one case the second cannot',
|
|
102
|
+
'tell from title case, and the first is sometimes right.',
|
|
103
|
+
'',
|
|
93
104
|
'Length reports a whole document past the ceiling and never exits 2.',
|
|
94
105
|
'The Document ceiling gate stage owns that verdict, since this audit also',
|
|
95
106
|
'runs on a record kept out of version control, where a long plan is not',
|
|
@@ -100,7 +111,7 @@ export function register(program: Command): void {
|
|
|
100
111
|
'it correctly and a flat paragraph there is not a defect.',
|
|
101
112
|
'',
|
|
102
113
|
'Rewrite the sentence carrying a hit rather than swapping the token for',
|
|
103
|
-
'a near-synonym. A code span clears the report and is the answer only',
|
|
114
|
+
'a near-synonym or a colon or a comma. A code span clears the report and is the answer only',
|
|
104
115
|
'where the token is genuinely an identifier under discussion, which is',
|
|
105
116
|
'what markdown.md reserves the span for.',
|
|
106
117
|
'',
|
|
@@ -160,9 +171,13 @@ async function runAudit(
|
|
|
160
171
|
const abs = resolve(root, rel)
|
|
161
172
|
const source = await readFile(abs, 'utf8')
|
|
162
173
|
const lines = bodyLines(source)
|
|
174
|
+
// The release tool writes a changelog, so neither advisory reads it.
|
|
175
|
+
const advisory = !isChangelog(rel)
|
|
163
176
|
return {
|
|
164
177
|
rel,
|
|
165
178
|
bans: scanBans(lines, bans),
|
|
179
|
+
colons: advisory ? findColons(lines) : [],
|
|
180
|
+
headingCase: advisory ? findHeadingCase(source) : [],
|
|
166
181
|
links: findBrokenLinks(lines, (path) =>
|
|
167
182
|
existsSync(resolve(dirname(abs), path)),
|
|
168
183
|
),
|
|
@@ -177,6 +192,8 @@ async function runAudit(
|
|
|
177
192
|
reportScope(scope.files, scope.unmatched)
|
|
178
193
|
reportBans(reports, bans, empty)
|
|
179
194
|
reportLinks(reports, root)
|
|
195
|
+
reportColons(reports)
|
|
196
|
+
reportHeadingCase(reports)
|
|
180
197
|
reportBullets(reports, checkpoints)
|
|
181
198
|
reportParagraphs(reports, checkpoints)
|
|
182
199
|
reportCadence(reports, checkpoints)
|
|
@@ -210,6 +227,8 @@ async function runAudit(
|
|
|
210
227
|
path: report.rel,
|
|
211
228
|
bans: report.bans,
|
|
212
229
|
links: report.links,
|
|
230
|
+
colons: report.colons,
|
|
231
|
+
headingCase: report.headingCase,
|
|
213
232
|
longestRun: report.structure.longestRun,
|
|
214
233
|
longestRunLine: report.structure.longestRunLine,
|
|
215
234
|
heavyBullets: report.structure.heavyBullets,
|
|
@@ -230,6 +249,8 @@ async function runAudit(
|
|
|
230
249
|
bans: reports.flatMap((report) => report.bans),
|
|
231
250
|
links: reports.flatMap((report) => report.links),
|
|
232
251
|
structure: reports.map((report) => report.structure),
|
|
252
|
+
colons: reports.flatMap((report) => report.colons),
|
|
253
|
+
headingCase: reports.flatMap((report) => report.headingCase),
|
|
233
254
|
})
|
|
234
255
|
|
|
235
256
|
return gating ? EXIT_GATE : 0
|
|
@@ -310,7 +331,7 @@ function reportBans(
|
|
|
310
331
|
logWarn(`${plural(total, 'hit')} across ${plural(carrying.length, 'file')}`)
|
|
311
332
|
logWarn('This fails the run. Every other check below reports.')
|
|
312
333
|
logInfo(
|
|
313
|
-
|
|
334
|
+
"Rewrite the sentence rather than swapping the token for a near-synonym, or a colon or a comma in the dash's place. That is the same sentence.",
|
|
314
335
|
)
|
|
315
336
|
logInfo(
|
|
316
337
|
'A code span clears the report and is the answer only where the token is genuinely an identifier under discussion, which is what markdown.md reserves the span for.',
|
|
@@ -383,6 +404,78 @@ function reportLinks(reports: readonly FileReport[], root: string): void {
|
|
|
383
404
|
)
|
|
384
405
|
}
|
|
385
406
|
|
|
407
|
+
function reportColons(reports: readonly FileReport[]): void {
|
|
408
|
+
logStep('Colons')
|
|
409
|
+
logInfo(
|
|
410
|
+
'A colon followed by more text in a prose line, outside a label, a code span, and a link.',
|
|
411
|
+
)
|
|
412
|
+
logInfo(
|
|
413
|
+
'A heading, a table row, a fenced block, and a line-final colon are excluded. This never fails a run.',
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
const carrying = reports
|
|
417
|
+
.filter((report) => report.colons.length > 0)
|
|
418
|
+
.sort((a, b) => b.colons.length - a.colons.length)
|
|
419
|
+
|
|
420
|
+
if (carrying.length === 0) {
|
|
421
|
+
logInfo('No colon joining two clauses.')
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const total = carrying.reduce((sum, report) => sum + report.colons.length, 0)
|
|
426
|
+
logWarn(`${plural(total, 'colon')} across ${plural(carrying.length, 'file')}`)
|
|
427
|
+
logInfo(
|
|
428
|
+
'Split the sentence or rewrite it. A colon belongs after a label or at the end of a line introducing a list.',
|
|
429
|
+
)
|
|
430
|
+
pipeOutput(
|
|
431
|
+
carrying
|
|
432
|
+
.map(
|
|
433
|
+
(report) =>
|
|
434
|
+
`${report.rel} ${plural(report.colons.length, 'colon')}\n${report.colons
|
|
435
|
+
.map((found) => ` :${found.line}:${found.column + 1}`)
|
|
436
|
+
.join('\n')}`,
|
|
437
|
+
)
|
|
438
|
+
.join('\n'),
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function reportHeadingCase(reports: readonly FileReport[]): void {
|
|
443
|
+
logStep('Heading case')
|
|
444
|
+
logInfo(
|
|
445
|
+
'A heading or frontmatter title with a capitalized word after its first, outside code spans and all-caps tokens.',
|
|
446
|
+
)
|
|
447
|
+
logInfo(
|
|
448
|
+
'A proper noun reports too and is left as it is. This never fails a run.',
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
const carrying = reports
|
|
452
|
+
.filter((report) => report.headingCase.length > 0)
|
|
453
|
+
.sort((a, b) => b.headingCase.length - a.headingCase.length)
|
|
454
|
+
|
|
455
|
+
if (carrying.length === 0) {
|
|
456
|
+
logInfo('No heading carrying a capital after its first word.')
|
|
457
|
+
return
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const total = carrying.reduce(
|
|
461
|
+
(sum, report) => sum + report.headingCase.length,
|
|
462
|
+
0,
|
|
463
|
+
)
|
|
464
|
+
logWarn(
|
|
465
|
+
`${plural(total, 'heading')} across ${plural(carrying.length, 'file')}`,
|
|
466
|
+
)
|
|
467
|
+
pipeOutput(
|
|
468
|
+
carrying
|
|
469
|
+
.map(
|
|
470
|
+
(report) =>
|
|
471
|
+
`${report.rel} ${plural(report.headingCase.length, 'heading')}\n${report.headingCase
|
|
472
|
+
.map((found) => ` :${found.line} ${found.text}`)
|
|
473
|
+
.join('\n')}`,
|
|
474
|
+
)
|
|
475
|
+
.join('\n'),
|
|
476
|
+
)
|
|
477
|
+
}
|
|
478
|
+
|
|
386
479
|
function reportBullets(
|
|
387
480
|
reports: readonly FileReport[],
|
|
388
481
|
checkpoints: Checkpoints,
|
package/src/commands/migrate.ts
CHANGED
|
@@ -1124,7 +1124,7 @@ export function register(program: Command): void {
|
|
|
1124
1124
|
'repoints every citation that names one, live or archived.',
|
|
1125
1125
|
'',
|
|
1126
1126
|
'A promoted folder is one a durable record cites and no source file',
|
|
1127
|
-
'under claude/, scripts/, src/,
|
|
1127
|
+
'under claude/, scripts/, src/, or governance/ names by',
|
|
1128
1128
|
'path. A folder a script still writes into stays in scratch, since',
|
|
1129
1129
|
'moving it needs a code change first.',
|
|
1130
1130
|
'',
|
package/src/commands/records.ts
CHANGED
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
type Finding,
|
|
31
31
|
isRecordKind,
|
|
32
|
-
isSharedScratch,
|
|
33
32
|
RECORD_KINDS,
|
|
34
33
|
type RecordKind,
|
|
35
34
|
type ValidateOutcome,
|
|
@@ -82,7 +81,7 @@ export function register(program: Command): void {
|
|
|
82
81
|
const records = program
|
|
83
82
|
.command('records')
|
|
84
83
|
.description(
|
|
85
|
-
'Check a governed corpus against its standard, and back up the session records under .
|
|
84
|
+
'Check a governed corpus against its standard, and back up the session records under .canon/',
|
|
86
85
|
)
|
|
87
86
|
.helpOption('-h, --help', 'Show this help message')
|
|
88
87
|
|
|
@@ -92,10 +91,7 @@ export function register(program: Command): void {
|
|
|
92
91
|
.argument('<kind>', `Record folder: ${RECORD_KINDS.join(', ')}`)
|
|
93
92
|
.helpOption('-h, --help', 'Show this help message')
|
|
94
93
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
95
|
-
.option(
|
|
96
|
-
'--root <path>',
|
|
97
|
-
'Project root, defaulting to the main worktree except on standards',
|
|
98
|
-
)
|
|
94
|
+
.option('--root <path>', 'Project root, defaulting to the main worktree')
|
|
99
95
|
.addHelpText(
|
|
100
96
|
'after',
|
|
101
97
|
[
|
|
@@ -104,8 +100,6 @@ export function register(program: Command): void {
|
|
|
104
100
|
' plans filename, required sections, and the suggested-and-answer contract',
|
|
105
101
|
' groundwork README and current-state files, numbering, dating, and a half-closed track',
|
|
106
102
|
' intake overview file, numbering, dating, and the four bullets every item carries',
|
|
107
|
-
' standards frontmatter, the scope section and its handoff list, and a filename',
|
|
108
|
-
' derived from the governed path',
|
|
109
103
|
'',
|
|
110
104
|
'Exit codes:',
|
|
111
105
|
' 0 every check passed',
|
|
@@ -113,12 +107,11 @@ export function register(program: Command): void {
|
|
|
113
107
|
' 2 at least one record carries a finding',
|
|
114
108
|
'',
|
|
115
109
|
'It reports and never writes, so a session fixes what the report names. A',
|
|
116
|
-
'session record has no history to undo a wrong repair from
|
|
117
|
-
'is installed and cited everywhere, so a rename is larger than a file move.',
|
|
110
|
+
'session record has no history to undo a wrong repair from.',
|
|
118
111
|
'',
|
|
119
112
|
'Examples:',
|
|
120
113
|
' canon records validate plans',
|
|
121
|
-
' canon records validate
|
|
114
|
+
' canon records validate teach',
|
|
122
115
|
' canon records validate intake --json',
|
|
123
116
|
'',
|
|
124
117
|
].join('\n'),
|
|
@@ -718,15 +711,6 @@ function reportRefusal(
|
|
|
718
711
|
return 1
|
|
719
712
|
}
|
|
720
713
|
|
|
721
|
-
/**
|
|
722
|
-
* A shared-scratch kind reads the main worktree root, so a linked worktree
|
|
723
|
-
* validates the records every other session reads. A tracked corpus reads the
|
|
724
|
-
* checkout the caller stands in, which is the copy that session has edited.
|
|
725
|
-
*/
|
|
726
|
-
function defaultRoot(kind: RecordKind): Promise<string> {
|
|
727
|
-
return isSharedScratch(kind) ? mainWorktreeRoot() : currentWorktreeRoot()
|
|
728
|
-
}
|
|
729
|
-
|
|
730
714
|
async function runValidate(
|
|
731
715
|
kind: string,
|
|
732
716
|
opts: ValidateCommandOptions,
|
|
@@ -745,7 +729,7 @@ async function runValidate(
|
|
|
745
729
|
)
|
|
746
730
|
}
|
|
747
731
|
|
|
748
|
-
const root = opts.root ?? (await
|
|
732
|
+
const root = opts.root ?? (await mainWorktreeRoot())
|
|
749
733
|
|
|
750
734
|
return report(await validateWithStacks(root, kind), emitJson, root)
|
|
751
735
|
}
|
package/src/commands/sync.ts
CHANGED
|
@@ -57,6 +57,17 @@ export function register(program: Command): void {
|
|
|
57
57
|
.addHelpText(
|
|
58
58
|
'after',
|
|
59
59
|
[
|
|
60
|
+
'',
|
|
61
|
+
'Runs `canon gov sync`, `canon design sync`, and `canon claude sync`',
|
|
62
|
+
'on each domain installed, so each carries its own replace behavior:',
|
|
63
|
+
'governance replaces an edited rule and deletes a retired one, design',
|
|
64
|
+
'replaces its shipped files, and claude writes .gitignore alone.',
|
|
65
|
+
'Tooling is not synced. Refuses a dirty working tree.',
|
|
66
|
+
'',
|
|
67
|
+
'Exit codes:',
|
|
68
|
+
' 0 synced, no domain installed, or --check without --exit-code',
|
|
69
|
+
' 1 the target or a dirty tree was refused, opening the pull request',
|
|
70
|
+
' failed, or --check --exit-code found drift',
|
|
60
71
|
'',
|
|
61
72
|
'Examples:',
|
|
62
73
|
' canon sync',
|
package/src/commands/tooling.ts
CHANGED
|
@@ -99,14 +99,21 @@ export function register(program: Command): void {
|
|
|
99
99
|
' canon tooling sync vite-react web --skip base',
|
|
100
100
|
'',
|
|
101
101
|
'A target below its git root syncs as a subfolder. It withholds',
|
|
102
|
-
'.github/, which GitHub reads only at the root
|
|
103
|
-
'cspell.json registering the stack word lists.',
|
|
102
|
+
'.github/, which GitHub reads only at the root.',
|
|
104
103
|
'',
|
|
105
104
|
'To gate CI on tooling drift, run headlessly with neither flag. That',
|
|
106
105
|
'exits 1 when a file would be replaced and 0 when none would, which is',
|
|
107
106
|
'what `canon sync --check --exit-code` spells with a flag. Pass --check',
|
|
108
107
|
'to report the same list and always exit 0.',
|
|
109
108
|
'',
|
|
109
|
+
'--write replaces every golden config that differs from the stack.',
|
|
110
|
+
'Seeds are copy-once, so a seed the target already holds is left alone.',
|
|
111
|
+
'',
|
|
112
|
+
'Exit codes:',
|
|
113
|
+
' 0 synced, up to date, cancelled, or run with --check',
|
|
114
|
+
' 1 refused, or run headlessly with neither flag while a file would',
|
|
115
|
+
' be replaced',
|
|
116
|
+
'',
|
|
110
117
|
].join('\n'),
|
|
111
118
|
)
|
|
112
119
|
.action(
|
|
@@ -276,8 +283,7 @@ function runList(opts: ListOptions): number {
|
|
|
276
283
|
|
|
277
284
|
/**
|
|
278
285
|
* Writes the reference to stdout and every frame line to stderr, so a caller
|
|
279
|
-
* capturing the output with `$(...)` receives the document alone.
|
|
280
|
-
* `print` in `src/commands/standards.ts`.
|
|
286
|
+
* capturing the output with `$(...)` receives the document alone.
|
|
281
287
|
*/
|
|
282
288
|
function printReference(stack: string): number {
|
|
283
289
|
intro('canon tooling reference')
|
package/src/counts/catalogs.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { AUDITS } from '@/audits/catalog'
|
|
4
4
|
import { listSkills } from '@/claude/skills-list'
|
|
5
5
|
import { buildGovCatalog } from '@/gov/list'
|
|
6
|
-
import { listStandards } from '@/standards/read'
|
|
7
6
|
|
|
8
7
|
export interface Catalog {
|
|
9
8
|
readonly id: string
|
|
@@ -59,11 +58,6 @@ export const CATALOGS: readonly Catalog[] = [
|
|
|
59
58
|
nouns: ['rule', 'rules'],
|
|
60
59
|
count: (root) => buildGovCatalog(root).rules.length,
|
|
61
60
|
},
|
|
62
|
-
{
|
|
63
|
-
id: 'standards',
|
|
64
|
-
nouns: ['standard', 'standards'],
|
|
65
|
-
count: (root) => listStandards(root).length,
|
|
66
|
-
},
|
|
67
61
|
{
|
|
68
62
|
id: 'commands',
|
|
69
63
|
nouns: ['command', 'commands'],
|
package/src/design/budget.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { canonicalHeading } from '@/design/parse'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The prose budget `claude/skills/canvas-board/references/design.md` sets for a design record, and the
|
|
3
5
|
* counter the `design` gate stage reads it through.
|
|
@@ -11,21 +13,23 @@
|
|
|
11
13
|
* `<` is dropped the way `prose()` drops it, and so is a fenced block or an
|
|
12
14
|
* HTML comment spanning several lines, which `prose()` would read in part. A
|
|
13
15
|
* `###` subsection counts toward the `##` section holding it, since the
|
|
14
|
-
* standard fixes the sections and a subheading is no way past the cap.
|
|
16
|
+
* standard fixes the sections and a subheading is no way past the cap. A
|
|
17
|
+
* legacy `## Personality` takes the Overview cap through the parser's alias,
|
|
18
|
+
* and a finding names the heading as the record wrote it.
|
|
15
19
|
*/
|
|
16
20
|
export const DESIGN_BUDGET = {
|
|
17
21
|
/** Words before the first `##` section, the title excluded. */
|
|
18
22
|
leadWords: 40,
|
|
19
|
-
/** Words in `##
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
/** Words in `## Overview`, held to one paragraph. */
|
|
24
|
+
overviewWords: 60,
|
|
25
|
+
overviewParagraphs: 1,
|
|
22
26
|
/** Bullets or paragraphs in any other section, beside its table. */
|
|
23
27
|
rules: 3,
|
|
24
28
|
/** Words in one of those rules, the proxy for one line. */
|
|
25
29
|
ruleWords: 25,
|
|
26
30
|
} as const
|
|
27
31
|
|
|
28
|
-
const
|
|
32
|
+
const OVERVIEW = 'Overview'
|
|
29
33
|
|
|
30
34
|
/** A list item at any depth, which starts a rule of its own. */
|
|
31
35
|
const BULLET = /^\s*(?:[-*+]|\d+[.)])\s+/
|
|
@@ -52,15 +56,15 @@ export function measureDesignProse(source: string): string[] {
|
|
|
52
56
|
`The lead holds ${total} words against a cap of ${DESIGN_BUDGET.leadWords}`,
|
|
53
57
|
)
|
|
54
58
|
}
|
|
55
|
-
} else if (section.heading ===
|
|
56
|
-
if (rules.length > DESIGN_BUDGET.
|
|
59
|
+
} else if (canonicalHeading(section.heading) === OVERVIEW) {
|
|
60
|
+
if (rules.length > DESIGN_BUDGET.overviewParagraphs) {
|
|
57
61
|
findings.push(
|
|
58
|
-
`${
|
|
62
|
+
`${section.heading} holds ${rules.length} paragraphs against a cap of ${DESIGN_BUDGET.overviewParagraphs}`,
|
|
59
63
|
)
|
|
60
64
|
}
|
|
61
|
-
if (total > DESIGN_BUDGET.
|
|
65
|
+
if (total > DESIGN_BUDGET.overviewWords) {
|
|
62
66
|
findings.push(
|
|
63
|
-
`${
|
|
67
|
+
`${section.heading} holds ${total} words against a cap of ${DESIGN_BUDGET.overviewWords}`,
|
|
64
68
|
)
|
|
65
69
|
}
|
|
66
70
|
} else {
|
package/src/design/document.ts
CHANGED
|
@@ -114,10 +114,10 @@ export function renderDesignDocument(tokens: DesignTokens = TOKENS): string {
|
|
|
114
114
|
'',
|
|
115
115
|
LEAD,
|
|
116
116
|
'',
|
|
117
|
-
section('
|
|
117
|
+
section('Overview', tokens.personality),
|
|
118
118
|
'',
|
|
119
119
|
section(
|
|
120
|
-
'
|
|
120
|
+
'Colors',
|
|
121
121
|
bullets(tokens.colorRules),
|
|
122
122
|
formatTable(['Role', 'Intent', 'Value'], colorRows(tokens.color)),
|
|
123
123
|
),
|
|
@@ -137,8 +137,10 @@ export function renderDesignDocument(tokens: DesignTokens = TOKENS): string {
|
|
|
137
137
|
formatTable(['Step', 'Multiplier', 'Value'], spaceRows(tokens.spacing)),
|
|
138
138
|
),
|
|
139
139
|
'',
|
|
140
|
+
section('Layout', bullets(tokens.layout)),
|
|
141
|
+
'',
|
|
140
142
|
section(
|
|
141
|
-
'
|
|
143
|
+
'Shapes',
|
|
142
144
|
bullets(tokens.bordersRules),
|
|
143
145
|
formatTable(
|
|
144
146
|
['Role', 'Radius', 'Width', 'When used'],
|
|
@@ -146,8 +148,6 @@ export function renderDesignDocument(tokens: DesignTokens = TOKENS): string {
|
|
|
146
148
|
),
|
|
147
149
|
),
|
|
148
150
|
'',
|
|
149
|
-
section('Layout', bullets(tokens.layout)),
|
|
150
|
-
'',
|
|
151
151
|
section('Motion', bullets(tokens.motion)),
|
|
152
152
|
'',
|
|
153
153
|
section('Iconography', bullets(tokens.iconography)),
|
package/src/design/parse.ts
CHANGED
|
@@ -25,6 +25,23 @@ export interface DesignDoc {
|
|
|
25
25
|
iconography: string
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The three headings the standard renamed toward Google's DESIGN.md, each read
|
|
30
|
+
* as the section it became. Records the seed and `canvas-board` wrote before
|
|
31
|
+
* the rename sit in targets and will not rename themselves, and an unmatched
|
|
32
|
+
* heading parses to nothing without saying so. The field names on `DesignDoc`
|
|
33
|
+
* keep the old words, since the preview reading them is retired separately.
|
|
34
|
+
*/
|
|
35
|
+
export const LEGACY_HEADINGS: Readonly<Record<string, string>> = {
|
|
36
|
+
Personality: 'Overview',
|
|
37
|
+
Color: 'Colors',
|
|
38
|
+
Borders: 'Shapes',
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function canonicalHeading(heading: string): string {
|
|
42
|
+
return LEGACY_HEADINGS[heading] ?? heading
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
const VERIFY_TAG = /\s*\?\s*verify\s*$/
|
|
29
46
|
|
|
30
47
|
/** A cell whose whole content is one balanced code span and nothing else. */
|
|
@@ -34,11 +51,11 @@ export function parseDesignDoc(path: string): DesignDoc {
|
|
|
34
51
|
const raw = readFileSync(path, 'utf8')
|
|
35
52
|
const sections = splitSections(raw)
|
|
36
53
|
return {
|
|
37
|
-
personality: prose(sections['
|
|
38
|
-
color: table(sections['
|
|
54
|
+
personality: prose(sections['Overview']),
|
|
55
|
+
color: table(sections['Colors']),
|
|
39
56
|
typography: table(sections['Typography']),
|
|
40
57
|
spacing: table(sections['Spacing']),
|
|
41
|
-
borders: table(sections['
|
|
58
|
+
borders: table(sections['Shapes']),
|
|
42
59
|
motion: prose(sections['Motion']),
|
|
43
60
|
iconography: prose(sections['Iconography']),
|
|
44
61
|
}
|
|
@@ -53,7 +70,7 @@ function splitSections(raw: string): Record<string, string> {
|
|
|
53
70
|
const match = line.match(/^##\s+(.+?)\s*$/)
|
|
54
71
|
if (match) {
|
|
55
72
|
if (current) out[current] = buffer.join('\n')
|
|
56
|
-
current = match[1]
|
|
73
|
+
current = canonicalHeading(match[1])
|
|
57
74
|
buffer = []
|
|
58
75
|
} else if (current) {
|
|
59
76
|
buffer.push(line)
|
package/src/design/render.ts
CHANGED
|
@@ -268,7 +268,7 @@ ${sections.join('\n')}
|
|
|
268
268
|
|
|
269
269
|
function sectionPersonality(text: string): string {
|
|
270
270
|
if (!text) return ''
|
|
271
|
-
return `<h2>
|
|
271
|
+
return `<h2>Overview</h2>\n<p>${escape(text)}</p>`
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
function sectionColor(rows: Row[]): string {
|
|
@@ -283,7 +283,7 @@ function sectionColor(rows: Row[]): string {
|
|
|
283
283
|
return `<tr><td>${swatch}${cellText(r, 'Role')}</td><td>${cellText(r, 'Intent')}</td><td><code>${shown}</code>${mark(r, 'Value')}</td></tr>`
|
|
284
284
|
})
|
|
285
285
|
.join('\n')
|
|
286
|
-
return `<h2>
|
|
286
|
+
return `<h2>Colors</h2>\n<table><thead><tr><th>Role</th><th>Intent</th><th>Value</th></tr></thead><tbody>${body}</tbody></table>`
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
function sectionTypography(rows: Row[]): string {
|
|
@@ -325,7 +325,7 @@ function sectionBorders(rows: Row[]): string {
|
|
|
325
325
|
return `<tr><td>${cellText(r, 'Role')}</td><td><code>${escape(radius)}</code>${mark(r, 'Radius')}</td><td><code>${escape(width)}</code>${mark(r, 'Width')}</td><td>${cellText(r, 'When used')}</td><td>${sample}</td></tr>`
|
|
326
326
|
})
|
|
327
327
|
.join('\n')
|
|
328
|
-
return `<h2>
|
|
328
|
+
return `<h2>Shapes</h2>\n<table><thead><tr><th>Role</th><th>Radius</th><th>Width</th><th>When used</th><th>Sample</th></tr></thead><tbody>${body}</tbody></table>`
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
/** A section the record writes as one-line rules draws them as a list. */
|
package/src/gate/boundaries.ts
CHANGED
|
@@ -337,8 +337,8 @@ export const capabilitySeeding: Measure = async (ctx) => {
|
|
|
337
337
|
* Every file under `claude/` resolved through its symlinks and rejected when it
|
|
338
338
|
* lands under `internal/`.
|
|
339
339
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
340
|
+
* An installer dereferences any symlink under the plugin root with no code in
|
|
341
|
+
* the path to filter, so a link added there ships whatever it points at. Walking with symlinks
|
|
342
342
|
* followed is what an install copies, so this is the only place the boundary
|
|
343
343
|
* can be measured. The walk tracks the real directories on its current descent
|
|
344
344
|
* and does not enter one a second time, the way `find -L` stops on a loop.
|
package/src/gate/measures.ts
CHANGED
|
@@ -505,7 +505,7 @@ export const documentCeiling: Measure = async (ctx) => {
|
|
|
505
505
|
* Whether a path's stem, its basename up to the first `.`, repeats its
|
|
506
506
|
* immediate parent folder, either whole or as a `<folder>-` prefix.
|
|
507
507
|
*
|
|
508
|
-
* Exact rather than singular-aware, so `
|
|
508
|
+
* Exact rather than singular-aware, so `skills/skill.md` passes. A false
|
|
509
509
|
* positive on a gate stage teaches a contributor to route around it, and a
|
|
510
510
|
* widened match is cheap to add once a real echo slips through.
|
|
511
511
|
*/
|
|
@@ -659,7 +659,7 @@ export const markdownBans: Measure = async (ctx) => {
|
|
|
659
659
|
return {
|
|
660
660
|
emissions: [output(frame.all)],
|
|
661
661
|
failure:
|
|
662
|
-
'Markdown prose carries a banned character, or a relative link resolves to nothing on disk. Rewrite the sentence or fix the link, and reach for a code span only where the token is genuinely an identifier under discussion.',
|
|
662
|
+
'Markdown prose carries a banned character, or a relative link resolves to nothing on disk. Rewrite the sentence, since a colon or a comma standing where the em dash was is the same sentence, or fix the link, and reach for a code span only where the token is genuinely an identifier under discussion.',
|
|
663
663
|
}
|
|
664
664
|
}
|
|
665
665
|
|
|
@@ -771,28 +771,6 @@ export function seedEntryCount(payload: string): number {
|
|
|
771
771
|
)
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
-
/**
|
|
775
|
-
* Scoped to arrival rather than the corpus, since `standards/standard.md`
|
|
776
|
-
* forbids writing a criterion into an existing standard outside the change that
|
|
777
|
-
* exercises it. Gating the known gaps would fail every push until someone
|
|
778
|
-
* closed them all, which is the sweep that rule exists to prevent.
|
|
779
|
-
*/
|
|
780
|
-
export const standardCriteria: Measure = async (ctx) => {
|
|
781
|
-
const run = await ctx.cli(['standards', 'audit', '--arrivals-only'])
|
|
782
|
-
|
|
783
|
-
if (run.exitCode === 0) {
|
|
784
|
-
return { emissions: [info('Arriving standards carry a success criterion')] }
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
return {
|
|
788
|
-
emissions: [output(run.all)],
|
|
789
|
-
failure:
|
|
790
|
-
run.exitCode === 2
|
|
791
|
-
? 'A standard new to this branch carries no success-criterion section. Run bun src/cli.ts standards audit.'
|
|
792
|
-
: 'canon standards audit could not read which standards arrived on this branch. Run bun src/cli.ts standards audit --json to see why.',
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
774
|
/**
|
|
797
775
|
* Every file in the gated corpus, walked from `ctx.root` rather than from the
|
|
798
776
|
* changed set.
|
|
@@ -804,7 +782,7 @@ export const standardCriteria: Measure = async (ctx) => {
|
|
|
804
782
|
* nothing it would notice.
|
|
805
783
|
*
|
|
806
784
|
* `dot` is what reaches the seeds, which is half of what `tooling/` ships. Every
|
|
807
|
-
* seeded `.claude/` tree, `.
|
|
785
|
+
* seeded `.claude/` tree, `.github/` workflow, and `.husky/` hook sits behind a
|
|
808
786
|
* dotted segment, so the default scan walks `tooling/` and returns none of the
|
|
809
787
|
* files a scaffolded project actually receives.
|
|
810
788
|
*/
|
|
@@ -841,8 +819,8 @@ function describeShippedReference(reference: ShippedReference): string {
|
|
|
841
819
|
return 'a pull request number that resolves elsewhere for a reader in a target'
|
|
842
820
|
case 'docs-path':
|
|
843
821
|
return 'a path into this repository that a registry install never carries'
|
|
844
|
-
case '
|
|
845
|
-
return 'a
|
|
822
|
+
case 'retired-verb':
|
|
823
|
+
return 'a call to canon standards, a verb no installed CLI carries'
|
|
846
824
|
case 'phase-label':
|
|
847
825
|
return 'a phase label that names a board no target holds'
|
|
848
826
|
case 'rule-path':
|
|
@@ -903,8 +881,8 @@ export const shippedReferences: Measure = async (ctx) => {
|
|
|
903
881
|
),
|
|
904
882
|
failure:
|
|
905
883
|
found.length === 1
|
|
906
|
-
? `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>,
|
|
907
|
-
: `${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>,
|
|
884
|
+
? `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>, cite the owner skill's reference in place of a canon standards call, 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.`
|
|
885
|
+
: `${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>, cite the owner skill's reference in place of a canon standards call, 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.`,
|
|
908
886
|
}
|
|
909
887
|
}
|
|
910
888
|
|
package/src/gate/sequencer.ts
CHANGED
|
@@ -50,6 +50,45 @@ export interface LoadReading {
|
|
|
50
50
|
/** The 1-minute load average. */
|
|
51
51
|
readonly loadAverage: number
|
|
52
52
|
readonly cores: number
|
|
53
|
+
/**
|
|
54
|
+
* The `some avg10` stall share per resource from `/proc/pressure`, which the
|
|
55
|
+
* load average folds into one number. Absent where the kernel has no PSI.
|
|
56
|
+
*/
|
|
57
|
+
readonly pressure?: PressureReading
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PressureReading {
|
|
61
|
+
readonly cpu: number
|
|
62
|
+
readonly memory: number
|
|
63
|
+
readonly io: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reads the `some avg10` percentage from one `/proc/pressure/<resource>` file.
|
|
68
|
+
* A file that does not parse answers `undefined`, which drops the clause.
|
|
69
|
+
*/
|
|
70
|
+
export function parsePressure(text: string): number | undefined {
|
|
71
|
+
const match = /^some\b.*\bavg10=(\d+(?:\.\d+)?)\b/m.exec(text)
|
|
72
|
+
if (match === null) return undefined
|
|
73
|
+
const value = Number.parseFloat(match[1])
|
|
74
|
+
return Number.isFinite(value) ? value : undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readPressure(): PressureReading | undefined {
|
|
78
|
+
try {
|
|
79
|
+
const read = (name: string) =>
|
|
80
|
+
parsePressure(readFileSync(`/proc/pressure/${name}`, 'utf8'))
|
|
81
|
+
const cpu = read('cpu')
|
|
82
|
+
const memory = read('memory')
|
|
83
|
+
const io = read('io')
|
|
84
|
+
if (cpu === undefined || memory === undefined || io === undefined) {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
return { cpu, memory, io }
|
|
88
|
+
} catch {
|
|
89
|
+
// PSI off or absent: the files may exist and still fail with EOPNOTSUPP.
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
53
92
|
}
|
|
54
93
|
|
|
55
94
|
/**
|
|
@@ -62,7 +101,12 @@ export function readProcLoad(): LoadReading | undefined {
|
|
|
62
101
|
const first = readFileSync('/proc/loadavg', 'utf8').split(' ')[0]
|
|
63
102
|
const loadAverage = Number.parseFloat(first)
|
|
64
103
|
if (!Number.isFinite(loadAverage)) return undefined
|
|
65
|
-
|
|
104
|
+
const pressure = readPressure()
|
|
105
|
+
return {
|
|
106
|
+
loadAverage,
|
|
107
|
+
cores: availableParallelism(),
|
|
108
|
+
...(pressure === undefined ? {} : { pressure }),
|
|
109
|
+
}
|
|
66
110
|
} catch {
|
|
67
111
|
return undefined
|
|
68
112
|
}
|
|
@@ -252,7 +296,12 @@ function withLoadLine(
|
|
|
252
296
|
const reading = (ctx.readLoad ?? readProcLoad)()
|
|
253
297
|
if (reading === undefined) return outcome
|
|
254
298
|
|
|
255
|
-
const
|
|
299
|
+
const { pressure } = reading
|
|
300
|
+
const stalls =
|
|
301
|
+
pressure === undefined
|
|
302
|
+
? ''
|
|
303
|
+
: `, pressure cpu ${pressure.cpu.toFixed(1)}% memory ${pressure.memory.toFixed(1)}% io ${pressure.io.toFixed(1)}% over 10s,`
|
|
304
|
+
const text = `Load average ${reading.loadAverage} on ${reading.cores} cores${stalls} when the ${stage.label} stage failed`
|
|
256
305
|
return {
|
|
257
306
|
...outcome,
|
|
258
307
|
emissions: [...outcome.emissions, { kind: 'info', text }],
|