@erclx/canon 4.103.0 → 4.105.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 (60) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/canon-cli/SKILL.md +3 -77
  3. package/claude/skills/canon-operator/REQUIREMENT.md +2 -2
  4. package/claude/skills/canon-operator/SKILL.md +5 -5
  5. package/claude/skills/design-taste/references/craft.md +1 -1
  6. package/claude/skills/design-taste/references/preflight.md +2 -2
  7. package/claude/skills/design-taste/references/tells.md +1 -1
  8. package/claude/skills/git-followup/SKILL.md +1 -1
  9. package/claude/skills/git-issue/SKILL.md +2 -2
  10. package/{standards → claude/skills/git-issue/references}/issue.md +3 -8
  11. package/claude/skills/git-pr/SKILL.md +2 -2
  12. package/claude/skills/index-lookup/REQUIREMENT.md +2 -2
  13. package/claude/skills/project-commands/REQUIREMENT.md +1 -1
  14. package/claude/skills/seed-sync/REQUIREMENT.md +1 -1
  15. package/claude/skills/target-check/REQUIREMENT.md +1 -1
  16. package/claude/skills/target-setup/REQUIREMENT.md +56 -0
  17. package/claude/skills/target-setup/SKILL.md +163 -0
  18. package/claude/skills/target-setup/references/detect.md +80 -0
  19. package/claude/skills/{setup-indexes/SKILL.md → target-setup/references/indexes.md} +59 -23
  20. package/claude/skills/target-setup/references/verify.md +95 -0
  21. package/docs/agents/sandbox.md +1 -1
  22. package/docs/agents/scripting.md +3 -2
  23. package/docs/agents/tasks.md +13 -11
  24. package/docs/target-projects.md +11 -11
  25. package/docs/workflow/ai-workflow.md +8 -13
  26. package/governance/rules/ui/400-ui.md +0 -5
  27. package/governance/rules/ui/410-a11y.md +5 -0
  28. package/governance/rules/ui/440-surface-capture.md +7 -1
  29. package/governance/rules/ui/460-design-taste.md +1 -1
  30. package/governance/rules/ui/465-interface-casing.md +31 -0
  31. package/governance/stacks/astro.toml +1 -1
  32. package/governance/stacks/react.toml +1 -1
  33. package/package.json +1 -1
  34. package/scripts/core/regen-hero.sh +1 -1
  35. package/src/claude/cases/setup.ts +12 -11
  36. package/src/commands/sync.ts +1 -1
  37. package/src/commands/tasks.ts +1 -0
  38. package/src/design/document.ts +2 -0
  39. package/src/design/tokens.ts +4 -0
  40. package/src/gate/measures.ts +57 -0
  41. package/src/gate/stages.ts +10 -14
  42. package/src/gov/list.ts +1 -1
  43. package/src/sandbox/census.ts +3 -3
  44. package/src/shipped/references.ts +1 -1
  45. package/src/tasks/validate.ts +68 -0
  46. package/standards/design.md +1 -1
  47. package/standards/index.md +0 -1
  48. package/claude/skills/setup-gov/REQUIREMENT.md +0 -37
  49. package/claude/skills/setup-gov/SKILL.md +0 -77
  50. package/claude/skills/setup-indexes/REQUIREMENT.md +0 -48
  51. package/claude/skills/setup-init/REQUIREMENT.md +0 -46
  52. package/claude/skills/setup-init/SKILL.md +0 -133
  53. package/claude/skills/setup-plugins/REQUIREMENT.md +0 -42
  54. package/claude/skills/setup-plugins/SKILL.md +0 -81
  55. package/claude/skills/setup-plugins/references/plugin-catalog.md +0 -53
  56. package/claude/skills/setup-smoke/REQUIREMENT.md +0 -38
  57. package/claude/skills/setup-smoke/SKILL.md +0 -53
  58. package/claude/skills/setup-verify/REQUIREMENT.md +0 -39
  59. package/claude/skills/setup-verify/SKILL.md +0 -51
  60. package/scripts/core/regen-tooling-paths.sh +0 -61
