@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/tooling/inject.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
package/src/tooling/read.ts
CHANGED
|
@@ -28,9 +28,8 @@ function toolingSourceDir(root: string): string {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
* The roots a stack reference resolves against
|
|
32
|
-
*
|
|
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.
|
package/src/tooling/scan.ts
CHANGED
|
@@ -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
|
-
*
|
|
78
|
-
*
|
|
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) : []
|
package/src/tooling/subfolder.ts
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
}
|
|
@@ -20,7 +20,6 @@ packages = [
|
|
|
20
20
|
"preview" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) && export WORKTREE_PORT_OFFSET && astro preview"
|
|
21
21
|
"astro" = "astro"
|
|
22
22
|
"typecheck" = "astro check"
|
|
23
|
-
"setup" = "./scripts/setup.sh"
|
|
24
23
|
"card:dev" = "WORKTREE_PORT_OFFSET=$(bash scripts/worktree-port.sh) && export WORKTREE_PORT_OFFSET && astro dev --config card.config.mjs"
|
|
25
24
|
"card:check" = "bash scripts/check-card-exclusion.sh"
|
|
26
25
|
|
|
@@ -34,14 +34,6 @@ Unit-test React island components (`.tsx`) only. Do not test `.astro` files. Pag
|
|
|
34
34
|
|
|
35
35
|
Vitest is pinned to `^3` in the web manifest. Vitest `^4` bundles rolldown-vite (vite@8), which ignores the esbuild JSX config in `@vitejs/plugin-react` and breaks `.tsx` tests, plus emits deprecation warnings. Unpin once `@astrojs/react` ships a rolldown-aware plugin (or astro's bundled vite reaches 8).
|
|
36
36
|
|
|
37
|
-
## Setup script
|
|
38
|
-
|
|
39
|
-
- File: `scripts/setup.sh`. Destructive: deletes `.git` and self-removes after running. Run once immediately after scaffolding.
|
|
40
|
-
- Prompt for project name, normalize to kebab-case, derive title-cased display name.
|
|
41
|
-
- Update `package.json` name and version, inject verify/clean/update scripts, remove setup.
|
|
42
|
-
- Update `astro.config.mjs`: set `site` if provided.
|
|
43
|
-
- Update `<title>` and `<meta name="description">` in the default layout.
|
|
44
|
-
|
|
45
37
|
## Prettier (extend)
|
|
46
38
|
|
|
47
39
|
Add `prettier-plugin-astro` first in plugins, then `prettier-plugin-tailwindcss` last (per its docs). Add a parser override for `.astro` files.
|
|
@@ -27,8 +27,12 @@ interface Phase {
|
|
|
27
27
|
readonly title: string
|
|
28
28
|
readonly failed: string
|
|
29
29
|
readonly passed: string
|
|
30
|
+
readonly tools?: readonly Tool[]
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
const SHELL_TOOLS_HINT =
|
|
34
|
+
"Install it with 'sudo apt-get install shfmt shellcheck' or 'brew install shfmt shellcheck'"
|
|
35
|
+
|
|
32
36
|
const PHASES: readonly Phase[] = [
|
|
33
37
|
{
|
|
34
38
|
script: 'typecheck',
|
|
@@ -54,17 +58,15 @@ const PHASES: readonly Phase[] = [
|
|
|
54
58
|
failed: 'Format check failed',
|
|
55
59
|
passed: 'Format check passed',
|
|
56
60
|
},
|
|
57
|
-
{
|
|
58
|
-
script: 'check:spell',
|
|
59
|
-
title: 'Spelling',
|
|
60
|
-
failed: 'Spell check failed',
|
|
61
|
-
passed: 'Spell check passed',
|
|
62
|
-
},
|
|
63
61
|
{
|
|
64
62
|
script: 'check:shell',
|
|
65
63
|
title: 'Shell',
|
|
66
64
|
failed: 'Shell check failed',
|
|
67
65
|
passed: 'Shell check passed',
|
|
66
|
+
tools: [
|
|
67
|
+
{ name: 'shfmt', hint: SHELL_TOOLS_HINT },
|
|
68
|
+
{ name: 'shellcheck', hint: SHELL_TOOLS_HINT },
|
|
69
|
+
],
|
|
68
70
|
},
|
|
69
71
|
{
|
|
70
72
|
script: 'test:run',
|
|
@@ -83,12 +85,11 @@ const PHASES: readonly Phase[] = [
|
|
|
83
85
|
// A subfolder synced with --skip base declares none of these, since the
|
|
84
86
|
// repository root owns them, so a list that does not require one runs it only
|
|
85
87
|
// where the folder declares it.
|
|
86
|
-
const BASE_PHASES = new Set([
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
])
|
|
88
|
+
const BASE_PHASES = new Set(['format', 'check:format'])
|
|
89
|
+
|
|
90
|
+
// An add-on stack declares these, keyed to the stack that ships each, so a
|
|
91
|
+
// folder that never synced the add-on skips the phase rather than failing it.
|
|
92
|
+
const ADD_ON_PHASES = new Map([['check:shell', 'shell']])
|
|
92
93
|
|
|
93
94
|
const PHASE_LIST = 'scripts/verify.json'
|
|
94
95
|
const CARD_EXCLUSION = 'scripts/check-card-exclusion.sh'
|
|
@@ -237,8 +238,8 @@ function readDeclaredScripts(): Set<string> {
|
|
|
237
238
|
)
|
|
238
239
|
}
|
|
239
240
|
|
|
240
|
-
function checkTools(
|
|
241
|
-
for (const tool of
|
|
241
|
+
function checkTools(tools: readonly Tool[]): void {
|
|
242
|
+
for (const tool of tools) {
|
|
242
243
|
if (onPath(tool.name)) continue
|
|
243
244
|
fail(
|
|
244
245
|
tool.hint === undefined
|
|
@@ -261,7 +262,8 @@ function runPhase(
|
|
|
261
262
|
declared: Set<string>,
|
|
262
263
|
): void {
|
|
263
264
|
const isBase = BASE_PHASES.has(phase.script)
|
|
264
|
-
|
|
265
|
+
const addOn = ADD_ON_PHASES.get(phase.script)
|
|
266
|
+
if (!required && !isBase && addOn === undefined) return
|
|
265
267
|
|
|
266
268
|
logStep(phase.title)
|
|
267
269
|
if (!declared.has(phase.script)) {
|
|
@@ -271,10 +273,13 @@ function runPhase(
|
|
|
271
273
|
)
|
|
272
274
|
}
|
|
273
275
|
logSkip(
|
|
274
|
-
|
|
276
|
+
addOn === undefined
|
|
277
|
+
? `Skipped: ${phase.script} is not declared here, so the repository root runs it`
|
|
278
|
+
: `Skipped: ${phase.script} is not declared here. Run 'canon tooling sync ${addOn} . --write' to add it`,
|
|
275
279
|
)
|
|
276
280
|
return
|
|
277
281
|
}
|
|
282
|
+
checkTools(phase.tools ?? [])
|
|
278
283
|
runCheck(`bun run ${phase.script}`, phase.failed)
|
|
279
284
|
logInfo(phase.passed)
|
|
280
285
|
}
|
|
@@ -343,7 +348,7 @@ function checkMarkdownBans(): void {
|
|
|
343
348
|
function main(): void {
|
|
344
349
|
const declared = readDeclaredScripts()
|
|
345
350
|
const list = readPhaseList()
|
|
346
|
-
checkTools(list)
|
|
351
|
+
checkTools(list.tools)
|
|
347
352
|
checkPreconditions(list)
|
|
348
353
|
|
|
349
354
|
if (!isNested) print(`${GREY}┌${NC}`)
|
|
@@ -6,26 +6,26 @@ scaffold = ""
|
|
|
6
6
|
|
|
7
7
|
[sync]
|
|
8
8
|
source = "configs"
|
|
9
|
-
per_root = ["scripts/verify.
|
|
9
|
+
per_root = ["scripts/verify.ts", "scripts/verify.json"]
|
|
10
10
|
|
|
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:
|
|
22
|
-
"
|
|
23
|
-
"check:shell" = "find scripts -name '*.sh' -exec shellcheck --severity=warning {} +"
|
|
24
|
-
"format" = "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && find scripts -name '*.sh' -exec shfmt --write --indent 2 {} +"
|
|
20
|
+
"check:format" = "prettier --check --log-level warn --ignore-path .gitignore --ignore-path .prettierignore ."
|
|
21
|
+
"format" = "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore ."
|
|
25
22
|
"prepare" = "husky"
|
|
26
|
-
|
|
23
|
+
|
|
24
|
+
[scripts.override]
|
|
25
|
+
"check" = "bun scripts/verify.ts"
|
|
27
26
|
|
|
28
27
|
[gitignore]
|
|
29
28
|
"# System" = [".DS_Store"]
|
|
30
29
|
"# Dependencies" = ["node_modules/"]
|
|
31
30
|
"# Secrets" = [".env", ".env.*", "*.local", "!.env.example"]
|
|
31
|
+
"# 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,
|
|
5
|
+
The base layer covers every project the toolkit scaffolds, whatever language sits on top. It ships formatting, conventional commits, git hooks, CI, and the verify runner. Every other stack extends it, so a decision made here is one every stack inherits. Shell formatting and linting live in the `shell` add-on stack, which a project syncs once it writes shell.
|
|
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
|
|
|
@@ -11,21 +11,15 @@ A repository with several language roots layers this stack once at the root and
|
|
|
11
11
|
Golden config files live in `tooling/base/configs/` and are copied into the target on `canon tooling sync base . --write`, replacing whatever sits at those paths. They are the source of truth. The reference covers rationale and tradeoffs. Configs show the concrete setup.
|
|
12
12
|
|
|
13
13
|
- `.prettierrc`: `semi: false`, `singleQuote: true`, plus a parser override per non-standard extension (`.mdx` to `markdown`).
|
|
14
|
-
- `.shellcheckrc`: `external-sources=true`. Required for shellcheck to follow `source` directives.
|
|
15
|
-
- `.editorconfig`: `root = true`, with an `[*.sh]` block setting `indent_style = space` and `indent_size = 2`.
|
|
16
14
|
- `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
15
|
- `.husky/`: `pre-commit`, `commit-msg`, `pre-push`, `post-merge`, `post-rewrite`.
|
|
18
|
-
-
|
|
19
|
-
- `scripts/verify.
|
|
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.
|
|
16
|
+
- `scripts/verify.ts`: the one verify runner every stack shares, and the command behind `check`. 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.
|
|
17
|
+
- `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 two phases, so a base-only target still fails on a missing one. The shell phase runs only where `package.json` declares `check:shell`, which the `shell` stack adds, and probes for `shfmt` and `shellcheck` first. These two files are base's `[sync] per_root` set, which a `--skip base` subfolder still receives.
|
|
22
18
|
|
|
23
19
|
## What ships as user-owned seeds
|
|
24
20
|
|
|
25
21
|
Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and never overwrites it, so a project extends them freely.
|
|
26
22
|
|
|
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
23
|
- `.lintstagedrc`: the glob map below.
|
|
30
24
|
- `.prettierignore`: created empty. Projects add their own entries.
|
|
31
25
|
- `.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.
|
|
@@ -35,16 +29,13 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
|
|
|
35
29
|
## Tool pairing
|
|
36
30
|
|
|
37
31
|
- Runtime: `bun` as package manager and script runner, `bunx` over `npx` for a one-off executable.
|
|
38
|
-
- Formatting: Prettier for what it parses
|
|
39
|
-
- Spelling: cspell over the whole tree, with project vocabulary split into a project-terms dictionary and a tech-stack one.
|
|
40
|
-
- 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.
|
|
32
|
+
- Formatting: Prettier for what it parses. Prettier has no shell parser, so shell formatting is the `shell` stack's.
|
|
41
33
|
- 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`, `
|
|
34
|
+
- Dev dependencies: `prettier`, `husky`, `@commitlint/cli`, `@commitlint/config-conventional`. Install via `bun add -D`.
|
|
43
35
|
|
|
44
36
|
## File layout
|
|
45
37
|
|
|
46
|
-
-
|
|
47
|
-
- Dictionaries live in `.cspell/`, hooks in `.husky/`, seeded context docs in `canon/context/`.
|
|
38
|
+
- Hooks live in `.husky/`, seeded context docs in `canon/context/`.
|
|
48
39
|
- 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
40
|
|
|
50
41
|
## Hooks
|
|
@@ -59,13 +50,12 @@ Seeds live in `tooling/base/seeds/`. Sync drops each once on first install and n
|
|
|
59
50
|
|
|
60
51
|
Seeded baseline globs:
|
|
61
52
|
|
|
62
|
-
- `**/*.{json,md,mdc}` runs `prettier --write --ignore-path .gitignore --ignore-path .prettierignore`
|
|
53
|
+
- `**/*.{json,md,mdc}` runs `prettier --write --ignore-path .gitignore --ignore-path .prettierignore`
|
|
63
54
|
- `**/*.md` runs `canon indexes regen`
|
|
64
|
-
- `**/*.sh` runs `shfmt --write --indent 2` then `shellcheck --severity=warning`
|
|
65
55
|
|
|
66
56
|
## CI
|
|
67
57
|
|
|
68
|
-
- Steps: checkout, setup Bun at latest, `bun install --frozen-lockfile`,
|
|
58
|
+
- Steps: checkout, setup Bun at latest, `bun install --frozen-lockfile`, then `check:format`.
|
|
69
59
|
- CI asserts and never writes. Format must be clean before push.
|
|
70
60
|
|
|
71
61
|
## Gitignore
|
|
@@ -73,30 +63,27 @@ Seeded baseline globs:
|
|
|
73
63
|
- `# System`: `.DS_Store`
|
|
74
64
|
- `# Dependencies`: `node_modules/`
|
|
75
65
|
- `# Secrets`: `.env`, `.env.*`, `*.local`, `!.env.example`
|
|
66
|
+
- `# VSCode`: `.vscode/`
|
|
76
67
|
|
|
77
68
|
## Anti-patterns
|
|
78
69
|
|
|
79
70
|
Sticky negative knowledge. Do not relearn.
|
|
80
71
|
|
|
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
72
|
- 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
73
|
- Do NOT omit `--log-level warn` from a prettier invocation. The default prints a line per unchanged file.
|
|
85
74
|
- 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.
|
|
86
|
-
- Do NOT drop the `[*.sh]` block from `.editorconfig` because shfmt already sets indentation. The editor and shfmt then disagree and produce spurious git diffs.
|
|
87
|
-
- Do NOT copy lint-staged's `**/*.sh` glob into a package.json script. lint-staged expands its own globs and passes matched files as arguments, which a bare shell script does not.
|
|
88
75
|
|
|
89
76
|
## CLI
|
|
90
77
|
|
|
91
|
-
| Script | What it does
|
|
92
|
-
| ---------------------- |
|
|
93
|
-
| `bun run check` | Repairs a checkout
|
|
94
|
-
| `bun run check:format` | Asserts prettier
|
|
95
|
-
| `bun run
|
|
96
|
-
| `bun run
|
|
97
|
-
| `bun run format` | Writes prettier and shfmt formatting in place |
|
|
98
|
-
| `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
|
|
78
|
+
| Script | What it does |
|
|
79
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `bun run check` | Repairs a checkout through `scripts/verify.ts`. Honors `VERIFY_NESTED=true` to suppress timeline boundaries when another script calls it. |
|
|
81
|
+
| `bun run check:format` | Asserts prettier formatting without writing |
|
|
82
|
+
| `bun run format` | Writes prettier formatting in place |
|
|
83
|
+
| `bun run prepare` | Initializes husky hooks, run automatically on `bun install` |
|
|
99
84
|
|
|
100
|
-
|
|
85
|
+
Base writes `check` as a `[scripts.override]`, so a sync replaces whatever `check` a target declared, including one that chained further checks after the runner. Re-add a chained check as its own script after a sync.
|
|
101
86
|
|
|
102
|
-
`
|
|
87
|
+
`bun run check` repairs a checkout rather than gating one. It runs `format` first to auto-fix drifted code, then asserts only what the formatter could not fix. The pull request workflow calls `check:format` directly, and that is the gate.
|
|
88
|
+
|
|
89
|
+
`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 format check above.
|
|
@@ -22,14 +22,5 @@ jobs:
|
|
|
22
22
|
- name: Install Dependencies
|
|
23
23
|
run: bun install --frozen-lockfile
|
|
24
24
|
|
|
25
|
-
- name: Install Shell Tools
|
|
26
|
-
run: sudo apt-get install -y shfmt shellcheck
|
|
27
|
-
|
|
28
25
|
- name: Check Formatting
|
|
29
26
|
run: bun run check:format
|
|
30
|
-
|
|
31
|
-
- name: Check Spelling
|
|
32
|
-
run: bun run check:spell
|
|
33
|
-
|
|
34
|
-
- name: Check Shell
|
|
35
|
-
run: bun run check:shell
|
|
@@ -1,8 +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
|
-
"**/*.md": ["canon indexes regen"]
|
|
7
|
-
"**/*.sh": ["shfmt --write --indent 2", "shellcheck --severity=warning"]
|
|
5
|
+
"**/*.md": ["canon indexes regen"]
|
|
8
6
|
}
|
|
@@ -22,12 +22,10 @@ Owns the GitHub Actions workflow that gates a merge: which events start a run, a
|
|
|
22
22
|
|
|
23
23
|
Defined in `.github/workflows/verify.yml`. All jobs must pass before merge.
|
|
24
24
|
|
|
25
|
-
| Check | Command | What it asserts
|
|
26
|
-
| ------ | ---------------------- |
|
|
27
|
-
| Format | `bun run check:format` | prettier
|
|
28
|
-
| Spell | `bun run check:spell` | cspell passes against dictionaries |
|
|
29
|
-
| Shell | `bun run check:shell` | shellcheck passes at warning level |
|
|
25
|
+
| Check | Command | What it asserts |
|
|
26
|
+
| ------ | ---------------------- | ----------------- |
|
|
27
|
+
| Format | `bun run check:format` | prettier is clean |
|
|
30
28
|
|
|
31
29
|
## Running CI locally
|
|
32
30
|
|
|
33
|
-
`bun run check` runs the same
|
|
31
|
+
`bun run check` runs the same assert plus auto-formats first. If CI fails on format, run `bun run check` locally and commit the diff.
|
|
@@ -24,7 +24,7 @@ Owns how the project runs on a developer machine: installing the toolchain, the
|
|
|
24
24
|
| Command | Purpose |
|
|
25
25
|
| ---------------- | ------------------------------------------------------------------------------ |
|
|
26
26
|
| `bun run check` | Repairs locally. Auto-formats, then asserts what the formatters could not fix. |
|
|
27
|
-
| `bun run format` | Auto-fix prettier
|
|
27
|
+
| `bun run format` | Auto-fix prettier formatting. |
|
|
28
28
|
|
|
29
29
|
## Shell scripts
|
|
30
30
|
|
|
@@ -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
|
|
35
|
+
- `pre-commit` runs `lint-staged` (prettier 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
|
|
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 == "
|
|
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++
|
|
@@ -57,5 +57,5 @@ marker="$marker_dir/$key"
|
|
|
57
57
|
mkdir -p "$marker_dir"
|
|
58
58
|
: >"$marker"
|
|
59
59
|
|
|
60
|
-
msg='Temporary file write outside the project scratch folder. Write temp files to .canon/tmp/<slug>/ in the project root, not system temp. See the Scratch rule (
|
|
60
|
+
msg='Temporary file write outside the project scratch folder. Write temp files to .canon/tmp/<slug>/ in the project root, not system temp. See the Scratch rule (scratch.md, under canon/ in your installed governance rules).'
|
|
61
61
|
jq -nc --arg msg "$msg" '{hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:$msg}}'
|