@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.
- package/README.md +3 -5
- 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/SKILL.md +10 -11
- 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/draft-and-pick/SKILL.md +1 -1
- 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/review-address/REQUIREMENT.md +1 -1
- package/claude/skills/review-address/SKILL.md +2 -2
- 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/docs/agents/audits.md +1 -1
- package/docs/agents/commands.md +2 -4
- package/docs/agents/counts.md +5 -6
- package/docs/agents/drift-surfaces.md +15 -19
- package/docs/agents/index.md +2 -3
- package/docs/agents/install-and-sync.md +7 -31
- package/docs/agents/markdown-audit.md +10 -12
- package/docs/agents/migrate.md +1 -1
- 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 +11 -14
- 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 +9 -9
- 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/operating-model.md +16 -18
- package/docs/workflow/skill-map.md +1 -1
- package/governance/detect.toml +0 -4
- package/governance/renames.toml +0 -1
- 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 +2 -3
- package/src/audits/catalog.ts +0 -9
- package/src/claude/routing.ts +1 -1
- package/src/claude/skills-reach.ts +4 -6
- package/src/cli/help.ts +3 -4
- package/src/cli.ts +0 -2
- package/src/commands/gov.ts +5 -9
- package/src/commands/init.ts +1 -1
- package/src/commands/labels.ts +4 -44
- package/src/commands/migrate.ts +1 -1
- package/src/commands/records.ts +4 -20
- package/src/commands/tooling.ts +2 -4
- package/src/counts/catalogs.ts +0 -6
- package/src/gate/boundaries.ts +2 -2
- package/src/gate/measures.ts +6 -28
- package/src/gate/sequencer.ts +51 -2
- package/src/gate/stages.ts +0 -18
- package/src/gov/citations.ts +11 -43
- package/src/markdown/bans.ts +1 -1
- 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 +29 -80
- package/src/sync/check.ts +18 -14
- 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/base/configs/scripts/verify.json +1 -1
- package/tooling/base/configs/scripts/verify.ts +1 -12
- package/tooling/base/manifest.toml +1 -2
- package/tooling/base/reference.md +10 -16
- package/tooling/base/seeds/.github/workflows/verify.yml +0 -3
- package/tooling/base/seeds/.lintstagedrc +1 -2
- package/tooling/base/seeds/canon/context/ci.md +0 -1
- package/tooling/base/seeds/canon/context/development.md +1 -1
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/.claude/hooks/review-post-scan.sh +13 -1
- package/tooling/go/reference.md +1 -1
- package/tooling/php/reference.md +1 -1
- package/tooling/python/reference.md +2 -2
- package/tooling/web/configs/.github/workflows/verify.yml +0 -3
- package/tooling/web/manifest.toml +0 -1
- package/tooling/web/reference.md +2 -4
- 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/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
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
extends = "base"
|
|
2
|
-
rules = ["code", "100-typescript"]
|
|
2
|
+
rules = ["code", "100-typescript", "300-testing-ts"]
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
extends = "base"
|
|
2
|
-
rules = ["code", "110-python", "
|
|
2
|
+
rules = ["code", "110-python", "330-testing-py"]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erclx/canon",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.35.0",
|
|
5
5
|
"description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
12
|
"scripts",
|
|
13
|
-
"standards",
|
|
14
13
|
"governance",
|
|
15
14
|
"tooling",
|
|
16
15
|
"claude",
|
|
@@ -25,7 +24,6 @@
|
|
|
25
24
|
"access": "public"
|
|
26
25
|
},
|
|
27
26
|
"scripts": {
|
|
28
|
-
"check:spell": "cspell '**' '.*/**' '.*' --no-progress --color --show-context",
|
|
29
27
|
"format": "prettier --write --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && shfmt --write --indent 2 scripts/ tooling/ claude/",
|
|
30
28
|
"check:format": "prettier --check --log-level warn --ignore-path .gitignore --ignore-path .prettierignore . && shfmt --diff --indent 2 scripts/ tooling/ claude/",
|
|
31
29
|
"check:shell": "find scripts tooling claude .claude/hooks -name '*.sh' -exec shellcheck --severity=warning {} + && find .husky -maxdepth 1 -type f -exec shellcheck --severity=warning --shell=sh {} +",
|
|
@@ -71,7 +69,6 @@
|
|
|
71
69
|
"@types/bun": "^1.2.10",
|
|
72
70
|
"@types/micromatch": "^4.0.10",
|
|
73
71
|
"astro": "^6.1.9",
|
|
74
|
-
"cspell": "^8.17.1",
|
|
75
72
|
"happy-dom": "^20.14.5",
|
|
76
73
|
"husky": "^9.1.7",
|
|
77
74
|
"micromatch": "^4.0.8",
|
|
@@ -97,9 +97,8 @@ log_step "Assert scaffold"
|
|
|
97
97
|
# catches a failed domain, and the gate stays green while the target is
|
|
98
98
|
# missing everything that domain provides.
|
|
99
99
|
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
# the toolkit rather than a copy this gate could assert on. The two
|
|
100
|
+
# Authoring references name no path because they ship inside the plugin's
|
|
101
|
+
# skills rather than installing into a target. The two
|
|
103
102
|
# rule paths stand for the `claude` and `canon` groups `base` takes whole.
|
|
104
103
|
for path in "CLAUDE.md" ".claude/wiki/index.md" ".claude" "canon/context/index.md" \
|
|
105
104
|
".prettierrc" ".editorconfig" ".lintstagedrc" ".husky/pre-commit" ".github/workflows/verify.yml" "scripts/verify.sh" \
|
package/src/audits/catalog.ts
CHANGED
|
@@ -468,7 +468,6 @@ const RECORD_KINDS: readonly (readonly [string, Corpus])[] = [
|
|
|
468
468
|
['plans', 'per-machine'],
|
|
469
469
|
['groundwork', 'per-machine'],
|
|
470
470
|
['intake', 'per-machine'],
|
|
471
|
-
['standards', 'tracked'],
|
|
472
471
|
['teach', 'per-machine'],
|
|
473
472
|
]
|
|
474
473
|
|
|
@@ -588,14 +587,6 @@ export const AUDITS: readonly AuditSpec[] = [
|
|
|
588
587
|
argv: ['records', 'validate', kind, '--json'],
|
|
589
588
|
gatingExits: [],
|
|
590
589
|
corpus,
|
|
591
|
-
// `standards` is the one tracked kind here, so it takes none of the
|
|
592
|
-
// per-machine default the other five inherit from their corpus. A target
|
|
593
|
-
// reads standards through `canon standards` rather than a copy in its own
|
|
594
|
-
// tree, so carrying no standards folder at all is the
|
|
595
|
-
// ordinary state of every project but this repository.
|
|
596
|
-
...(kind === 'standards' && {
|
|
597
|
-
absentReasons: ['no-folder'] satisfies RecordRefusal[],
|
|
598
|
-
}),
|
|
599
590
|
counts: findingsOnly,
|
|
600
591
|
})),
|
|
601
592
|
{
|
package/src/claude/routing.ts
CHANGED
|
@@ -16,7 +16,7 @@ const PATH_TOKEN = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*$/
|
|
|
16
16
|
/** A placeholder segment, which a shape carries and a real path does not. */
|
|
17
17
|
const SHAPE = /<[^>]*>/
|
|
18
18
|
|
|
19
|
-
/** An alphabetic extension, which separates `
|
|
19
|
+
/** An alphabetic extension, which separates `tsconfig.json` from `3.6.0`. */
|
|
20
20
|
const EXTENSION = /\.[a-z]{2,5}$/
|
|
21
21
|
|
|
22
22
|
/** Extensions a folder is probed with, so a glob narrowed by type still hits. */
|
|
@@ -6,10 +6,9 @@ import { SURFACE_ROOTS } from '@/roots/surface'
|
|
|
6
6
|
/**
|
|
7
7
|
* The authoring roots this repository owns and no install channel delivers.
|
|
8
8
|
*
|
|
9
|
-
* Every entry is a folder a target never holds under that spelling.
|
|
10
|
-
* install
|
|
11
|
-
*
|
|
12
|
-
* own source, docs, and catalogs.
|
|
9
|
+
* Every entry is a folder a target never holds under that spelling. Rules
|
|
10
|
+
* install under `.claude/rules/`, and the rest are this repository's own
|
|
11
|
+
* source, docs, and catalogs.
|
|
13
12
|
*
|
|
14
13
|
* `src/`, `scripts/`, and bare `docs/` are deliberately absent. A body naming
|
|
15
14
|
* one of those is describing the reader's own tree, so listing them would
|
|
@@ -28,7 +27,6 @@ const AUTHORING_ROOTS = [
|
|
|
28
27
|
'docs/agents/',
|
|
29
28
|
'governance/',
|
|
30
29
|
'internal/',
|
|
31
|
-
'standards/',
|
|
32
30
|
'tooling/',
|
|
33
31
|
'wiki/',
|
|
34
32
|
] as const
|
|
@@ -51,7 +49,7 @@ const TOKEN = /`([^`\s]+)`/g
|
|
|
51
49
|
*
|
|
52
50
|
* Requires an extension and a separator, and admits no `<`, `$`, or `*`. A
|
|
53
51
|
* body writes `canon/context/<domain>.md` to name a shape rather than a
|
|
54
|
-
* file, and `${CLAUDE_SKILL_DIR}
|
|
52
|
+
* file, and `${CLAUDE_SKILL_DIR}/../<skill>/references/<name>.md` to resolve
|
|
55
53
|
* against the plugin root, which is self-contained by construction.
|
|
56
54
|
*/
|
|
57
55
|
const CONCRETE = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*\.[a-z]{1,4}$/
|
package/src/cli/help.ts
CHANGED
|
@@ -27,7 +27,6 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
|
27
27
|
title: 'Domains',
|
|
28
28
|
rows: [
|
|
29
29
|
['gov [cmd]', 'Governance rules (install, sync, list)'],
|
|
30
|
-
['standards [cmd]', 'Standards (list, <name>)'],
|
|
31
30
|
['tooling [cmd]', 'Tooling stacks (sync, ref, create)'],
|
|
32
31
|
['claude [cmd]', 'Claude workflow (init, sync, setup)'],
|
|
33
32
|
['wiki [cmd]', 'Wiki pages (init)'],
|
|
@@ -75,7 +74,7 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
|
75
74
|
['labels [cmd]', 'Read a changed set against the label map (audit)'],
|
|
76
75
|
['comments [cmd]', 'Measure comment density and trend (scan)'],
|
|
77
76
|
['context [cmd]', 'Report context folder health (audit)'],
|
|
78
|
-
['markdown [cmd]', 'Report markdown against the
|
|
77
|
+
['markdown [cmd]', 'Report markdown against the shipped bans (audit)'],
|
|
79
78
|
['census [path]', 'Report file count, extensions, and line totals'],
|
|
80
79
|
],
|
|
81
80
|
},
|
|
@@ -91,10 +90,10 @@ const SANDBOX_ROWS: CommandRow[] = [
|
|
|
91
90
|
const EXAMPLES = [
|
|
92
91
|
'canon sync ../my-app',
|
|
93
92
|
'canon sandbox git:commit',
|
|
94
|
-
'canon gov install
|
|
93
|
+
'canon gov install node',
|
|
95
94
|
'canon gov sync ../my-app',
|
|
96
95
|
'canon gov restated --json',
|
|
97
|
-
'canon
|
|
96
|
+
'canon markdown audit docs/',
|
|
98
97
|
'canon init ../my-app',
|
|
99
98
|
'canon tooling sync base',
|
|
100
99
|
'canon claude init',
|
package/src/cli.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { register as init } from '@/commands/init'
|
|
|
5
5
|
import { register as sandbox } from '@/commands/sandbox'
|
|
6
6
|
import { register as sync } from '@/commands/sync'
|
|
7
7
|
import { register as gov } from '@/commands/gov'
|
|
8
|
-
import { register as standards } from '@/commands/standards'
|
|
9
8
|
import { register as tooling } from '@/commands/tooling'
|
|
10
9
|
import { register as claude } from '@/commands/claude'
|
|
11
10
|
import { register as wiki } from '@/commands/wiki'
|
|
@@ -66,7 +65,6 @@ init(program)
|
|
|
66
65
|
sandbox(program)
|
|
67
66
|
sync(program)
|
|
68
67
|
gov(program)
|
|
69
|
-
standards(program)
|
|
70
68
|
tooling(program)
|
|
71
69
|
claude(program)
|
|
72
70
|
wiki(program)
|
package/src/commands/gov.ts
CHANGED
|
@@ -143,7 +143,7 @@ export function register(program: Command): void {
|
|
|
143
143
|
gov
|
|
144
144
|
.command('install')
|
|
145
145
|
.description('Install a governance stack into .claude/rules/canon/')
|
|
146
|
-
.argument('[stack]', 'Stack name (e.g. base, node,
|
|
146
|
+
.argument('[stack]', 'Stack name (e.g. base, node, python)')
|
|
147
147
|
.argument('[target]', 'Target directory', '.')
|
|
148
148
|
.helpOption('-h, --help', 'Show this help message')
|
|
149
149
|
.option('--add <rules>', 'Comma-separated rules to layer on the stack')
|
|
@@ -152,9 +152,9 @@ export function register(program: Command): void {
|
|
|
152
152
|
[
|
|
153
153
|
'',
|
|
154
154
|
'Examples:',
|
|
155
|
-
' canon gov install
|
|
155
|
+
' canon gov install node',
|
|
156
156
|
' canon gov install node ../my-app',
|
|
157
|
-
' canon gov install
|
|
157
|
+
' canon gov install node --add 210-astro,260-shadcn ../my-app',
|
|
158
158
|
' canon gov install --add tasks,plan ../my-app',
|
|
159
159
|
'',
|
|
160
160
|
'With --add and no stack, the named rules install alone. A first',
|
|
@@ -307,7 +307,7 @@ export function register(program: Command): void {
|
|
|
307
307
|
'members a closed catalog holds, then compares the stated figure',
|
|
308
308
|
'against what the tree actually counts.',
|
|
309
309
|
'',
|
|
310
|
-
'Catalogs read: skills, governance rules,
|
|
310
|
+
'Catalogs read: skills, governance rules, CLI commands,',
|
|
311
311
|
'and audit ids. Closed rather than derived, so widening the',
|
|
312
312
|
'set is a deliberate change rather than a side effect of a new list',
|
|
313
313
|
'command shipping elsewhere.',
|
|
@@ -441,10 +441,6 @@ export function register(program: Command): void {
|
|
|
441
441
|
'present and the question answerable.',
|
|
442
442
|
'',
|
|
443
443
|
'Forms read, with where each resolves:',
|
|
444
|
-
' standard canon standards <name> , against standards/ alone, which',
|
|
445
|
-
' is where the verb itself looks. internal/standards/ is not',
|
|
446
|
-
' tried, since a name resolving only there refuses for the',
|
|
447
|
-
' session that opens the citation.',
|
|
448
444
|
' path a backticked path carrying a directory segment and a file',
|
|
449
445
|
' extension, against the root',
|
|
450
446
|
" sibling a bare <nnn>-<slug>.md , against the citing rule's folder",
|
|
@@ -454,7 +450,7 @@ export function register(program: Command): void {
|
|
|
454
450
|
' naming a file, such as canon/context/<domain>.md or app/**/route.ts',
|
|
455
451
|
' a bare filename naming a convention, such as route.ts or manifest.toml',
|
|
456
452
|
' a span carrying no file extension, which is a folder or a module',
|
|
457
|
-
' specifier, such as src/pages/ , next/font , or claude/
|
|
453
|
+
' specifier, such as src/pages/ , next/font , or claude/skills',
|
|
458
454
|
' a fenced block, which displays a path rather than pointing at one',
|
|
459
455
|
'',
|
|
460
456
|
'Exempt, reported by name rather than dropped:',
|
package/src/commands/init.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function register(program: Command): void {
|
|
|
40
40
|
'Examples:',
|
|
41
41
|
' canon init',
|
|
42
42
|
' canon init ../my-app',
|
|
43
|
-
' canon init --stack
|
|
43
|
+
' canon init --stack node --add 210-astro,260-shadcn ../my-app',
|
|
44
44
|
' canon init --skip governance ../my-app',
|
|
45
45
|
'',
|
|
46
46
|
].join('\n'),
|
package/src/commands/labels.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { checkTitleFormat, type TitleFormatIssue } from '@/labels/format'
|
|
|
10
10
|
import { MAP_REL } from '@/labels/map'
|
|
11
11
|
import { isReleasePullRequest, scanPhaseLabels } from '@/labels/phase'
|
|
12
12
|
import { checkSections } from '@/labels/sections'
|
|
13
|
-
import { scanTitleSpelling } from '@/labels/spelling'
|
|
14
13
|
import { BAN_SETS } from '@/markdown/bans'
|
|
15
14
|
import { bodyLines, scanBans } from '@/markdown/scan'
|
|
16
15
|
import { intro, logInfo, logStep, logWarn, outro, plural } from '@/cli/ui'
|
|
@@ -169,7 +168,7 @@ export function register(program: Command): void {
|
|
|
169
168
|
labels
|
|
170
169
|
.command('scan')
|
|
171
170
|
.description(
|
|
172
|
-
'Fail a pull request or a posted review whose title, body, or review comment carries a phase label, a board identifier, a session link
|
|
171
|
+
'Fail a pull request or a posted review whose title, body, or review comment carries a phase label, a board identifier, or a session link',
|
|
173
172
|
)
|
|
174
173
|
.helpOption('-h, --help', 'Show this help message')
|
|
175
174
|
.option(
|
|
@@ -188,7 +187,7 @@ export function register(program: Command): void {
|
|
|
188
187
|
'after',
|
|
189
188
|
[
|
|
190
189
|
'',
|
|
191
|
-
'Reads
|
|
190
|
+
'Reads the git-commit versioning reference for the two namespaces and sorts every',
|
|
192
191
|
'version-shaped token this pull request carries into the one the pull',
|
|
193
192
|
'request is allowed to hold. A release-please pull request, read off its',
|
|
194
193
|
'own fixed head branch and title, may carry semver references. Every',
|
|
@@ -212,13 +211,6 @@ export function register(program: Command): void {
|
|
|
212
211
|
'session and for no other reader, which no clone repairs, so it is read',
|
|
213
212
|
'on a release pull request too, where the board identifier is not.',
|
|
214
213
|
'',
|
|
215
|
-
"It also spell-checks the title alone against this repository's own",
|
|
216
|
-
'cspell config, since release-please copies the title into',
|
|
217
|
-
'CHANGELOG.md and nothing else spell-checks it first. The check shells',
|
|
218
|
-
"this repository's own resolved cspell binary and reports nothing when",
|
|
219
|
-
'a target project carries none, rather than reaching the network or',
|
|
220
|
-
'forcing a new dependency.',
|
|
221
|
-
'',
|
|
222
214
|
"It also grades the title alone against claude/skills/git-pr/references/pr.md's ## Title",
|
|
223
215
|
'section: the `<type>(<scope>)[!]: <subject>` structure, lowercase casing',
|
|
224
216
|
'for the type, the scope, and the first subject word, and a 72-',
|
|
@@ -234,11 +226,11 @@ export function register(program: Command): void {
|
|
|
234
226
|
'request or a review, outside a code span or a link.',
|
|
235
227
|
'',
|
|
236
228
|
'Exit codes:',
|
|
237
|
-
' 0 none of the
|
|
229
|
+
' 0 none of the six found',
|
|
238
230
|
' 1 refused, with the reason on stderr or in the JSON record',
|
|
239
231
|
' 2 the title or body carries a phase label, a board identifier, a',
|
|
240
232
|
' session link, a banned character, a heading outside the closed',
|
|
241
|
-
' list or a required one missing,
|
|
233
|
+
' list or a required one missing, or a title',
|
|
242
234
|
" breaking claude/skills/git-pr/references/pr.md's format, casing, or length",
|
|
243
235
|
'',
|
|
244
236
|
'Examples:',
|
|
@@ -532,7 +524,6 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
532
524
|
}
|
|
533
525
|
|
|
534
526
|
const result = scanPhaseLabels(resolved)
|
|
535
|
-
const spelling = await scanTitleSpelling(resolved.title, process.cwd())
|
|
536
527
|
// A scan given no title, whatever produced that absence, has nothing to
|
|
537
528
|
// grade and would otherwise fail as `structure` for the wrong reason.
|
|
538
529
|
const titleFormat =
|
|
@@ -579,34 +570,6 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
579
570
|
for (const link of result.sessionLinks) logWarn(link)
|
|
580
571
|
}
|
|
581
572
|
|
|
582
|
-
const spellingChecked = spelling.kind === 'checked'
|
|
583
|
-
const unspelledWords =
|
|
584
|
-
spelling.kind === 'checked' ? spelling.unknownWords : []
|
|
585
|
-
|
|
586
|
-
logStep(
|
|
587
|
-
spelling.kind === 'unavailable'
|
|
588
|
-
? 'Spelling unavailable'
|
|
589
|
-
: unspelledWords.length === 0
|
|
590
|
-
? 'Clean'
|
|
591
|
-
: 'Unspelled word found',
|
|
592
|
-
)
|
|
593
|
-
if (spelling.kind === 'unavailable' && spelling.reason === 'no-binary') {
|
|
594
|
-
logInfo(
|
|
595
|
-
`no node_modules/.bin/cspell resolved walking up from ${spelling.probedFrom}, so the title was not checked`,
|
|
596
|
-
)
|
|
597
|
-
} else if (spelling.kind === 'unavailable') {
|
|
598
|
-
logInfo(
|
|
599
|
-
`cspell did not exit clean or with issues found (${spelling.message}), so the title was not checked`,
|
|
600
|
-
)
|
|
601
|
-
} else if (unspelledWords.length === 0) {
|
|
602
|
-
logInfo('no word in the title is absent from every dictionary')
|
|
603
|
-
} else {
|
|
604
|
-
logWarn(
|
|
605
|
-
`${plural(unspelledWords.length, 'word')} in the title absent from every dictionary. Fix the spelling, or add jargon and project-specific terms to .cspell/project-terms.txt and dependency vocabulary to .cspell/tech-stack.txt.`,
|
|
606
|
-
)
|
|
607
|
-
for (const word of unspelledWords) logWarn(word)
|
|
608
|
-
}
|
|
609
|
-
|
|
610
573
|
const titleFormatIssues = titleFormat?.issues ?? []
|
|
611
574
|
|
|
612
575
|
// A review carries its own headings, a release-please body opens on a
|
|
@@ -707,8 +670,6 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
707
670
|
semverTags: result.semverTags,
|
|
708
671
|
boardReferences: result.boardReferences,
|
|
709
672
|
sessionLinks: result.sessionLinks,
|
|
710
|
-
unspelledWords,
|
|
711
|
-
spellingChecked,
|
|
712
673
|
titleFormatIssues,
|
|
713
674
|
sections: { checked: sectionsChecked, ...sections },
|
|
714
675
|
bannedCharacters,
|
|
@@ -719,7 +680,6 @@ async function runScan(opts: ScanOptions): Promise<number> {
|
|
|
719
680
|
return result.phaseLabels.length === 0 &&
|
|
720
681
|
result.boardReferences.length === 0 &&
|
|
721
682
|
result.sessionLinks.length === 0 &&
|
|
722
|
-
unspelledWords.length === 0 &&
|
|
723
683
|
titleFormatIssues.length === 0 &&
|
|
724
684
|
sections.outside.length === 0 &&
|
|
725
685
|
sections.missing.length === 0 &&
|
package/src/commands/migrate.ts
CHANGED
|
@@ -1124,7 +1124,7 @@ export function register(program: Command): void {
|
|
|
1124
1124
|
'repoints every citation that names one, live or archived.',
|
|
1125
1125
|
'',
|
|
1126
1126
|
'A promoted folder is one a durable record cites and no source file',
|
|
1127
|
-
'under claude/, scripts/, src/,
|
|
1127
|
+
'under claude/, scripts/, src/, or governance/ names by',
|
|
1128
1128
|
'path. A folder a script still writes into stays in scratch, since',
|
|
1129
1129
|
'moving it needs a code change first.',
|
|
1130
1130
|
'',
|
package/src/commands/records.ts
CHANGED
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
import {
|
|
30
30
|
type Finding,
|
|
31
31
|
isRecordKind,
|
|
32
|
-
isSharedScratch,
|
|
33
32
|
RECORD_KINDS,
|
|
34
33
|
type RecordKind,
|
|
35
34
|
type ValidateOutcome,
|
|
@@ -92,10 +91,7 @@ export function register(program: Command): void {
|
|
|
92
91
|
.argument('<kind>', `Record folder: ${RECORD_KINDS.join(', ')}`)
|
|
93
92
|
.helpOption('-h, --help', 'Show this help message')
|
|
94
93
|
.option('--json', 'Add a machine-readable record on stdout')
|
|
95
|
-
.option(
|
|
96
|
-
'--root <path>',
|
|
97
|
-
'Project root, defaulting to the main worktree except on standards',
|
|
98
|
-
)
|
|
94
|
+
.option('--root <path>', 'Project root, defaulting to the main worktree')
|
|
99
95
|
.addHelpText(
|
|
100
96
|
'after',
|
|
101
97
|
[
|
|
@@ -104,8 +100,6 @@ export function register(program: Command): void {
|
|
|
104
100
|
' plans filename, required sections, and the suggested-and-answer contract',
|
|
105
101
|
' groundwork README and current-state files, numbering, dating, and a half-closed track',
|
|
106
102
|
' intake overview file, numbering, dating, and the four bullets every item carries',
|
|
107
|
-
' standards frontmatter, the scope section and its handoff list, and a filename',
|
|
108
|
-
' derived from the governed path',
|
|
109
103
|
'',
|
|
110
104
|
'Exit codes:',
|
|
111
105
|
' 0 every check passed',
|
|
@@ -113,12 +107,11 @@ export function register(program: Command): void {
|
|
|
113
107
|
' 2 at least one record carries a finding',
|
|
114
108
|
'',
|
|
115
109
|
'It reports and never writes, so a session fixes what the report names. A',
|
|
116
|
-
'session record has no history to undo a wrong repair from
|
|
117
|
-
'is installed and cited everywhere, so a rename is larger than a file move.',
|
|
110
|
+
'session record has no history to undo a wrong repair from.',
|
|
118
111
|
'',
|
|
119
112
|
'Examples:',
|
|
120
113
|
' canon records validate plans',
|
|
121
|
-
' canon records validate
|
|
114
|
+
' canon records validate teach',
|
|
122
115
|
' canon records validate intake --json',
|
|
123
116
|
'',
|
|
124
117
|
].join('\n'),
|
|
@@ -718,15 +711,6 @@ function reportRefusal(
|
|
|
718
711
|
return 1
|
|
719
712
|
}
|
|
720
713
|
|
|
721
|
-
/**
|
|
722
|
-
* A shared-scratch kind reads the main worktree root, so a linked worktree
|
|
723
|
-
* validates the records every other session reads. A tracked corpus reads the
|
|
724
|
-
* checkout the caller stands in, which is the copy that session has edited.
|
|
725
|
-
*/
|
|
726
|
-
function defaultRoot(kind: RecordKind): Promise<string> {
|
|
727
|
-
return isSharedScratch(kind) ? mainWorktreeRoot() : currentWorktreeRoot()
|
|
728
|
-
}
|
|
729
|
-
|
|
730
714
|
async function runValidate(
|
|
731
715
|
kind: string,
|
|
732
716
|
opts: ValidateCommandOptions,
|
|
@@ -745,7 +729,7 @@ async function runValidate(
|
|
|
745
729
|
)
|
|
746
730
|
}
|
|
747
731
|
|
|
748
|
-
const root = opts.root ?? (await
|
|
732
|
+
const root = opts.root ?? (await mainWorktreeRoot())
|
|
749
733
|
|
|
750
734
|
return report(await validateWithStacks(root, kind), emitJson, root)
|
|
751
735
|
}
|
package/src/commands/tooling.ts
CHANGED
|
@@ -99,8 +99,7 @@ export function register(program: Command): void {
|
|
|
99
99
|
' canon tooling sync vite-react web --skip base',
|
|
100
100
|
'',
|
|
101
101
|
'A target below its git root syncs as a subfolder. It withholds',
|
|
102
|
-
'.github/, which GitHub reads only at the root
|
|
103
|
-
'cspell.json registering the stack word lists.',
|
|
102
|
+
'.github/, which GitHub reads only at the root.',
|
|
104
103
|
'',
|
|
105
104
|
'To gate CI on tooling drift, run headlessly with neither flag. That',
|
|
106
105
|
'exits 1 when a file would be replaced and 0 when none would, which is',
|
|
@@ -276,8 +275,7 @@ function runList(opts: ListOptions): number {
|
|
|
276
275
|
|
|
277
276
|
/**
|
|
278
277
|
* Writes the reference to stdout and every frame line to stderr, so a caller
|
|
279
|
-
* capturing the output with `$(...)` receives the document alone.
|
|
280
|
-
* `print` in `src/commands/standards.ts`.
|
|
278
|
+
* capturing the output with `$(...)` receives the document alone.
|
|
281
279
|
*/
|
|
282
280
|
function printReference(stack: string): number {
|
|
283
281
|
intro('canon tooling reference')
|
package/src/counts/catalogs.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { join } from 'node:path'
|
|
|
3
3
|
import { AUDITS } from '@/audits/catalog'
|
|
4
4
|
import { listSkills } from '@/claude/skills-list'
|
|
5
5
|
import { buildGovCatalog } from '@/gov/list'
|
|
6
|
-
import { listStandards } from '@/standards/read'
|
|
7
6
|
|
|
8
7
|
export interface Catalog {
|
|
9
8
|
readonly id: string
|
|
@@ -59,11 +58,6 @@ export const CATALOGS: readonly Catalog[] = [
|
|
|
59
58
|
nouns: ['rule', 'rules'],
|
|
60
59
|
count: (root) => buildGovCatalog(root).rules.length,
|
|
61
60
|
},
|
|
62
|
-
{
|
|
63
|
-
id: 'standards',
|
|
64
|
-
nouns: ['standard', 'standards'],
|
|
65
|
-
count: (root) => listStandards(root).length,
|
|
66
|
-
},
|
|
67
61
|
{
|
|
68
62
|
id: 'commands',
|
|
69
63
|
nouns: ['command', 'commands'],
|
package/src/gate/boundaries.ts
CHANGED
|
@@ -337,8 +337,8 @@ export const capabilitySeeding: Measure = async (ctx) => {
|
|
|
337
337
|
* Every file under `claude/` resolved through its symlinks and rejected when it
|
|
338
338
|
* lands under `internal/`.
|
|
339
339
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
340
|
+
* An installer dereferences any symlink under the plugin root with no code in
|
|
341
|
+
* the path to filter, so a link added there ships whatever it points at. Walking with symlinks
|
|
342
342
|
* followed is what an install copies, so this is the only place the boundary
|
|
343
343
|
* can be measured. The walk tracks the real directories on its current descent
|
|
344
344
|
* and does not enter one a second time, the way `find -L` stops on a loop.
|
package/src/gate/measures.ts
CHANGED
|
@@ -505,7 +505,7 @@ export const documentCeiling: Measure = async (ctx) => {
|
|
|
505
505
|
* Whether a path's stem, its basename up to the first `.`, repeats its
|
|
506
506
|
* immediate parent folder, either whole or as a `<folder>-` prefix.
|
|
507
507
|
*
|
|
508
|
-
* Exact rather than singular-aware, so `
|
|
508
|
+
* Exact rather than singular-aware, so `skills/skill.md` passes. A false
|
|
509
509
|
* positive on a gate stage teaches a contributor to route around it, and a
|
|
510
510
|
* widened match is cheap to add once a real echo slips through.
|
|
511
511
|
*/
|
|
@@ -771,28 +771,6 @@ export function seedEntryCount(payload: string): number {
|
|
|
771
771
|
)
|
|
772
772
|
}
|
|
773
773
|
|
|
774
|
-
/**
|
|
775
|
-
* Scoped to arrival rather than the corpus, since `standards/standard.md`
|
|
776
|
-
* forbids writing a criterion into an existing standard outside the change that
|
|
777
|
-
* exercises it. Gating the known gaps would fail every push until someone
|
|
778
|
-
* closed them all, which is the sweep that rule exists to prevent.
|
|
779
|
-
*/
|
|
780
|
-
export const standardCriteria: Measure = async (ctx) => {
|
|
781
|
-
const run = await ctx.cli(['standards', 'audit', '--arrivals-only'])
|
|
782
|
-
|
|
783
|
-
if (run.exitCode === 0) {
|
|
784
|
-
return { emissions: [info('Arriving standards carry a success criterion')] }
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
return {
|
|
788
|
-
emissions: [output(run.all)],
|
|
789
|
-
failure:
|
|
790
|
-
run.exitCode === 2
|
|
791
|
-
? 'A standard new to this branch carries no success-criterion section. Run bun src/cli.ts standards audit.'
|
|
792
|
-
: 'canon standards audit could not read which standards arrived on this branch. Run bun src/cli.ts standards audit --json to see why.',
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
774
|
/**
|
|
797
775
|
* Every file in the gated corpus, walked from `ctx.root` rather than from the
|
|
798
776
|
* changed set.
|
|
@@ -804,7 +782,7 @@ export const standardCriteria: Measure = async (ctx) => {
|
|
|
804
782
|
* nothing it would notice.
|
|
805
783
|
*
|
|
806
784
|
* `dot` is what reaches the seeds, which is half of what `tooling/` ships. Every
|
|
807
|
-
* seeded `.claude/` tree, `.
|
|
785
|
+
* seeded `.claude/` tree, `.github/` workflow, and `.husky/` hook sits behind a
|
|
808
786
|
* dotted segment, so the default scan walks `tooling/` and returns none of the
|
|
809
787
|
* files a scaffolded project actually receives.
|
|
810
788
|
*/
|
|
@@ -841,8 +819,8 @@ function describeShippedReference(reference: ShippedReference): string {
|
|
|
841
819
|
return 'a pull request number that resolves elsewhere for a reader in a target'
|
|
842
820
|
case 'docs-path':
|
|
843
821
|
return 'a path into this repository that a registry install never carries'
|
|
844
|
-
case '
|
|
845
|
-
return 'a
|
|
822
|
+
case 'retired-verb':
|
|
823
|
+
return 'a call to canon standards, a verb no installed CLI carries'
|
|
846
824
|
case 'phase-label':
|
|
847
825
|
return 'a phase label that names a board no target holds'
|
|
848
826
|
case 'rule-path':
|
|
@@ -903,8 +881,8 @@ export const shippedReferences: Measure = async (ctx) => {
|
|
|
903
881
|
),
|
|
904
882
|
failure:
|
|
905
883
|
found.length === 1
|
|
906
|
-
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>,
|
|
907
|
-
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>,
|
|
884
|
+
? `One reference in the shipped corpora resolves wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, cite the owner skill's reference in place of a canon standards call, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning canon/context/ entry, state the fact a shipped skill body's cited rule enforces instead of its path, or mark the line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`
|
|
885
|
+
: `${found.length} references in the shipped corpora resolve wrong for a reader in a target. Qualify a cross-repository citation as owner/repo#123 or owner/repo@abc1234, cite a docs page through canon docs <name>, cite the owner skill's reference in place of a canon standards call, state a same-repository citation or a phase label as a fact instead and relocate the evidence to the owning canon/context/ entry, state the fact a shipped skill body's cited rule enforces instead of its path, or mark each line ${REFERENCE_MARKER}: <reason> where the bare form is the point.`,
|
|
908
886
|
}
|
|
909
887
|
}
|
|
910
888
|
|
package/src/gate/sequencer.ts
CHANGED
|
@@ -50,6 +50,45 @@ export interface LoadReading {
|
|
|
50
50
|
/** The 1-minute load average. */
|
|
51
51
|
readonly loadAverage: number
|
|
52
52
|
readonly cores: number
|
|
53
|
+
/**
|
|
54
|
+
* The `some avg10` stall share per resource from `/proc/pressure`, which the
|
|
55
|
+
* load average folds into one number. Absent where the kernel has no PSI.
|
|
56
|
+
*/
|
|
57
|
+
readonly pressure?: PressureReading
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PressureReading {
|
|
61
|
+
readonly cpu: number
|
|
62
|
+
readonly memory: number
|
|
63
|
+
readonly io: number
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reads the `some avg10` percentage from one `/proc/pressure/<resource>` file.
|
|
68
|
+
* A file that does not parse answers `undefined`, which drops the clause.
|
|
69
|
+
*/
|
|
70
|
+
export function parsePressure(text: string): number | undefined {
|
|
71
|
+
const match = /^some\b.*\bavg10=(\d+(?:\.\d+)?)\b/m.exec(text)
|
|
72
|
+
if (match === null) return undefined
|
|
73
|
+
const value = Number.parseFloat(match[1])
|
|
74
|
+
return Number.isFinite(value) ? value : undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readPressure(): PressureReading | undefined {
|
|
78
|
+
try {
|
|
79
|
+
const read = (name: string) =>
|
|
80
|
+
parsePressure(readFileSync(`/proc/pressure/${name}`, 'utf8'))
|
|
81
|
+
const cpu = read('cpu')
|
|
82
|
+
const memory = read('memory')
|
|
83
|
+
const io = read('io')
|
|
84
|
+
if (cpu === undefined || memory === undefined || io === undefined) {
|
|
85
|
+
return undefined
|
|
86
|
+
}
|
|
87
|
+
return { cpu, memory, io }
|
|
88
|
+
} catch {
|
|
89
|
+
// PSI off or absent: the files may exist and still fail with EOPNOTSUPP.
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
53
92
|
}
|
|
54
93
|
|
|
55
94
|
/**
|
|
@@ -62,7 +101,12 @@ export function readProcLoad(): LoadReading | undefined {
|
|
|
62
101
|
const first = readFileSync('/proc/loadavg', 'utf8').split(' ')[0]
|
|
63
102
|
const loadAverage = Number.parseFloat(first)
|
|
64
103
|
if (!Number.isFinite(loadAverage)) return undefined
|
|
65
|
-
|
|
104
|
+
const pressure = readPressure()
|
|
105
|
+
return {
|
|
106
|
+
loadAverage,
|
|
107
|
+
cores: availableParallelism(),
|
|
108
|
+
...(pressure === undefined ? {} : { pressure }),
|
|
109
|
+
}
|
|
66
110
|
} catch {
|
|
67
111
|
return undefined
|
|
68
112
|
}
|
|
@@ -252,7 +296,12 @@ function withLoadLine(
|
|
|
252
296
|
const reading = (ctx.readLoad ?? readProcLoad)()
|
|
253
297
|
if (reading === undefined) return outcome
|
|
254
298
|
|
|
255
|
-
const
|
|
299
|
+
const { pressure } = reading
|
|
300
|
+
const stalls =
|
|
301
|
+
pressure === undefined
|
|
302
|
+
? ''
|
|
303
|
+
: `, pressure cpu ${pressure.cpu.toFixed(1)}% memory ${pressure.memory.toFixed(1)}% io ${pressure.io.toFixed(1)}% over 10s,`
|
|
304
|
+
const text = `Load average ${reading.loadAverage} on ${reading.cores} cores${stalls} when the ${stage.label} stage failed`
|
|
256
305
|
return {
|
|
257
306
|
...outcome,
|
|
258
307
|
emissions: [...outcome.emissions, { kind: 'info', text }],
|