@erclx/canon 5.34.0 → 5.35.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.
Files changed (143) hide show
  1. package/README.md +3 -5
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/api-design/REQUIREMENT.md +1 -1
  4. package/claude/skills/auto-ship/references/draft-mark.md +1 -1
  5. package/claude/skills/canon-cli/SKILL.md +10 -11
  6. package/claude/skills/create-skill/SKILL.md +2 -1
  7. package/claude/skills/create-skill/references/skill-paths.md +12 -15
  8. package/{standards/standard.md → claude/skills/create-skill/references/skill-reference.md} +50 -45
  9. package/claude/skills/create-skill/references/skill.md +9 -3
  10. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  11. package/claude/skills/git-commit/references/versioning.md +13 -13
  12. package/claude/skills/git-pr/SKILL.md +1 -1
  13. package/claude/skills/git-pr/references/publish.md +1 -1
  14. package/claude/skills/review-address/REQUIREMENT.md +1 -1
  15. package/claude/skills/review-address/SKILL.md +2 -2
  16. package/claude/skills/review-pr/SKILL.md +3 -3
  17. package/claude/skills/review-pr/references/post.md +1 -1
  18. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  19. package/claude/skills/review-ui/SKILL.md +1 -1
  20. package/claude/skills/role-orchestrator/REQUIREMENT.md +2 -2
  21. package/claude/skills/role-orchestrator/SKILL.md +11 -12
  22. package/claude/skills/role-orchestrator/references/orchestrator-cloud-launch.md +3 -1
  23. package/claude/skills/role-orchestrator/references/orchestrator-dispatch.md +11 -8
  24. package/claude/skills/role-orchestrator/references/orchestrator-handback.md +10 -4
  25. package/claude/skills/role-orchestrator/references/orchestrator-launch.md +2 -2
  26. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +11 -12
  27. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +12 -15
  28. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +6 -4
  29. package/claude/skills/role-orchestrator/scripts/poll.ts +50 -1
  30. package/claude/skills/role-reviewer/REQUIREMENT.md +17 -7
  31. package/claude/skills/role-reviewer/SKILL.md +17 -10
  32. package/claude/skills/role-worker/REQUIREMENT.md +1 -1
  33. package/claude/skills/role-worker/SKILL.md +6 -6
  34. package/claude/skills/session-compact/references/handoff-note.md +1 -1
  35. package/claude/skills/target-setup/SKILL.md +2 -2
  36. package/claude/skills/target-setup/references/detect.md +3 -3
  37. package/docs/agents/audits.md +1 -1
  38. package/docs/agents/commands.md +2 -4
  39. package/docs/agents/counts.md +5 -6
  40. package/docs/agents/drift-surfaces.md +15 -19
  41. package/docs/agents/index.md +2 -3
  42. package/docs/agents/install-and-sync.md +7 -31
  43. package/docs/agents/markdown-audit.md +10 -12
  44. package/docs/agents/migrate.md +1 -1
  45. package/docs/agents/records-stale.md +1 -1
  46. package/docs/agents/records-validate.md +11 -23
  47. package/docs/agents/records.md +1 -1
  48. package/docs/agents/routing.md +1 -1
  49. package/docs/agents/rule-citations.md +11 -14
  50. package/docs/agents/scripting.md +2 -20
  51. package/docs/agents/state-scoped-risk.md +1 -1
  52. package/docs/agents/tasks-validate.md +1 -1
  53. package/docs/agents/teach.md +1 -1
  54. package/docs/target/index.md +1 -0
  55. package/docs/target/migrations.md +4 -0
  56. package/docs/target/projects.md +9 -9
  57. package/docs/target/scenarios.md +5 -6
  58. package/docs/target/standards-moved.md +32 -0
  59. package/docs/target/sync.md +5 -5
  60. package/docs/workflow/operating-model.md +16 -18
  61. package/docs/workflow/skill-map.md +1 -1
  62. package/governance/detect.toml +0 -4
  63. package/governance/renames.toml +0 -1
  64. package/governance/stacks/base.toml +15 -8
  65. package/governance/stacks/go.toml +2 -0
  66. package/governance/stacks/node.toml +1 -1
  67. package/governance/stacks/php.toml +2 -0
  68. package/governance/stacks/python.toml +1 -1
  69. package/package.json +1 -4
  70. package/scripts/core/install-check.sh +2 -3
  71. package/src/audits/catalog.ts +0 -9
  72. package/src/claude/routing.ts +1 -1
  73. package/src/claude/skills-reach.ts +4 -6
  74. package/src/cli/help.ts +3 -4
  75. package/src/cli.ts +0 -2
  76. package/src/commands/gov.ts +5 -9
  77. package/src/commands/init.ts +1 -1
  78. package/src/commands/labels.ts +4 -44
  79. package/src/commands/migrate.ts +1 -1
  80. package/src/commands/records.ts +4 -20
  81. package/src/commands/tooling.ts +2 -4
  82. package/src/counts/catalogs.ts +0 -6
  83. package/src/gate/boundaries.ts +2 -2
  84. package/src/gate/measures.ts +6 -28
  85. package/src/gate/sequencer.ts +51 -2
  86. package/src/gate/stages.ts +0 -18
  87. package/src/gov/citations.ts +11 -43
  88. package/src/markdown/bans.ts +1 -1
  89. package/src/migrate/scratch-evidence.ts +2 -2
  90. package/src/records/validate.ts +6 -194
  91. package/src/sandbox/provision.ts +0 -5
  92. package/src/secrets/shipped.ts +1 -2
  93. package/src/serve/static.ts +1 -2
  94. package/src/shipped/references.ts +29 -80
  95. package/src/sync/check.ts +18 -14
  96. package/src/tooling/inject.ts +6 -37
  97. package/src/tooling/read.ts +2 -3
  98. package/src/tooling/scan.ts +4 -17
  99. package/src/tooling/subfolder.ts +2 -66
  100. package/tooling/base/configs/scripts/verify.json +1 -1
  101. package/tooling/base/configs/scripts/verify.ts +1 -12
  102. package/tooling/base/manifest.toml +1 -2
  103. package/tooling/base/reference.md +10 -16
  104. package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
  105. package/tooling/base/seeds/.lintstagedrc +1 -2
  106. package/tooling/base/seeds/canon/context/ci.md +0 -1
  107. package/tooling/base/seeds/canon/context/development.md +1 -1
  108. package/tooling/claude/reference.md +1 -1
  109. package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
  110. package/tooling/go/reference.md +1 -1
  111. package/tooling/php/reference.md +1 -1
  112. package/tooling/python/reference.md +2 -2
  113. package/tooling/web/configs/.github/workflows/verify.yml +0 -3
  114. package/tooling/web/manifest.toml +0 -1
  115. package/tooling/web/reference.md +2 -4
  116. package/docs/agents/standards-audit.md +0 -40
  117. package/governance/rules/tooling/765-spelling.md +0 -19
  118. package/governance/stacks/astro.toml +0 -2
  119. package/governance/stacks/nextjs.toml +0 -2
  120. package/governance/stacks/node-server.toml +0 -2
  121. package/governance/stacks/python-fastapi.toml +0 -2
  122. package/governance/stacks/react.toml +0 -2
  123. package/src/commands/standards.ts +0 -278
  124. package/src/labels/spelling.ts +0 -120
  125. package/src/standards/audit.ts +0 -146
  126. package/src/standards/list.ts +0 -33
  127. package/src/standards/read.ts +0 -177
  128. package/src/standards/scope.ts +0 -56
  129. package/standards/index.md +0 -10
  130. package/tooling/base/configs/.vscode/extensions.json +0 -9
  131. package/tooling/base/configs/.vscode/settings.json +0 -3
  132. package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
  133. package/tooling/base/seeds/.cspell/tech-stack.txt +0 -25
  134. package/tooling/base/seeds/cspell.json +0 -20
  135. package/tooling/go/seeds/.cspell/tech-stack.txt +0 -16
  136. package/tooling/nextjs/seeds/.cspell/tech-stack.txt +0 -3
  137. package/tooling/php/seeds/.cspell/tech-stack.txt +0 -5
  138. package/tooling/python/seeds/.cspell/tech-stack.txt +0 -18
  139. package/tooling/vite-react/seeds/.cspell/project-terms.txt +0 -1
  140. package/tooling/vite-react/seeds/.cspell/tech-stack.txt +0 -2
  141. package/tooling/web/configs/.vscode/extensions.json +0 -13
  142. package/tooling/web/configs/.vscode/settings.json +0 -10
  143. package/tooling/web/seeds/.cspell/tech-stack.txt +0 -21