@@ -109,7 +109,7 @@ function renderCheck(report: CheckReport): void {
109
109
  logStep('Not a toolkit project')
110
110
  logWarn('No .claude/ directory and no CLAUDE.md at the target.')
111
111
  logInfo(
112
- 'Run `canon init` to install, or /canon:setup-init to resolve a stack.',
112
+ 'Run `canon init` to install, or /canon:target-setup to resolve a stack.',
113
113
  )
114
114
  outro()
115
115
  return
@@ -218,6 +218,7 @@ export function register(program: Command): void {
218
218
  '',
219
219
  'Checks:',
220
220
  ' every Run now row points at a plan file that resolves',
221
+ ' no Needs a plan row has a live plan, and every Up next row has one',
221
222
  ' every task file carries a board row or a backlog line, never both',
222
223
  ' no task carries more than one row',
223
224
  ' no two Run now rows touch the same file',
@@ -134,6 +134,8 @@ export function renderDesignDocument(tokens: DesignTokens = TOKENS): string {
134
134
  ),
135
135
  ),
136
136
  '',
137
+ section('Layout', tokens.layout),
138
+ '',
137
139
  section('Motion', tokens.motion),
138
140
  '',
139
141
  section('Iconography', tokens.iconography),
@@ -70,6 +70,7 @@ export interface DesignTokens {
70
70
  readonly spacingNote: string
71
71
  readonly borders: readonly BorderToken[]
72
72
  readonly bordersNote: string
73
+ readonly layout: string
73
74
  readonly motion: string
74
75
  readonly iconography: string
75
76
  readonly preamble: string
@@ -371,6 +372,9 @@ export const TOKENS: DesignTokens = {
371
372
  },
372
373
  ],
373
374
 
375
+ layout:
376
+ 'Capture widths are 320, 768, 1280 and 1536 pixels, declared once in `tooling/web/configs/e2e/screenshot.ts`, with 320 and 1280 committed as evidence. 320 is the reflow floor and the rest are the breakpoint buckets. The landing page under `web/` sets its own `max-width` media queries between 430 and 1100 pixels per component rather than from a shared scale.',
377
+
374
378
  motion:
375
379
  'Motion is not used. No transition, animation, or keyframe declaration appears on any rendered surface, and the capture pipeline screenshots a static frame.',
376
380
 
@@ -629,6 +629,63 @@ export const shippedReferences: Measure = async (ctx) => {
629
629
  }
630
630
  }
631
631
 