package/src/sync/check.ts CHANGED
@@ -3,9 +3,10 @@ import { basename, join, sep } from 'node:path'
3
3
  import { execa } from 'execa'
4
4
  import { gitEnv } from '@/git/env'
5
5
  import { createDesignAdapter, DESIGN_INSTALL_DIR } from '@/design/adapter'
6
- import { createGovAdapter, rulesSourceDir } from '@/gov/adapter'
6
+ import { createGovAdapter } from '@/gov/adapter'
7
7
  import { successorResolver } from '@/gov/renames'
8
- import { loadGovStack, resolveMissingRules, resolveRules } from '@/gov/stacks'
8
+ import { lookupRules } from '@/gov/install'
9
+ import { resolveMissingRules, resolveRules } from '@/gov/stacks'
9
10
  import { planSync, type ScanEntry, type SyncAdapter } from '@/sync/engine'
10
11
  import {
11
12
  collectSuperseded,
@@ -576,7 +577,8 @@ export function readInstalledRules(
576
577
  * not already hold.
577
578
  *
578
579
  * The band test is the entitlement filter, and `bands` carries two sources: the
579
- * folders the target already holds, and the folders the base stack takes whole.
580
+ * folders the target already holds, and the folders holding a rule the base
581
+ * stack resolves to.
580
582
  * A rule authored under `lang/` or `ui/` is named by an individual stack, so it
581
583
  * belongs to some targets and not others, and listing every added file would
582
584
  * tell a base consumer about rules it was never entitled to. The test
@@ -615,27 +617,29 @@ export function selectNewRules(
615
617
  }
616
618
 
617
619
  /**
618
- * Band folders the base stack takes whole. Every governance stack extends base,
619
- * so a rule authored under one of these is entitled to every target.
620
+ * Band folders holding a rule the base stack resolves to. Every governance
621
+ * stack extends base, so a new rule authored beside one of base's rules is
622
+ * reported to every target the fallback below reads.
620
623
  *
621
624
  * This is what covers a band no target can carry yet. Entitlement is otherwise
622
- * read off folders the target already holds, and a folder added to base later
625
+ * read off folders the target already holds, and a band base reaches later
623
626
  * exists in no installed tree, so without this the rules inside it would reach
624
- * nobody. Read from the stack file rather than fixed, so that addition needs no
625
- * code change here.
627
+ * nobody. Read through `resolveRules` rather than off the stack's entries,
628
+ * since base names its rules one by one and an entry is a slug rather than a
629
+ * folder.
626
630
  *
627
- * A folder only a leaf stack names is deliberately absent. It is entitled to
631
+ * A folder only a leaf stack reaches is deliberately absent. It is entitled to
628
632
  * some targets and not others, which is the distinction the band test makes and
629
633
  * the installed tree is the only evidence of.
630
634
  */
631
635
  export function baseBands(root: string): Set<string> {
632
- const stack = loadGovStack(root, 'base')
633
- if (stack === undefined) return new Set()
636
+ const resolution = resolveRules(root, 'base')
637
+ if (!resolution.ok) return new Set()
634
638
 
635
639
  return new Set(
636
- stack.rules.filter((entry) =>
637
- isDirectory(join(rulesSourceDir(root), entry)),
638
- ),
640
+ lookupRules(root, resolution.rules)
641
+ .found.map((source) => source.subdir.split(sep)[0] ?? '')
642
+ .filter((band) => band !== ''),
639
643
  )
640
644
  }
641
645
 
@@ -20,13 +20,7 @@ import {
20
20
  readPackage,
21
21
  serializePackage,
22
22
  } from '@/tooling/package'
23
- import {
24
- hasSpellConfig,
25
- isSubfolderTarget,
26
- isWithheldInSubfolder,
27
- SUBFOLDER_SPELL_CONFIG,
28
- subfolderSpellConfig,
29
- } from '@/tooling/subfolder'
23
+ import { isSubfolderTarget, isWithheldInSubfolder } from '@/tooling/subfolder'
30
24
  import { logAdd, logRemove, logStep, logWarn } from '@/cli/ui'
31
25
 
32
26
  export async function injectConfigs(
@@ -72,30 +66,13 @@ async function writeSeed(src: string, dest: string): Promise<void> {
72
66
  }
73
67
 
74
68
  /**
75
- * Copies a seed when the target lacks it. When the target already has one and
76
- * the seed is a `.txt` word list, missing lines are appended and the file is
77
- * re-sorted. Every other existing file is left untouched.
69
+ * Copies a seed when the target lacks it and leaves an existing file untouched.
78
70
  */
79
- async function mergeSeedFile(src: string, dest: string): Promise<void> {
71
+ async function copySeedIfMissing(src: string, dest: string): Promise<void> {
80
72
  await mkdir(dirname(dest), { recursive: true })
73
+ if (existsSync(dest)) return
81
74
 
82
- if (!existsSync(dest)) {
83
- await writeSeed(src, dest)
84
- return
85
- }
86
-
87
- if (!src.endsWith('.txt')) return
88
-
89
- const existing = (await readFile(dest, 'utf8')).split('\n')
90
- const incoming = (await readFile(src, 'utf8')).split('\n')
91
- const missing = incoming.filter(
92
- (word) => word !== '' && !existing.includes(word),
93
- )
94
-
95
- if (missing.length === 0) return
96
-
97
- const merged = [...existing.filter((line) => line !== ''), ...missing].sort()
98
- await writeFile(dest, `${merged.join('\n')}\n`)
75
+ await writeSeed(src, dest)
99
76
  }
100
77
 
101
78
  export async function injectSeeds(
@@ -116,20 +93,12 @@ export async function injectSeeds(
116
93
  logStep(`Applying ${manifest.name} seeds`)
117
94
  for (const rel of files) {
118
95
  const dest = resolveSurfacePath(target, rel)
119
- await mergeSeedFile(join(manifest.seedsDir, rel), dest)
96
+ await copySeedIfMissing(join(manifest.seedsDir, rel), dest)
120
97
  logAdd(relative(target, dest))
121
98
  applied.push(rel)
122
99
  }
123
100
  }
124
101
 
125
- const spellConfig = isSubfolder ? subfolderSpellConfig(chain) : undefined
126
- if (spellConfig !== undefined && !hasSpellConfig(target)) {
127
- logStep('Applying subfolder spell config')
128
- await writeFile(join(target, SUBFOLDER_SPELL_CONFIG), spellConfig)
129
- logAdd(SUBFOLDER_SPELL_CONFIG)
130
- applied.push(SUBFOLDER_SPELL_CONFIG)
131
- }
132
-
133
102
  return applied
134
103
  }
135
104
 
@@ -28,9 +28,8 @@ function toolingSourceDir(root: string): string {
28
28
  }
29
29
 
30
30
  /**
31
- * The roots a stack reference resolves against, mirroring `standardRoots` in
32
- * `src/standards/read.ts`. No stack reference installs into a project any
33
- * more, so the package corpus is the only root that answers there. The
31
+ * The roots a stack reference resolves against. No stack reference installs
32
+ * into a project any more, so the package corpus is the only root that answers there. The
34
33
  * authoring root stays ahead of it for the toolkit's own repository, which
35
34
  * authors the corpus this reads, and for a project authoring stacks of its
36
35
  * own under the same layout.
@@ -16,13 +16,7 @@ import {
16
16
  readPackage,
17
17
  type ScriptState,
18
18
  } from '@/tooling/package'
19
- import {
20
- hasSpellConfig,
21
- isSubfolderTarget,
22
- isWithheldInSubfolder,
23
- SUBFOLDER_SPELL_CONFIG,
24
- subfolderSpellConfig,
25
- } from '@/tooling/subfolder'
19
+ import { isSubfolderTarget, isWithheldInSubfolder } from '@/tooling/subfolder'
26
20
 
27
21
  export interface ConfigState {
28
22
  readonly rel: string
@@ -73,9 +67,9 @@ function isIdentical(a: string, b: string): boolean {
73
67
  * so the path is reported once.
74
68
  *
75
69
  * A target below its git toplevel is scanned as a subfolder: a config or seed
76
- * GitHub reads only at the root moves to `withheld` and counts as no change,
77
- * and the nested spell config joins the seeds. The scope is read here so every
78
- * caller of `scan` agrees without passing a flag.
70
+ * GitHub reads only at the root moves to `withheld` and counts as no change.
71
+ * The scope is read here so every caller of `scan` agrees without passing a
72
+ * flag.
79
73
  */
80
74
  export function scan(chain: readonly Manifest[], target: string): ScanResult {
81
75
  const isSubfolder = isSubfolderTarget(target)
@@ -132,13 +126,6 @@ export function scan(chain: readonly Manifest[], target: string): ScanResult {
132
126
  }
133
127
  }
134
128
 
135
- if (isSubfolder && subfolderSpellConfig(chain) !== undefined) {
136
- seeds.push({
137
- rel: SUBFOLDER_SPELL_CONFIG,
138
- state: hasSpellConfig(target) ? 'present' : 'missing',
139
- })
140
- }
141
-
142
129
  const packagePath = join(target, 'package.json')
143
130
  const pkg = readPackage(packagePath)
144
131
  const scripts = pkg ? collectScripts(chain, pkg) : []
@@ -1,18 +1,9 @@
1
- import { existsSync, readdirSync, realpathSync } from 'node:fs'
2
- import { basename, join, relative } from 'node:path'
1
+ import { realpathSync } from 'node:fs'
2
+ import { relative } from 'node:path'
3
3
  import { gitEnv } from '@/git/env'
4
- import { isWholeStack, type Manifest, seedFiles } from '@/tooling/manifest'
5
-
6
- /**
7
- * The path the generated subfolder spell config is written to. Written
8
- * copy-once, since a project that already configured cspell here owns it.
9
- */
10
- export const SUBFOLDER_SPELL_CONFIG = 'cspell.json'
11
4
 
12
5
  const WITHHELD_PREFIX = '.github/'
13
6
 
14
- const SPELL_CONFIG_NAMES = ['cspell.json', '.cspell.json']
15
-
16
7
  /**
17
8
  * Whether the target sits below the root of the git repository holding it. A
18
9
  * target outside any repository, or one git cannot read, counts as a root, so
@@ -57,58 +48,3 @@ export function subfolderPath(target: string): string | undefined {
57
48
  export function isWithheldInSubfolder(rel: string): boolean {
58
49
  return rel.startsWith(WITHHELD_PREFIX)
59
50
  }
60
-
61
- /**
62
- * Whether the target already carries a cspell config of its own, in any of
63
- * the file names cspell searches for.
64
- */
65
- export function hasSpellConfig(target: string): boolean {
66
- if (SPELL_CONFIG_NAMES.some((name) => existsSync(join(target, name)))) {
67
- return true
68
- }
69
- if (!existsSync(target)) return false
70
- return readdirSync(target).some((name) => name.startsWith('cspell.config.'))
71
- }
72
-
73
- /**
74
- * Builds the nested cspell config that registers a subfolder's seeded word
75
- * lists. A root `cspell '**'` merges it with the root config for files under
76
- * the subfolder. Each dictionary takes a `local-` name, since reusing a root
77
- * dictionary's name shadows it inside the subfolder and a root word then fails
78
- * there.
79
- *
80
- * Returns nothing when the chain carries `base`, whose own seed already writes
81
- * a config, or when no stack seeds a word list.
82
- */
83
- export function subfolderSpellConfig(
84
- chain: readonly Manifest[],
85
- ): string | undefined {
86
- if (
87
- chain.some((manifest) => manifest.name === 'base' && isWholeStack(manifest))
88
- ) {
89
- return undefined
90
- }
91
-
92
- const lists = new Set<string>()
93
- for (const manifest of chain) {
94
- for (const rel of seedFiles(manifest)) {
95
- if (rel.startsWith('.cspell/') && rel.endsWith('.txt')) lists.add(rel)
96
- }
97
- }
98
- if (lists.size === 0) return undefined
99
-
100
- const definitions = [...lists].sort().map((path) => ({
101
- name: `local-${basename(path, '.txt')}`,
102
- path,
103
- addWords: true,
104
- }))
105
-
106
- const config = {
107
- version: '0.2',
108
- dictionaryDefinitions: definitions,
109
- dictionaries: definitions.map((definition) => definition.name),
110
- ignorePaths: ['.cspell/**'],
111
- }
112
-
113
- return `${JSON.stringify(config, null, 2)}\n`
114
- }
@@ -1,3 +1,3 @@
1
1
  {
2
- "require": ["format", "check:format", "check:spell", "check:shell"]
2
+ "require": ["format", "check:format", "check:shell"]
3
3
  }
@@ -54,12 +54,6 @@ const PHASES: readonly Phase[] = [
54
54
  failed: 'Format check failed',
55
55
  passed: 'Format check passed',
56
56
  },
57
- {
58
- script: 'check:spell',
59
- title: 'Spelling',
60
- failed: 'Spell check failed',
61
- passed: 'Spell check passed',
62
- },
63
57
  {
64
58
  script: 'check:shell',
65
59
  title: 'Shell',
@@ -83,12 +77,7 @@ const PHASES: readonly Phase[] = [
83
77
  // A subfolder synced with --skip base declares none of these, since the
84
78
  // repository root owns them, so a list that does not require one runs it only
85
79
  // where the folder declares it.
86
- const BASE_PHASES = new Set([
87
- 'format',
88
- 'check:format',
89
- 'check:spell',
90
- 'check:shell',
91
- ])
80
+ const BASE_PHASES = new Set(['format', 'check:format', 'check:shell'])
92
81
 
93
82
  const PHASE_LIST = 'scripts/verify.json'
94
83
  const CARD_EXCLUSION = 'scripts/check-card-exclusion.sh'
@@ -11,14 +11,12 @@ per_root = ["scripts/verify.sh", "scripts/verify.ts", "scripts/verify.json"]
11
11
  [dependencies.dev]
12
12
  packages = [
13
13
  "prettier",
14
- "cspell",
15
14
  "husky",
16
15
  "@commitlint/cli",
17
16
  "@commitlint/config-conventional",
18
17
  ]
19
18
 
20
19
  [scripts]
21
- "check:spell" = "cspell '**' '.*/**' '.*' --no-progress --color --show-context"
22
20
  "check:format" = "prettier --check --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && find scripts -name '*.sh' -exec shfmt --diff --indent 2 {} +"
23
21
  "check:shell" = "find scripts -name '*.sh' -exec shellcheck --severity=warning {} +"
24
22
  "format" = "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && find scripts -name '*.sh' -exec shfmt --write --indent 2 {} +"
@@ -29,3 +27,4 @@ packages = [
29
27
  "# System" = [".DS_Store"]
30
28
  "# Dependencies" = ["node_modules/"]
31
29
  "# Secrets" = [".env", ".env.*", "*.local", "!.env.example"]
30
+ "# VSCode" = [".vscode/"]
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, spelling, shell linting, conventional commits, git hooks, CI, and one maintenance script. Every other stack extends it, so a decision made here is one every stack inherits.
5
+ The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, shell linting, conventional commits, git hooks, CI, and one maintenance script. Every other stack extends it, so a decision made here is one every stack inherits.
6
6
 
7
7
  A repository with several language roots layers this stack once at the root and skips it per subtree, since git honors only one `core.hooksPath` and a re-dropped husky silently breaks. `canon docs scenarios` carries the recipe.
8
8
 
@@ -15,17 +15,14 @@ Golden config files live in `tooling/base/configs/` and are copied into the targ
15
15
  - `.editorconfig`: `root = true`, with an `[*.sh]` block setting `indent_style = space` and `indent_size = 2`.
16
16
  - `commitlint.config.js`: ESM default export extending `@commitlint/config-conventional`. Rules are `header-max-length: 72`, `scope-case: lower-case`, `subject-full-stop: never`, `subject-case` disabled, and a local `no-claude-co-author` rule refusing a `Co-authored-by` trailer that names Claude or Anthropic.
17
17
  - `.husky/`: `pre-commit`, `commit-msg`, `pre-push`, `post-merge`, `post-rewrite`.
18
- - `.vscode/extensions.json` and `.vscode/settings.json`: editor wiring for Prettier, cspell, shfmt, and shellcheck.
19
18
  - `scripts/verify.sh`: the maintenance entry point behind `check`, a wrapper that hands off to `scripts/verify.ts`.
20
- - `scripts/verify.ts`: the one verify runner every stack shares. It runs typecheck, lint, format, format check, spelling, shell, unit tests, build, markdown bans, then card exclusion, always in that order, and skips any phase the folder's `scripts/verify.json` leaves out. Node modules only, since a target's own lint and typecheck read it.
21
- - `scripts/verify.json`: the phases a stack requires, plus any tools and preconditions it probes first. The nearest stack's copy wins. A required phase whose script `package.json` lacks fails the run. A base phase the list leaves out runs only where the folder declares its script. Base requires its four phases, so a base-only target still fails on a missing one. These three files are base's `[sync] per_root` set, which a `--skip base` subfolder still receives.
19
+ - `scripts/verify.ts`: the one verify runner every stack shares. It runs typecheck, lint, format, format check, shell, unit tests, build, markdown bans, then card exclusion, always in that order, and skips any phase the folder's `scripts/verify.json` leaves out. Node modules only, since a target's own lint and typecheck read it.
20
+ - `scripts/verify.json`: the phases a stack requires, plus any tools and preconditions it probes first. The nearest stack's copy wins. A required phase whose script `package.json` lacks fails the run. A base phase the list leaves out runs only where the folder declares its script. Base requires its three phases, so a base-only target still fails on a missing one. These three files are base's `[sync] per_root` set, which a `--skip base` subfolder still receives.
22
21
 
23
22
  ## What ships as user-owned seeds
24
23
 
25
24
  Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and never overwrites it, so a project extends them freely.
26
25
 
27
- - `cspell.json`: `version: "0.2"`, `language: "en"`, `useGitignore: true`, `gitignoreRoot: ["."]`, dictionary definitions for `project-terms` and `tech-stack` with `addWords: true` on both, and `ignorePaths: [".cspell/**", ".git/**"]` to skip dictionary self-checks and git object files.
28
- - `.cspell/project-terms.txt` and `.cspell/tech-stack.txt`: one word per line, sorted alphabetically.
29
26
  - `.lintstagedrc`: the glob map below.
30
27
  - `.prettierignore`: created empty. Projects add their own entries.
31
28
  - `.github/workflows/verify.yml`: runs on pull requests targeting `main` and on `workflow_dispatch`. A project with more than one language root adds its own jobs here.
@@ -36,15 +33,14 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
36
33
 
37
34
  - Runtime: `bun` as package manager and script runner, `bunx` over `npx` for a one-off executable.
38
35
  - Formatting: Prettier for what it parses, shfmt for shell. Two formatters because Prettier has no shell parser.
39
- - Spelling: cspell over the whole tree, with project vocabulary split into a project-terms dictionary and a tech-stack one.
40
36
  - Shell: shfmt formats and shellcheck lints at warning severity. Both take their files from `find scripts -name '*.sh'`, since `scripts/` can mix shell with TypeScript and shellcheck has no directory mode. A shell script with a shebang and no `.sh` extension is covered by neither, so give it the extension.
41
37
  - Commits: commitlint against conventional commits, wired through the husky `commit-msg` hook. Format is `<type>(<scope>): <subject>` in imperative mood with no trailing period.
42
- - Dev dependencies: `prettier`, `cspell`, `husky`, `@commitlint/cli`, `@commitlint/config-conventional`. Install via `bun add -D`.
38
+ - Dev dependencies: `prettier`, `husky`, `@commitlint/cli`, `@commitlint/config-conventional`. Install via `bun add -D`.
43
39
 
44
40
  ## File layout
45
41
 
46
42
  - All shell scripts live in `scripts/`. Do not place a `.sh` file outside it.
47
- - Dictionaries live in `.cspell/`, hooks in `.husky/`, seeded context docs in `canon/context/`.
43
+ - Hooks live in `.husky/`, seeded context docs in `canon/context/`.
48
44
  - The `canon/context/` location matches the three-tier context model: project-wide invariants in `CLAUDE.md` and `canon/REQUIREMENTS.md`, path-scoped rules in `.claude/rules/`, and on-demand domain narrative in `canon/context/`. Indexes stay opt-in.
49
45
 
50
46
  ## Hooks
@@ -59,13 +55,13 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
59
55
 
60
56
  Seeded baseline globs:
61
57
 
62
- - `**/*.{json,md,mdc}` runs `prettier --write --ignore-path .gitignore --ignore-path .prettierignore` then `cspell --no-must-find-files`
58
+ - `**/*.{json,md,mdc}` runs `prettier --write --ignore-path .gitignore --ignore-path .prettierignore`
63
59
  - `**/*.md` runs `canon indexes regen`
64
60
  - `**/*.sh` runs `shfmt --write --indent 2` then `shellcheck --severity=warning`
65
61
 
66
62
  ## CI
67
63
 
68
- - Steps: checkout, setup Bun at latest, `bun install --frozen-lockfile`, install `shfmt` and `shellcheck` via apt, then `check:format`, `check:spell`, and `check:shell`.
64
+ - Steps: checkout, setup Bun at latest, `bun install --frozen-lockfile`, install `shfmt` and `shellcheck` via apt, then `check:format` and `check:shell`.
69
65
  - CI asserts and never writes. Format must be clean before push.
70
66
 
71
67
  ## Gitignore
@@ -73,13 +69,12 @@ Seeded baseline globs:
73
69
  - `# System`: `.DS_Store`
74
70
  - `# Dependencies`: `node_modules/`
75
71
  - `# Secrets`: `.env`, `.env.*`, `*.local`, `!.env.example`
72
+ - `# VSCode`: `.vscode/`
76
73
 
77
74
  ## Anti-patterns
78
75
 
79
76
  Sticky negative knowledge. Do not relearn.
80
77
 
81
- - Do NOT drop `gitignoreRoot: ["."]` from `cspell.json`. Without it, cspell run from inside a linked worktree under `.claude/worktrees/` walks up into the parent repo's `.gitignore`, resolves every worktree file as living under the ignored worktree path, and checks zero files. New words then pass locally and fail in CI.
82
- - Do NOT rely on the default `**` glob for spelling. It skips dot-prefixed folders, leaving `.claude/`, `.github/`, and `.husky/` unchecked. Pass `'**' '.*/**' '.*'` explicitly.
83
78
  - Do NOT invoke prettier without both `--ignore-path .gitignore --ignore-path .prettierignore`. Passing one drops the other, since the flag replaces the default rather than adding to it.
84
79
  - Do NOT omit `--log-level warn` from a prettier invocation. The default prints a line per unchanged file.
85
80
  - Do NOT put logic in a husky hook and expect its shebang to hold. Husky runs hooks as `sh -e`, so a hook carrying logic is POSIX sh under errexit whatever the first line says.
@@ -92,11 +87,10 @@ Sticky negative knowledge. Do not relearn.
92
87
  | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
93
88
  | `bun run check` | Repairs a checkout via `scripts/verify.ts`. Honors `VERIFY_NESTED=true` to suppress timeline boundaries when another script calls it. |
94
89
  | `bun run check:format` | Asserts prettier and shfmt formatting without writing |
95
- | `bun run check:spell` | Runs cspell across every file, with context on failures |
96
90
  | `bun run check:shell` | Runs shellcheck at warning severity |
97
91
  | `bun run format` | Writes prettier and shfmt formatting in place |
98
92
  | `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
99
93
 
100
- `bun run check` repairs a checkout rather than gating one. It runs `format` first to auto-fix drifted code, then asserts only what the formatters could not fix. The pull request workflow calls `check:format`, `check:spell`, and `check:shell` directly, and those three are the gate.
94
+ `bun run check` repairs a checkout rather than gating one. It runs `format` first to auto-fix drifted code, then asserts only what the formatters could not fix. The pull request workflow calls `check:format` and `check:shell` directly, and those two are the gate.
101
95
 
102
- `scripts/verify.ts`'s `Markdown bans` stage is the exception: it gates rather than repairs. It runs `canon markdown audit` over every tracked markdown file except `CHANGELOG.md`, since a generated changelog carries commit subjects nobody wrote against the ban set, and exits `2` on a finding or `3` on a shipped-empty ban set, failing `bun run check` and the `pre-push` hook that calls it. A refusal to measure exits `1` and is logged as a skip rather than a failure, as does a corpus with no file left to check once `CHANGELOG.md` is set aside. The stage needs `canon` on PATH. `canon docs projects` names the install command. Without it, the stage is skipped and logged, and coverage falls back to the three checks above.
96
+ `scripts/verify.ts`'s `Markdown bans` stage is the exception: it gates rather than repairs. It runs `canon markdown audit` over every tracked markdown file except `CHANGELOG.md`, since a generated changelog carries commit subjects nobody wrote against the ban set, and exits `2` on a finding or `3` on a shipped-empty ban set, failing `bun run check` and the `pre-push` hook that calls it. A refusal to measure exits `1` and is logged as a skip rather than a failure, as does a corpus with no file left to check once `CHANGELOG.md` is set aside. The stage needs `canon` on PATH. `canon docs projects` names the install command. Without it, the stage is skipped and logged, and coverage falls back to the two checks above.
@@ -28,8 +28,5 @@ jobs:
28
28
  - name: Check Formatting
29
29
  run: bun run check:format
30
30
 
31
- - name: Check Spelling
32
- run: bun run check:spell
33
-
34
31
  - name: Check Shell
35
32
  run: bun run check:shell
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "**/*.{json,md,mdc}": [
3
- "prettier --write --ignore-path .gitignore --ignore-path .prettierignore",
4
- "cspell --no-must-find-files"
3
+ "prettier --write --ignore-path .gitignore --ignore-path .prettierignore"
5
4
  ],
6
5
  "**/*.md": ["canon indexes regen"],
7
6
  "**/*.sh": ["shfmt --write --indent 2", "shellcheck --severity=warning"]
@@ -25,7 +25,6 @@ Defined in `.github/workflows/verify.yml`. All jobs must pass before merge.
25
25
  | Check | Command | What it asserts |
26
26
  | ------ | ---------------------- | ---------------------------------- |
27
27
  | Format | `bun run check:format` | prettier and shfmt are clean |
28
- | Spell | `bun run check:spell` | cspell passes against dictionaries |
29
28
  | Shell | `bun run check:shell` | shellcheck passes at warning level |
30
29
 
31
30
  ## Running CI locally
@@ -32,6 +32,6 @@ All `.sh` files live under `scripts/`. Do not place shell scripts outside `scrip
32
32
 
33
33
  ## Husky hooks
34
34
 
35
- - `pre-commit` runs `lint-staged` (prettier, cspell, shfmt, shellcheck on staged files).
35
+ - `pre-commit` runs `lint-staged` (prettier, shfmt, shellcheck on staged files).
36
36
  - `commit-msg` runs `commitlint` against the conventional commit format.
37
37
  - `pre-push` runs `bun run check`. When a markdown-bans audit tool is on PATH, it also gates on banned characters across every tracked markdown file except `CHANGELOG.md`. After pushing, run `git status`. If files changed, commit the diff as `style(<scope>):` and push again.
@@ -42,7 +42,7 @@ A project installed before the board became a folder still holds `.claude/TASKS.
42
42
 
43
43
  Convert by hand, once per project:
44
44
 
45
- 1. Split each `###` block into `.canon/tasks/vXX.Y-<slug>.md`, adding the `title` and `description` frontmatter the tasks standard requires, which `canon standards tasks` prints. Compare each new file against the block it came from rather than checking that every block landed somewhere.
45
+ 1. Split each `###` block into `.canon/tasks/vXX.Y-<slug>.md`, adding the `title` and `description` frontmatter the tasks reference in the `task-board` skill requires. Compare each new file against the block it came from rather than checking that every block landed somewhere.
46
46
  2. Run `canon indexes regen --no-stage --root . .canon/tasks/<any-task>.md` to build the catalog.
47
47
  3. Delete `.claude/TASKS.md`, and swap its `.gitignore` entry for `.canon/tasks/`.
48
48
 
@@ -50,7 +50,19 @@ END {
50
50
  while (i <= n && substr(s, i, 1) != "\n") i++
51
51
  continue
52
52
  }
53
- if (c == "<" && substr(s, i, 2) == "<<" && substr(s, i, 3) != "<<<") {
53
+ if (c == "(" && substr(s, i, 2) == "((") {
54
+ d = 0
55
+ while (i <= n) {
56
+ a = substr(s, i, 1)
57
+ if (a == "(") d++
58
+ else if (a == ")") { d--; if (d == 0) { seg = seg a; i++; break } }
59
+ seg = seg a; i++
60
+ }
61
+ continue
62
+ }
63
+ if (c == "<" && substr(s, i, 3) == "<<<") { seg = seg "<<<"; i += 3; continue }
64
+ if (c == "<" && substr(s, i, 2) == "<<" && seg ~ /^[ \t]*let[ \t]/) { seg = seg "<<"; i += 2; continue }
65
+ if (c == "<" && substr(s, i, 2) == "<<") {
54
66
  j = i + 2; dash = 0
55
67
  if (substr(s, j, 1) == "-") { dash = 1; j++ }
56
68
  while (substr(s, j, 1) ~ /[ \t]/) j++
@@ -10,7 +10,7 @@ The go stack covers Go modules linted and formatted by `golangci-lint` v2, type-
10
10
 
11
11
  1. Scaffold with `go mod init <module-path>`. The module path is the project's own identity, so this stack never ships a `go.mod`.
12
12
  2. Keep `./...` out of `node_modules`: `go mod edit -ignore=./node_modules`. At a root sync the base layer installs `node_modules/` inside the module, and a package there can ship a `.go` file that `go vet`, `go test`, and `golangci-lint` would otherwise walk. The directive needs Go 1.25 or later.
13
- 3. Seed `package.json` so the base layer's bun-side tools (husky, prettier, cspell, commitlint) have a target to install into: `bun init -y`.
13
+ 3. Seed `package.json` so the base layer's bun-side tools (husky, prettier, commitlint) have a target to install into: `bun init -y`.
14
14
  4. Install base tooling: `canon tooling sync base . --write`
15
15
  5. Install go tooling: `canon tooling sync go . --write`
16
16
  6. Install the linter: `go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest`. The manifest declares no `[dependencies.dev]`, because manifest injection runs `bun add -D`, which cannot install a Go tool. This step stays manual.
@@ -9,7 +9,7 @@ The php stack covers Composer projects on PHP 8.3 or later, formatted by PHP CS
9
9
  ## Scaffold checklist
10
10
 
11
11
  1. Scaffold with `composer init -n --name <vendor>/<name> --autoload src/`. The package name and namespace are the project's own identity, so this stack never ships a `composer.json`.
12
- 2. Seed `package.json` so the base layer's bun-side tools (husky, prettier, cspell, commitlint) have a target to install into: `bun init -y`.
12
+ 2. Seed `package.json` so the base layer's bun-side tools (husky, prettier, commitlint) have a target to install into: `bun init -y`.
13
13
  3. Install base tooling: `canon tooling sync base . --write`
14
14
  4. Install php tooling: `canon tooling sync php . --write`
15
15
  5. Install the dev tools: `composer require --dev friendsofphp/php-cs-fixer phpstan/phpstan phpunit/phpunit`. The manifest declares no `[dependencies.dev]`, because manifest injection runs `bun add -D`, which cannot install a Composer package. This step stays manual.
@@ -11,7 +11,7 @@ Configs ship as sidecar files (`ruff.toml`, `mypy.ini`, `pytest.ini`, `.coverage
11
11
  ## Scaffold checklist
12
12
 
13
13
  1. Scaffold with `uv init --package <name>`. This creates `pyproject.toml` with a `uv_build` backend, `.python-version` pinned to 3.14, and `src/<name with underscores>/__init__.py` holding an annotated `def main() -> None:`, so source lands under `src/` where `mypy.ini`, `pytest.ini`, and `.coveragerc` look for it. `uv init` defaults `requires-python` to `>=3.14`, which matches the `.python-version` pin this stack ships.
14
- 2. Seed `package.json` so the base layer's bun-side tools (husky, prettier, cspell, commitlint) have a target to install into: `bun init -y`. Without this the sync drops base configs but skips the dep install, since `resolve_missing_deps` short-circuits when `package.json` is absent.
14
+ 2. Seed `package.json` so the base layer's bun-side tools (husky, prettier, commitlint) have a target to install into: `bun init -y`. Without this the sync drops base configs but skips the dep install, since `resolve_missing_deps` short-circuits when `package.json` is absent.
15
15
  3. Install base tooling: `canon tooling sync base . --write`
16
16
  4. Install python tooling: `canon tooling sync python . --write`
17
17
  5. Install Python tooling deps: `uv add --dev ruff mypy pytest pytest-cov`. v1 of this stack does not declare these in `[dependencies.dev]` because manifest injection hardcodes `bun add -D`, which can not install Python packages. Until the injector branches on `runtime`, this step is manual.
@@ -31,7 +31,7 @@ The python stack also ships `tests/test_smoke.py` as a copy-once seed. `pytest`
31
31
 
32
32
  ## Hybrid project shape
33
33
 
34
- A python project synced with this stack ends up with both `package.json` from base and `pyproject.toml` from `uv init`. The base layer brings prettier, cspell, commitlint, and husky for non-Python files. Both `node_modules/` and `.venv/` coexist at the project root. The hybrid shape gives Python projects access to the toolkit's cross-cutting tools without forking the base stack.
34
+ A python project synced with this stack ends up with both `package.json` from base and `pyproject.toml` from `uv init`. The base layer brings prettier, commitlint, and husky for non-Python files. Both `node_modules/` and `.venv/` coexist at the project root. The hybrid shape gives Python projects access to the toolkit's cross-cutting tools without forking the base stack.
35
35
 
36
36
  ## Dependencies
37
37
 
@@ -42,9 +42,6 @@ jobs:
42
42
  - name: Check Formatting
43
43
  run: bun run check:format
44
44
 
45
- - name: Check Spelling
46
- run: bun run check:spell
47
-
48
45
  - name: Check Shell
49
46
  run: bun run check:shell
50
47
 
@@ -59,4 +59,3 @@ packages = [
59
59
  "# Coverage" = ["coverage/"]
60
60
  "# Playwright" = ["test-results/", "playwright-report/", "blob-report/", "playwright/.cache/"]
61
61
  "# Screenshots" = ["screenshots/"]
62
- "# VSCode" = [".vscode/*", "!.vscode/extensions.json", "!.vscode/settings.json"]
@@ -17,7 +17,6 @@ Golden config files live in `tooling/web/configs/` and are copied into the targe
17
17
  - `scripts/lib/preview-server.sh`: sourced, never executed. Builds, serves on `PREVIEW_PORT`, blocks until the port answers, and traps the whole process group on exit, including the detached grandchild `lsof` finds. Both capture scripts source it so the startup lives once.
18
18
  - `scripts/readme-screenshot.sh`: captures one case under `SCREENSHOT_FILTER` and copies its light and dark frames to `assets/evidence/readme/`. `README_FILTER` ships empty with `header/1280` as its example, and the script exits 1 until a project fills it, since a shipped default would capture the wrong part of the page in every project but one. Its copy reads from `screenshots/<hostname>/`, which is where the harness writes.
19
19
  - `.github/workflows/readme-screenshot.yml`: refreshes that frame on a pull request and commits it back to the branch. The path list ships as `REPLACE_WITH_PROJECT_PATHS`, and the job's first step after checkout exits 1 while it stays, since a job that never fires leaves no run and no diff to notice. The workflow's own path is in the list so the installing pull request reaches that step. When a commit-back is right and when a report is, `governance/rules/tooling/700-ci-workflow.md` states the test.
20
- - `.vscode/extensions.json` and `.vscode/settings.json`: editor wiring for ESLint, Tailwind, Playwright, Vitest.
21
20
  - `.github/workflows/verify.yml`: `static-checks`, `unit-tests`, `build-verify`, and `e2e-tests` jobs.
22
21
  - `scripts/verify.json`: the phases base's verify runner requires here, being typecheck, lint, unit tests, and build. `## Verify script` covers the order and the card exclusion stage.
23
22
  - `scripts/worktree-port.sh`: prints a base port plus this working directory's offset. Called with no argument it prints the offset alone. It refuses a folder left under the worktrees directory after its worktree was removed, rather than printing a port for it.
@@ -78,9 +77,8 @@ Sticky negative knowledge. Do not relearn.
78
77
 
79
78
  ## lint-staged (extend)
80
79
 
81
- - Add `**/*.{js,jsx,ts,tsx}` glob running `eslint --fix --max-warnings 0`, `prettier --write`, `cspell --no-must-find-files`.
80
+ - Add `**/*.{js,jsx,ts,tsx}` glob running `eslint --fix --max-warnings 0`, and `prettier --write`.
82
81
  - Extend the prettier glob to include `css`: `**/*.{json,css,md,mdc}`.
83
- - Each file type runs cspell once via its own glob. No standalone cspell glob.
84
82
 
85
83
  ## CI docs (extend)
86
84
 
@@ -128,6 +126,6 @@ The sweep under `screenshots/` is ignored again, and only a flagged case's `evid
128
126
 
129
127
  ## Verify script
130
128
 
131
- The web layer's `scripts/verify.json` requires `typecheck`, `lint`, `test:run`, and `build`, and base's `scripts/verify.ts` runs them in its fixed order: typecheck, lint, format, spelling, shell, unit tests, build, markdown bans, then the card exclusion check where one is installed. astro, vite-react, and nextjs inherit the list, since the nearest stack's copy wins. An adapter whose phases differ ships its own `verify.json` rather than a script.
129
+ The web layer's `scripts/verify.json` requires `typecheck`, `lint`, `test:run`, and `build`, and base's `scripts/verify.ts` runs them in its fixed order: typecheck, lint, format, shell, unit tests, build, markdown bans, then the card exclusion check where one is installed. astro, vite-react, and nextjs inherit the list, since the nearest stack's copy wins. An adapter whose phases differ ships its own `verify.json` rather than a script.
132
130
 
133
131
  The card stage runs `scripts/check-card-exclusion.sh` whenever that file is installed, which keeps the social card route's build exclusion on the default path rather than behind a flag. It tests for the script rather than the stack, so a project with no card route runs one less stage with no override, and the stack shipping a card route owns what the check reads, being the build the stage before it produced. The astro reference carries the route itself.
@@ -1,40 +0,0 @@
1
- ---
2
- title: Standard success criteria
3
- description: Reading the corpus against the success-criterion gate, the two headings it accepts, why the check scopes to arrival rather than the whole corpus, and the exit codes it sets
4
- ---
5
-
6
- # Standard success criteria
7
-
8
- `canon standards audit` reads the corpus at `standards/` and reports which files state a success criterion against which do not, per `standards/standard.md`. It fails only on a standard new to the current branch, never on one already short the section.
9
-
10
- Two headings count, at any casing. `## Success criterion` is the wording that standard states the rule under, and `## What a working <document type> looks like` is the wording its template prescribes for the section itself. Most of the corpus writes the templated form, so accepting the rule's wording alone would refuse a conforming standard.
11
-
12
- ```bash
13
- canon standards audit
14
- canon standards audit --json
15
- canon standards audit --arrivals-only
16
- ```
17
-
18
- | Option | Behavior |
19
- | ----------------- | ------------------------------------------------------------ |
20
- | `[path]` | Project root, defaulting to the current directory |
21
- | `--json` | Add a machine-readable record on stdout, keeping the frame |
22
- | `--arrivals-only` | Run the gating check alone, printing nothing on a clean pass |
23
-
24
- ## Why arrival rather than the corpus
25
-
26
- `standards/standard.md` states that a criterion is added to an existing standard when that standard is next exercised, not in a sweep: a criterion written with no failure to point at is the taste-based edit the rule exists to prevent. Gating the whole corpus would fail every push until every standard already short the section was closed at once, which is the sweep that rule forbids. The check reads the whole corpus and fails only on a file present in the working tree and absent at the branch's merge base, treating a rename into the corpus the same as a standard authored fresh.
27
-
28
- ## Exit codes and refusals
29
-
30
- | Code | Meaning |
31
- | ---- | ---------------------------------------------------------------------- |
32
- | `0` | every arriving standard carries the section |
33
- | `1` | refused, with `reason` naming the cause |
34
- | `2` | a standard new to this branch states no criterion under either heading |
35
-
36
- A project authoring no standards refuses with `no-corpus`, the ordinary state of most targets, the same absence `canon claude skills audit` reads as its own `no-corpus`.
37
-
38
- ## What it does not measure
39
-
40
- Presence of the heading is the whole check. The section's content, the questions it must answer or the task it must let a reader complete, is a judgment `canon standards audit` cannot read, so a standard carrying an empty or token section still passes.