632
+ const RAW_FIELD_FILE_REFERENCE = /(?:^|\s)(?:-f|--raw-field)[ =]\S+=@/
633
+
634
+ /**
635
+ * A `gh api` raw-string flag carrying a file reference, over the seven shipped
636
+ * corpora.
637
+ *
638
+ * `-f body=@<path>` sends the literal path as the field value, so a comment
639
+ * edited that way is overwritten with the path itself and loses the marker a
640
+ * later read resolves it by. `-F` is the flag that reads the file. The pattern
641
+ * requires `=@`, so `-F`, `--field`, and a raw string such as `-f body="text"`
642
+ * all pass.
643
+ *
644
+ * It scans prose rather than parsing a command, so a body quoting the wrong
645
+ * spelling to explain it fails too. Reword it rather than exempting it, since an
646
+ * exemption is what lets a real occurrence hide.
647
+ */
648
+ export const rawFieldFileReference: Measure = async (ctx) => {
649
+ const files = shippedCorpusFiles(ctx.root)
650
+
651
+ if (files.length === 0) {
652
+ return {
653
+ emissions: [],
654
+ unmeasured: `No corpus under ${SHIPPED_CORPORA.join(', ')} is present, so no shipped file was read.`,
655
+ }
656
+ }
657
+
658
+ const found = files.flatMap((file) =>
659
+ readFileSync(join(ctx.root, file), 'utf8')
660
+ .split('\n')
661
+ .flatMap((text, index) =>
662
+ RAW_FIELD_FILE_REFERENCE.test(text) ? [`${file}:${index + 1}`] : [],
663
+ ),
664
+ )
665
+
666
+ if (found.length === 0) {
667
+ return {
668
+ emissions: [
669
+ info(
670
+ `No raw-field file reference across ${files.length} files in ${SHIPPED_CORPORA.length} shipped corpora`,
671
+ ),
672
+ ],
673
+ }
674
+ }
675
+
676
+ return {
677
+ emissions: found.map((location) =>
678
+ warn(
679
+ `${location} passes a file reference through a raw-string flag, which posts the path as the field value. Use -F instead`,
680
+ ),
681
+ ),
682
+ failure:
683
+ found.length === 1
684
+ ? 'One raw-field file reference in the shipped corpora. Change -f or --raw-field to -F or --field where the value reads a file with @, or reword a prose line quoting the wrong spelling.'
685
+ : `${found.length} raw-field file references in the shipped corpora. Change -f or --raw-field to -F or --field where the value reads a file with @, or reword a prose line quoting the wrong spelling.`,
686
+ }
687
+ }
688
+
632
689
  /**
633
690
  * Every git-tracked file quoting a listed client command with the wrong
634
691
  * argument, over the whole tree rather than one corpus, since a wrong
@@ -10,6 +10,7 @@ import {
10
10
  recordIdempotence,
11
11
  sandboxCoverage,
12
12
  seedStandards,
13
+ rawFieldFileReference,
13
14
  shippedReferences,
14
15
  standardCriteria,
15
16
  unreferencedRules,
@@ -207,20 +208,6 @@ export const STAGES: readonly Stage[] = [
207
208
  ],
208
209
  success: 'Hero clean',
209
210
  },
210
- {
211
- id: 'tooling-paths',
212
- label: 'Tooling paths',
213
- checks: [
214
- script('regen-tooling-paths.sh', 'Tooling-path regen failed'),
215
- {
216
- kind: 'drift',
217
- pathspec: 'claude/skills/canon-cli/SKILL.md',
218
- failure:
219
- 'The overwrite contract drifted from what the stacks hold. Run bun run check and commit claude/skills/canon-cli/SKILL.md.',
220
- },
221
- ],
222
- success: 'Tooling paths clean',
223
- },
224
211
  {
225
212
  // `canon/DESIGN.md`, the base stylesheet, the web stylesheet, and the
226
213
  // tab icon are all written from `src/design/tokens.ts` and none is edited
@@ -423,6 +410,15 @@ export const STAGES: readonly Stage[] = [
423
410
  skipped: 'No shipped corpus changed, so no reference was read',
424
411
  checks: [{ kind: 'measure', measure: shippedReferences }],
425
412
  },
413
+ {
414
+ // Same corpora as shipped-references and a separate stage, since the
415
+ // verdict is a flag spelling rather than an unresolvable reference.
416
+ id: 'raw-field-file-reference',
417
+ label: 'Raw-field file references',
418
+ scope: new RegExp(SHIPPED_CORPORA.map((corpus) => `^${corpus}/`).join('|')),
419
+ skipped: 'No shipped corpus changed, so no flag was read',
420
+ checks: [{ kind: 'measure', measure: rawFieldFileReference }],
421
+ },
426
422
  {
427
423
  // Scoped to the two files a citation can drift between, so an edit to
428
424
  // either one runs the check. web/src/content/copy.ts carries the anchors
package/src/gov/list.ts CHANGED
@@ -30,7 +30,7 @@ export interface GovCatalog {
30
30
 
31
31
  /**
32
32
  * Reports each stack's own entries expanded, so a folder entry reaches a
33
- * consumer as the rules it stands for. `setup-gov` dedupes `--add` extras
33
+ * consumer as the rules it stands for. the `setup` skill dedupes `--add` extras
34
34
  * against this list, and a stack answering `core` there would re-add every
35
35
  * rule that folder already carries.
36
36
  *
@@ -71,9 +71,9 @@ export function listSkills(root: string): string[] {
71
71
  *
72
72
  * `<category>-<command>` is the rule `canon/context/sandbox/coverage.md` states, and it
73
73
  * alone pairs 29 of 54 skills. The bare `<command>` fallback is what reaches the
74
- * rest: `claude/setup-init.sh` drives the `setup-init` skill, not a
75
- * `claude-setup-init` that does not exist. Stating one spelling and shipping two
76
- * is what let the audit report a paired skill as unpaired.
74
+ * rest: `claude/target-setup.sh` drives the `target-setup` skill, not a `claude-target-setup` that
75
+ * does not exist. Stating one spelling and shipping two is what let the audit
76
+ * report a paired skill as unpaired.
77
77
  *
78
78
  * Returns undefined for a scenario driving no skill at all, which is every
79
79
  * `infra/` and `tooling/` scenario. Those exercise a CLI domain rather than a
@@ -210,7 +210,7 @@ const PHASE_LABEL = /\bv\d+\.\d+(?!\.\d)\b/g
210
210
  *
211
211
  * Anchored on the trailing `\d{3}-[\w-]+\.md` rather than on the bare
212
212
  * `.claude/rules/` prefix, which is what keeps a folder mention carrying no
213
- * number, such as `create-rule`, `memory-review`, and `setup-gov` already
213
+ * number, such as `create-rule`, `memory-review`, and `target-setup` already
214
214
  * write correctly, from matching. The segment group between `rules/` and the
215
215
  * numbered file admits both a governance-namespace path
216
216
  * (`canon/core/055-scratch.md`) and a project-namespace one
@@ -8,6 +8,7 @@ import {
8
8
  readOutcomes,
9
9
  readPlanTarget,
10
10
  readPullRequest,
11
+ resolveLivePlan,
11
12
  tasksDir,
12
13
  } from '@/tasks/archive'
13
14
  import { gitTrunkReader, type TrunkReader } from '@/tasks/trunk'
@@ -38,6 +39,8 @@ export const FINDING_KINDS = [
38
39
  'plan-unresolved',
39
40
  'plan-uncited',
40
41
  'plan-mismatched',
42
+ 'plan-parked',
43
+ 'plan-absent',
41
44
  'task-unresolved',
42
45
  'row-duplicated',
43
46
  'row-misshapen',
@@ -747,6 +750,70 @@ async function planDisagreement(
747
750
  }
748
751
  }
749
752
 
753
+ /**
754
+ * Tests the claim a group name makes about a plan. `## Needs a plan` says none
755
+ * is live and `## Up next` says one is written, and neither group has a `Plan`
756
+ * column, so the task file's own `Plan:` line is the only place either claim
757
+ * can be read against.
758
+ *
759
+ * A live plan is one the archive's resolution accepts and the disk holds, so a
760
+ * plan already archived reads as not live and a cited path with no file behind
761
+ * it reads the same way. A row whose task file is gone is left to
762
+ * `task-unresolved`, which owns that report.
763
+ */
764
+ async function checkGroupClaims(
765
+ rows: readonly BoardRow[],
766
+ dir: string,
767
+ root: string,
768
+ ): Promise<Finding[]> {
769
+ const parked = rows.filter(
770
+ (row) => row.group === 'Needs a plan' || row.group === 'Up next',
771
+ )
772
+
773
+ const found = await Promise.all(
774
+ parked.map(async (row) => groupClaimFinding(row, dir, root)),
775
+ )
776
+
777
+ return found.filter((finding): finding is Finding => finding !== undefined)
778
+ }
779
+
780
+ async function groupClaimFinding(
781
+ row: BoardRow,
782
+ dir: string,
783
+ root: string,
784
+ ): Promise<Finding | undefined> {
785
+ if (!row.stem) return undefined
786
+
787
+ const file = join(dir, `${row.stem}.md`)
788
+ if (!existsSync(file)) return undefined
789
+
790
+ const target = readPlanTarget(await readFile(file, 'utf8'))
791
+ const live = target ? resolveLivePlan(target, dir, root) : undefined
792
+ const hasLivePlan = live !== undefined && existsSync(live)
793
+
794
+ if (row.group === 'Needs a plan' && hasLivePlan) {
795
+ return {
796
+ kind: 'plan-parked',
797
+ group: row.group,
798
+ subject: row.stem,
799
+ message: `sits under ${row.group} and its task cites ${target}, a live plan. A row with a plan belongs under Up next or Run now, or the plan is stale and belongs in the archive.`,
800
+ }
801
+ }
802
+
803
+ if (row.group === 'Up next' && !hasLivePlan) {
804
+ return {
805
+ kind: 'plan-absent',
806
+ group: row.group,
807
+ subject: row.stem,
808
+ message: target
809
+ ? `sits under ${row.group}, which claims a written plan, and its task cites ${target}, which is not a live plan.`
810
+ : `sits under ${row.group}, which claims a written plan, and its task carries no Plan: line.`,
811
+ }
812
+ }
813
+
814
+ return undefined
815
+ }
816
+
750
817
  /**
751
818
  * Where a plan pointer lands, resolved against the board and against the
752
819
  * project root the way the archive resolves the same line. Neither base
@@ -1186,6 +1253,7 @@ export async function validateBoard(
1186
1253
  ...checkMapping(rows, backlog, dir),
1187
1254
  ...checkPlans(rows, dir, root),
1188
1255
  ...(await checkPlanAgreement(rows, dir, root)),
1256
+ ...(await checkGroupClaims(rows, dir, root)),
1189
1257
  ...checkCollisions(rows),
1190
1258
  ...checkOrdering(rows),
1191
1259
  ...parked.findings,
@@ -51,7 +51,7 @@ Use `## Personality`, `## Color`, `## Typography`, `## Spacing`, `## Borders`, `
51
51
 
52
52
  Add either optional section when the project has content for it:
53
53
 
54
- - `## Layout`: page width, breakpoints, and grid rules, as current constraints
54
+ - `## Layout`: page width, breakpoints, and grid rules, as current constraints. The surface-capture rule reads the breakpoints from here, so a project with none declared here is captured at the 320 reflow floor alone.
55
55
  - `## Mark`: the logo's construction rules and the files that carry it
56
56
 
57
57
  ## Template
@@ -19,7 +19,6 @@ Reference docs for consistent authoring across the toolkit and target projects.
19
19
  - [Glossary reference](glossary.md): Frontmatter, entry shape, ordering, and the rules deciding which terms a glossary carries
20
20
  - [Groundwork reference](groundwork.md): Folder layout, ordinal naming, reserved numbering, frontmatter and dating, required file contents, and conventions for a measurement track
21
21
  - [Intake reference](intake.md): Folder layout, ordinal naming, reserved index number, frontmatter and dating, the item template, the answer contract, and retrieval
22
- - [Issue reference](issue.md): GitHub issue title, labels, and body conventions
23
22
  - [Markdown reference](markdown.md): Headings, paragraph and list structure, code spans, the date form, punctuation, emphasis, file references, banned words, and frontmatter wording
24
23
  - [Memory reference](memory.md): Filename and type prefix, frontmatter, the body shape per type, links between entries, and the lifecycle from write to retire
25
24
  - [Mermaid reference](mermaid.md): Direction and layout, node and edge budgets, accessibility fields, label punctuation, and render verification for a Mermaid diagram
@@ -1,37 +0,0 @@
1
- ---
2
- name: setup-gov
3
- description: Scope boundary for governance rule install and the authoring it refuses to do in a target
4
- ---
5
-
6
- # Setup gov requirement
7
-
8
- ## Gap
9
-
10
- Without this skill, a session picks a stack name from memory and installs rules written for a framework the project does not use, passes an extra the catalog does not carry so the install warns on a file it cannot resolve, and adds a rule the picked stack already pulls in.
11
-
12
- The two costly failures are quieter. An unmatched technology gets the nearest rule instead of a stop, so the project carries governance authored for something adjacent and nothing marks it as a guess. A rule written directly into the target lands where no sync will ever reach it, so it drifts from the toolkit copy that owns it.
13
-
14
- The last failure sits at a boundary rather than inside a run. `setup-init` turns away a project whose language it carries no stack for and sends it here, and a body stating only what this skill does not cover leaves that route running one way, so a person arriving on it reads a skill that never says it is the destination.
15
-
16
- ## Must
17
-
18
- - Read the catalog at run time and resolve both the stack and every extra against it
19
- - Name the evidence file behind each detected technology in the preview, so a wrong match is visible before the install rather than after
20
- - Dedupe extras against the rules the picked stack already resolves
21
- - Run the install non-interactively, since the CLI picker blocks where no terminal is attached
22
- - State the inbound route from `setup-init`, so a project sent here for the language-neutral rule layer can tell it landed at the destination the chain named
23
-
24
- ## Must not
25
-
26
- - Author a rule inside the target project. Rules are authored in the toolkit and reach a project by install or sync.
27
- - Install a stack or extra whose name did not come from the catalog read
28
-
29
- ## Guards
30
-
31
- - An unmatched technology stops and presents the options rather than resolving to a nearby rule. Authoring the missing rule in the toolkit, installing the matching layers without the technology-specific one, and aborting are the three, and this skill picks none of them.
32
-
33
- ## Out of scope
34
-
35
- - The rest of the onboarding chain. This skill installs governance and nothing else, and `setup-init` is the one-shot path that installs governance beside tooling, snippets, and the seeds.
36
- - Scaffolding the rule file an unmatched technology needs. `create-rule` writes one into a target project, and a rule the toolkit should ship is authored under `governance/rules/` against `canon standards rule`.
37
- - Updating rules a project already has, which `canon gov sync` owns
@@ -1,77 +0,0 @@
1
- ---
2
- name: setup-gov
3
- description: Detects a project's stack from its files and installs matching toolkit governance rules into `.claude/rules/`. Use after scaffolding a new project, when asked to "install gov rules", "install governance", "set up governance", or when a target project has no `.claude/rules/` yet. Assumes the `canon` CLI is on PATH.
4
- ---
5
-
6
- # Gov install
7
-
8
- Automates `canon gov install` by inferring the stack and extras from the current project, then shelling out to the CLI with the resolved arguments.
9
-
10
- ## Scope
11
-
12
- - Governance rules and nothing else. `setup-init` is the one-shot chain that installs governance beside tooling, standards, snippets, and the seeds.
13
- - This is where `setup-init` sends a project whose language the toolkit carries no stack for. Rules install without the `base` development dependencies the tooling layer would drop on it. Resolve and stop exactly as below, since a language with no stack is the unmatched case `## Gap handling` already owns and arriving by that route changes none of it.
14
-
15
- ## Read the catalog
16
-
17
- Run this first to load the current stacks and rules. Never hardcode names. The catalog is the source of truth.
18
-
19
- ```bash
20
- canon gov list --json 2>/dev/null
21
- ```
22
-
23
- ## Detect the stack
24
-
25
- Read these from the project root in parallel:
26
-
27
- - `package.json`: dependencies and devDependencies
28
- - Root config files: `astro.config.*`, `next.config.*`, `vite.config.*`, `tailwind.config.*`, `tsconfig.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`
29
- - `canon/REQUIREMENTS.md` and `canon/ARCHITECTURE.md` if present
30
- - Directory structure via `ls -1` of the project root and `src/` if present
31
-
32
- ## Match
33
-
34
- Match the detected evidence to the catalog:
35
-
36
- - Pick the closest stack. Match detected runtime or framework against stack names in the catalog.
37
- - Identify additional technologies not already covered by the picked stack. For each, find a rule whose `description` or `paths` points at that technology, then add it via `--add`.
38
- - Dedupe extras against stack rules. Do not add a rule the stack already pulls in.
39
-
40
- ## Gap handling
41
-
42
- If a detected technology has no matching rule, stop and surface the gap. Do not guess.
43
-
44
- Present four options:
45
-
46
- 1. Author a new rule in the toolkit at `governance/rules/<domain>/<num>-<name>.md` following `${CLAUDE_SKILL_DIR}/../../standards/rule.md`, commit, then re-run install. Take this route when the toolkit should ship the rule to every project.
47
- 2. Invoke `create-rule`, which scaffolds the file into the target project at a non-colliding number. Take this route when the rule is specific to this project and no sync should ever overwrite it.
48
- 3. Install the matching non-<tech> rules and skip the tech-specific layer.
49
- 4. Abort.
50
-
51
- This skill authors no rule itself. Option 1 writes in the toolkit repository, and option 2 hands the project-local file to `create-rule`.
52
-
53
- ## Preview
54
-
55
- Before executing, output:
56
-
57
- - **Detected:** each technology with the file that evidenced it
58
- - **Stack:** picked stack name and total resolved rule count
59
- - **Extras:** each `--add` rule with a one-line reason
60
- - **Target:** resolved target path
61
- - **Command:** the exact shell command to run
62
-
63
- ## Execute
64
-
65
- Run with `CANON_NON_INTERACTIVE=1` so no CLI picker prompts appear. Claude Code's tool permission dialog is the confirmation gate.
66
-
67
- ```bash
68
- CANON_NON_INTERACTIVE=1 canon gov install <stack> --add <extras> <target>
69
- ```
70
-
71
- ## Response
72
-
73
- After execution, report:
74
-
75
- - Rule count installed
76
- - Target path
77
- - Any rules that failed to resolve (the CLI warns on missing rule files)
@@ -1,48 +0,0 @@
1
- ---
2
- name: setup-indexes
3
- description: Why index bootstrap is all-or-nothing per folder, what the confirmation loop protects, and why the convention block is pasted rather than written
4
- ---
5
-
6
- # Setup indexes requirement
7
-
8
- ## Gap
9
-
10
- Without this skill, a session bootstrapping the index system writes `index.md` into a folder and leaves the sibling frontmatter for later, so the first regen hard-errors on the first file missing a `title`. Partial coverage is not a partial success. The folder is broken until every sibling carries both fields, and the error surfaces at regen rather than at the write that caused it.
11
-
12
- The scan is where the other failures start. A walk that does not prune reaches `node_modules`, gitignored output, and the snippets folder, which is invoked by reference rather than browsed and needs neither an index nor per-file frontmatter. A folder that already carries an `index.md` gets a second one written over the first.
13
-
14
- One failure predates the scan. `setup-init` declines an install that wants the Claude layer without the tooling chain and names this skill as where the index system gets bootstrapped, and a body that never states the inbound route leaves the person arriving on it unable to tell whether they landed at the destination or somewhere adjacent to it.
15
-
16
- Drafted frontmatter is a proposal, and a session that writes it before the user sees it turns a review into a cleanup. The convention block has the mirror failure. A session that paraphrases it into `CLAUDE.md` produces a copy that reads correctly and no longer matches its source, so the two drift with nothing reporting it. And a project with no `CLAUDE.md` gets one scaffolded to hold the block, which installs a file the project declined.
17
-
18
- A fourth failure sits after the scan rather than inside it. `## Present candidates` had no branch for a scan that finds zero folders, so an empty result walked straight into the ask with nothing to choose from. `setup-init` folds this skill into the onboarding chain, and a fresh scaffold usually carries no markdown-heavy folder yet, so the ordinary onboarding run reached the undefined case.
19
-
20
- ## Must
21
-
22
- - Bootstrap all-or-nothing per chosen folder, since a folder carrying partial frontmatter hard-errors on regen
23
- - Prune the scan to folders a reader browses, and skip any folder already carrying an `index.md`
24
- - Surface every drafted `title` and `description` for the user to accept, edit, or reject before anything is written
25
- - Validate with a dry run and stop on the first reported error, before writing for real
26
- - Paste the convention block verbatim from its single source
27
- - Emit the closeout exactly once, whatever the seed step concluded
28
- - State the inbound route from `setup-init`, so an install wanting the Claude layer without the tooling chain can tell it landed at the destination the chain named
29
-
30
- ## Must not
31
-
32
- - Write a draft the user has not confirmed
33
- - Index the snippets folder, which is read by reference rather than browsed
34
- - Create `CLAUDE.md` to hold the convention block. Name the skip and the command that installs the file.
35
- - Overwrite content below the frontmatter block, or touch a file the user rejected
36
-
37
- ## Guards
38
-
39
- - A folder with fewer than the sibling threshold stays out of the candidate list unless the user names it explicitly, so the scan proposes and the user overrides
40
- - A `CLAUDE.md` already carrying the convention section skips the seed silently rather than appending a second copy
41
- - A scan that finds no candidate reports a one-line result and returns, skipping the ask and every step between it and the seed offer, rather than opening a prompt with nothing in it
42
-
43
- ## Out of scope
44
-
45
- - Regenerating an index in a project that already runs the system, which `canon indexes regen` does on its own
46
- - Wiring the lint-staged entry or the hook, which the closeout offers and the user opts into
47
- - Installing governance rules or scaffolding a project: `setup-gov` and `setup-init`
48
- - Writing the frontmatter conventions themselves, which the prose standard owns
@@ -1,46 +0,0 @@
1
- ---
2
- name: setup-init
3
- description: Scope boundary for the one-shot onboarding chain and the setup steps it leaves to a caller
4
- ---
5
-
6
- # Setup init requirement
7
-
8
- ## Gap
9
-
10
- Without this skill, a session installs a stack whose name it recalled rather than read, hand-writes configs the tooling sync already ships as golden files, and re-runs the chain over a project that owns its configs already.
11
-
12
- Two failures are the expensive ones because both return success. A monorepo run drops husky into every subtree, git honors one `core.hooksPath`, and the extra hook directories go quiet with nothing reporting it. A caller reads the chain as onboarding complete when plugin provisioning never runs, since no step states that it sits outside the chain.
13
-
14
- Two more start at the resolve step. A project whose language the toolkit carries no stack for resolves to `base`, and the preview reports the resolved name without marking it as a fallback, so `base` development dependencies, scripts, and hooks land on a project that will not use them and nobody had the moment to decline.
15
-
16
- The other is the refusal that ends rather than routes. An existing project, an install wanting the Claude layer without the tooling chain, and a language with no stack are the three states the chain does not serve, and each is declined by a declaration naming no destination, so the person who arrived with one reads a stop and goes no further.
17
-
18
- ## Must
19
-
20
- - Resolve every domain argument against a catalog read at run time, so a stack, rule, snippet, or tooling name the catalog does not carry cannot reach the command line
21
- - Preview the resolved chain before the first command runs, naming the evidence file behind each detected technology
22
- - Pass `--skip base` on every subtree past the first in a monorepo, so the shared hook layer lands once
23
- - State which onboarding steps the chain does not run and why, so a caller knows what is left and where the boundary sits rather than inferring completeness from a clean report
24
- - Mark a stack reached by fallback as a fallback in the preview, and name what `base` carries, so the write can be declined at the one point it is still declinable
25
- - Name a destination for every state the chain does not serve, in this body rather than only in a reference doc the person would have to already know to open, and say for each whether the chain stops or runs on a default
26
-
27
- ## Must not
28
-
29
- - Generate a config that tooling sync ships as a golden file. Generating from prose duplicates the installed file and the two drift.
30
- - Author a rule, stack, or snippet inside the target project when detection finds no match. Authoring happens in the toolkit and reaches a project by install or sync.
31
- - Grow an existing-project branch, a mode switch, or a clobber guard. The chain runs once against a fresh scaffold and the per-domain paths cover everything after that.
32
-
33
- ## Guards
34
-
35
- - An unmatched technology stops for a decision. Surface the gap and either defer to rule authoring or proceed on the matched layer with the gap named in the report, rather than resolving to the nearest rule.
36
-
37
- ## Out of scope
38
-
39
- - Installing governance rules alone: `setup-gov`. This skill installs governance beside tooling and the seeds.
40
- - Provisioning Claude Code plugins: `setup-plugins`. Those install once per machine rather than into a project, so no project-scoped chain can carry them.
41
- - Running the verification scripts, which `setup-verify` owns and this chain invokes as step 4
42
- - Publishing repository metadata: `repo-metadata`. It reaches a remote, which is past the project edge this chain stops at.
43
- - Making the first commit: `git-commit`. It reaches the project's history, which is past the project edge this chain stops at.
44
- - Re-installing into a project that already has the files, which the per-domain `canon <domain> install` and `canon sync` commands own
45
- - Deciding which of those per-domain commands an existing project needs: `canon-operator`. That skill reads the target before naming one, which this chain never does, so it is the destination rather than a guess made here.
46
- - Seeding the `.claude/` folder without the tooling chain, which `canon claude init` does on its own and `setup-indexes` finishes