@erclx/aitk 3.25.0 → 3.25.2

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 (44) hide show
  1. package/README.md +1 -1
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/create-snippet/REQUIREMENT.md +3 -3
  4. package/claude/skills/create-snippet/SKILL.md +3 -3
  5. package/claude/skills/migration-claude-md/REQUIREMENT.md +0 -1
  6. package/claude/skills/migration-context/REQUIREMENT.md +0 -1
  7. package/claude/skills/migration-superseded/REQUIREMENT.md +0 -1
  8. package/claude/skills/setup-init/REQUIREMENT.md +1 -1
  9. package/claude/skills/setup-init/SKILL.md +4 -9
  10. package/claude/skills/toolkit-cli/REQUIREMENT.md +1 -2
  11. package/claude/skills/toolkit-operator/REQUIREMENT.md +1 -1
  12. package/claude/skills/toolkit-operator/SKILL.md +1 -1
  13. package/docs/agents/commands.md +1 -1
  14. package/docs/agents/context-audit.md +1 -1
  15. package/docs/agents/install-and-sync.md +30 -45
  16. package/docs/agents/scripting.md +0 -6
  17. package/docs/agents/skills-reach.md +1 -1
  18. package/docs/ai-workflow.md +0 -1
  19. package/docs/target-projects.md +12 -14
  20. package/governance/stacks/base.toml +5 -3
  21. package/package.json +1 -1
  22. package/scripts/core/install-check.sh +10 -12
  23. package/scripts/core/verify.sh +9 -7
  24. package/src/claude/cases/setup.ts +8 -5
  25. package/src/claude/skills-reach.ts +3 -3
  26. package/src/cli.ts +2 -3
  27. package/src/commands/context.ts +2 -2
  28. package/src/commands/init.ts +0 -3
  29. package/src/commands/snippets.ts +2 -157
  30. package/src/commands/sync.ts +2 -5
  31. package/src/context/citations.ts +41 -1
  32. package/src/init/flags.ts +3 -8
  33. package/src/init/plan.ts +1 -21
  34. package/src/init/steps.ts +1 -21
  35. package/src/sync/check.ts +6 -11
  36. package/src/sync/layout.ts +9 -6
  37. package/src/sync/reverse.ts +3 -5
  38. package/src/sync/stamp.ts +7 -7
  39. package/src/sync/target.ts +1 -3
  40. package/standards/skill.md +1 -1
  41. package/claude/skills/migration-standards/REQUIREMENT.md +0 -50
  42. package/claude/skills/migration-standards/SKILL.md +0 -99
  43. package/src/snippets/adapter.ts +0 -35
  44. package/src/snippets/install.ts +0 -172
@@ -40,11 +40,6 @@ export const SETUP_CASES: readonly SkillCase[] = [
40
40
  'Move the agent-flavored docs out of the docs folder and into context.',
41
41
  expect: 'migration-context',
42
42
  },
43
- {
44
- prompt:
45
- 'The snippets folder needs to move under .claude to match the current layout.',
46
- expect: 'migration-standards',
47
- },
48
43
  {
49
44
  prompt:
50
45
  'This file was replaced by a folder, help me split its content into it.',
@@ -65,6 +60,14 @@ export const SETUP_CASES: readonly SkillCase[] = [
65
60
  "I don't know which specific toolkit skill I need, just handle it for me.",
66
61
  expect: 'toolkit-operator',
67
62
  },
63
+ // The performing skill wins a phrase naming its operation over the front
64
+ // door's own routing framing, even though toolkit-operator's description
65
+ // quotes a phrase near this one. See .claude/context/cli/audits.md.
66
+ {
67
+ prompt:
68
+ "Whatever the right toolkit command is, get this project's rules installed.",
69
+ expect: 'setup-gov',
70
+ },
68
71
  {
69
72
  prompt:
70
73
  'Work through the open feedback issues on the toolkit repo one by one.',
@@ -12,9 +12,9 @@ const SHIPPED_SKILLS = join('claude', 'skills')
12
12
  * The authoring roots this repository owns and no install channel delivers.
13
13
  *
14
14
  * Every entry is a folder a target never holds under that spelling. Standards
15
- * install nowhere and are reached through the plugin corpus, rules install
16
- * under `.claude/rules/`, snippets under `.claude/snippets/`, and the rest are
17
- * this repository's own source, docs, and catalogs.
15
+ * and snippets install nowhere and are reached through the plugin corpus,
16
+ * rules install under `.claude/rules/`, and the rest are this repository's
17
+ * own source, docs, and catalogs.
18
18
  *
19
19
  * `src/`, `scripts/`, and bare `docs/` are deliberately absent. A body naming
20
20
  * one of those is describing the reader's own tree, so listing them would
package/src/cli.ts CHANGED
@@ -49,7 +49,7 @@ function showHelp(): void {
49
49
  `${GREY}│${NC} sandbox [cat:cmd] ${GREY}# Provision and run sandbox scenarios${NC}`,
50
50
  `${GREY}│${NC} gov [command] ${GREY}# Governance commands (install, sync)${NC}`,
51
51
  `${GREY}│${NC} standards [cmd] ${GREY}# Standards commands (install, sync, list, <name>)${NC}`,
52
- `${GREY}│${NC} snippets [cmd] ${GREY}# Snippets commands (install, sync)${NC}`,
52
+ `${GREY}│${NC} snippets [cmd] ${GREY}# Snippets commands (create, list)${NC}`,
53
53
  `${GREY}│${NC} tooling [cmd] ${GREY}# Manage tooling stacks (sync, ref, create)${NC}`,
54
54
  `${GREY}│${NC} claude [cmd] ${GREY}# Claude workflow (init, sync, setup)${NC}`,
55
55
  `${GREY}│${NC} wiki [cmd] ${GREY}# Wiki commands (init)${NC}`,
@@ -89,8 +89,7 @@ function showHelp(): void {
89
89
  `${GREY}│${NC} aitk gov sync ../my-app`,
90
90
  `${GREY}│${NC} aitk gov restated --json`,
91
91
  `${GREY}│${NC} aitk standards markdown`,
92
- `${GREY}│${NC} aitk snippets install base ../my-app`,
93
- `${GREY}│${NC} aitk snippets sync ../my-app`,
92
+ `${GREY}│${NC} aitk snippets list`,
94
93
  `${GREY}│${NC} aitk init ../my-app`,
95
94
  `${GREY}│${NC} aitk tooling sync base`,
96
95
  `${GREY}│${NC} aitk tooling create`,
@@ -180,7 +180,7 @@ async function runAudit(
180
180
  if (folders.length === 0) {
181
181
  return refuse(
182
182
  'no-folders',
183
- `No audited folder found ${named ? 'under .claude/ or the project root' : 'under .claude/'}. Looked for: ${names.join(', ')}.`,
183
+ `No audited folder found ${named ? 'under .claude/ or the project root' : 'under .claude/'}, since resolving one needs its own index.md file. Looked for: ${names.join(', ')}.`,
184
184
  gateOnly,
185
185
  root,
186
186
  opts.json ?? false,
@@ -392,7 +392,7 @@ function reportScope(
392
392
  if (unresolved.length === 0) return
393
393
 
394
394
  logWarn(
395
- `Under neither .claude/ nor the project root: ${unresolved.join(', ')}`,
395
+ `Not audited, since resolving needs its own index.md: ${unresolved.join(', ')}`,
396
396
  )
397
397
  }
398
398
 
@@ -12,7 +12,6 @@ interface InitOptions {
12
12
  /** Always present: the option falls back to `DEFAULT_STACK`. */
13
13
  readonly stack: string
14
14
  readonly add?: string
15
- readonly snippets?: string
16
15
  readonly skip?: string
17
16
  }
18
17
 
@@ -35,7 +34,6 @@ export function register(program: Command): void {
35
34
  ' aitk init ../my-app',
36
35
  ' aitk init --stack astro --add 260-shadcn ../my-app',
37
36
  ' aitk init --skip governance ../my-app',
38
- ' aitk init --snippets all ../my-app',
39
37
  '',
40
38
  ].join('\n'),
41
39
  )
@@ -62,7 +60,6 @@ async function runInit(
62
60
  const flags: InitFlags = {
63
61
  stack: options.stack,
64
62
  add: options.add,
65
- snippets: options.snippets,
66
63
  skip,
67
64
  }
68
65
 
@@ -1,32 +1,9 @@
1
1
  import type { Command } from 'commander'
2
2
  import { registerPassThroughVerbs } from '@/commands/pass-through'
3
3
  import { PROJECT_ROOT } from '@/project-root'
4
- import { createSnippetsAdapter } from '@/snippets/adapter'
5
4
  import { BASE_CATEGORY } from '@/snippets/categories'
6
- import {
7
- ALL_CATEGORY,
8
- installSnippets,
9
- installSnippetsRule,
10
- installableCategories,
11
- resolveSnippets,
12
- } from '@/snippets/install'
13
5
  import { buildSnippetsCatalog } from '@/snippets/list'
14
- import { recordStamp, runDomainSync } from '@/sync/engine'
15
- import { resolveTarget } from '@/target'
16
- import {
17
- intro,
18
- isNonInteractive,
19
- logAdd,
20
- logError,
21
- logInfo,
22
- logStep,
23
- logWarn,
24
- outro,
25
- palette,
26
- select,
27
- } from '@/ui'
28
-
29
- const SNIPPETS_REL = '.claude/snippets'
6
+ import { intro, logInfo, logStep, outro } from '@/ui'
30
7
 
31
8
  const PASS_THROUGH_VERBS = ['create'] as const
32
9
 
@@ -39,42 +16,8 @@ interface ListOptions {
39
16
  export function register(program: Command): void {
40
17
  const snippets = program
41
18
  .command('snippets')
42
- .description('Snippets commands (install, sync, create, list)')
43
- .helpOption('-h, --help', 'Show this help message')
44
-
45
- snippets
46
- .command('install')
47
- .description('Install snippets into .claude/snippets/')
48
- .argument('[category]', "Preset, folder, or 'all'")
49
- .argument('[target]', 'Target directory', '.')
19
+ .description('Snippets commands (create, list)')
50
20
  .helpOption('-h, --help', 'Show this help message')
51
- .addHelpText(
52
- 'after',
53
- [
54
- '',
55
- 'Examples:',
56
- ' aitk snippets install essentials',
57
- ' aitk snippets install all',
58
- ' aitk snippets install claude ../my-app',
59
- '',
60
- ].join('\n'),
61
- )
62
- .action(async (category: string | undefined, target: string) => {
63
- process.exitCode = await runInstall(category, target)
64
- })
65
-
66
- snippets
67
- .command('sync')
68
- .description('Update snippets already installed under .claude/snippets/')
69
- .argument('[target]', 'Target directory', '.')
70
- .helpOption('-h, --help', 'Show this help message')
71
- .action(async (target: string) => {
72
- process.exitCode = await runDomainSync(
73
- createSnippetsAdapter(PROJECT_ROOT),
74
- target,
75
- { protectedRoot: PROJECT_ROOT },
76
- )
77
- })
78
21
 
79
22
  snippets
80
23
  .command('list')
@@ -90,104 +33,6 @@ export function register(program: Command): void {
90
33
  registerPassThroughVerbs(snippets, 'snippets', PASS_THROUGH_VERBS)
91
34
  }
92
35
 
93
- /**
94
- * Refuses rather than picking, the same judgment `aitk gov install` applies to
95
- * its stack picker. Headless, `select_option` returned the first option, which
96
- * here was `all`, so an agent omitting the argument installed every category.
97
- */
98
- async function chooseCategory(root: string): Promise<string | number> {
99
- const categories = installableCategories(root)
100
-
101
- if (categories.length === 0) {
102
- logError('No categories found in snippets source.')
103
- outro()
104
- return 1
105
- }
106
-
107
- if (isNonInteractive()) {
108
- logError(
109
- `Category argument is required in non-interactive mode. One of: ${[ALL_CATEGORY, ...categories].join(', ')}.`,
110
- )
111
- outro()
112
- return 1
113
- }
114
-
115
- return select({
116
- message: 'Select category to install:',
117
- options: [ALL_CATEGORY, ...categories].map((name) => ({
118
- value: name,
119
- label: name,
120
- })),
121
- })
122
- }
123
-
124
- async function runInstall(
125
- category: string | undefined,
126
- target: string,
127
- ): Promise<number> {
128
- intro('aitk snippets install')
129
-
130
- const resolved = resolveTarget(target, PROJECT_ROOT)
131
- if (typeof resolved === 'number') return resolved
132
-
133
- let selected = category
134
- if (selected === undefined) {
135
- const choice = await chooseCategory(PROJECT_ROOT)
136
- if (typeof choice === 'number') return choice
137
- selected = choice
138
- }
139
-
140
- const resolution = resolveSnippets(PROJECT_ROOT, selected)
141
- if (!resolution.ok) {
142
- logError(`Category not found: ${resolution.unknownCategory}`)
143
- outro()
144
- return 1
145
- }
146
-
147
- logStep(resolution.step)
148
- for (const slug of resolution.missing) {
149
- logWarn(`${slug} (source not found, skipping)`)
150
- }
151
-
152
- if (resolution.files.length === 0) {
153
- logWarn(`No snippets found for category: ${selected}`)
154
- outro()
155
- return 0
156
- }
157
-
158
- for (const file of resolution.files) logInfo(file.relPath)
159
-
160
- const display = target.replace(/\/$/, '')
161
- const shouldInstall = await select({
162
- message: `Install ${resolution.files.length} snippets to ${display}/${SNIPPETS_REL}?`,
163
- options: [
164
- { value: true, label: 'Yes' },
165
- { value: false, label: 'No' },
166
- ],
167
- nonInteractiveDefault: true,
168
- })
169
-
170
- if (!shouldInstall) {
171
- logWarn('Cancelled')
172
- outro()
173
- return 0
174
- }
175
-
176
- logStep('Installing snippets')
177
- for (const rel of await installSnippets(resolution.files, resolved)) {
178
- logAdd(rel)
179
- }
180
- for (const rel of await installSnippetsRule(PROJECT_ROOT, resolved)) {
181
- logAdd(rel)
182
- }
183
- await recordStamp(createSnippetsAdapter(PROJECT_ROOT), resolved, new Date())
184
-
185
- const { GREEN, NC } = palette(process.stderr)
186
- outro()
187
- process.stderr.write(`${GREEN}✓ Snippets installed${NC}\n`)
188
- return 0
189
- }
190
-
191
36
  /**
192
37
  * `JSON.stringify` replaces a `printf` template that interpolated names into a
193
38
  * JSON string literal unescaped, so a slug or category carrying a quote emitted
@@ -34,7 +34,6 @@ import {
34
34
  import { describeSkew } from '@/version/skew'
35
35
 
36
36
  const SYNC_ARGS: Record<SyncDomain, readonly string[]> = {
37
- snippets: ['snippets', 'sync'],
38
37
  governance: ['gov', 'sync'],
39
38
  claude: ['claude', 'sync'],
40
39
  }
@@ -157,7 +156,7 @@ function renderCheck(report: CheckReport): void {
157
156
  logWarn(
158
157
  `${entry.files} files at ${entry.rootPath}/, nothing at ${entry.installPath}/`,
159
158
  )
160
- logInfo('Run /aitk:migration-standards to relocate them.')
159
+ logInfo('Move the content yourself. No sync command touches these.')
161
160
  }
162
161
 
163
162
  renderSeeds(report)
@@ -338,9 +337,7 @@ function describeUnclaimed(entry: UnclaimedFolder): string {
338
337
  }
339
338
 
340
339
  /**
341
- * The two proposal-only skills no other field reaches. `migration-standards`
342
- * gets the same treatment from the unmigrated section above, which is the
343
- * precedent this follows rather than a second spelling of it.
340
+ * The proposal-only skills no other field reaches.
344
341
  */
345
342
  function renderMigrations(report: CheckReport): void {
346
343
  const { migrations } = report.reverse
@@ -14,6 +14,43 @@ import { listRepositoryFiles } from '@/git-files'
14
14
  */
15
15
  export const IGNORE_MARKER = 'audit-ignore-citations'
16
16
 
17
+ /**
18
+ * Matches the marker plus an optional comma-separated path list.
19
+ *
20
+ * `<!-- audit-ignore-citations: .claude/standards/X.md -->` narrows the skip
21
+ * to the paths named. The bare form with no colon skips every citation the
22
+ * line carries, which is the only shape a line with nothing else worth
23
+ * checking needs and the shape every marker predating this pattern still
24
+ * carries.
25
+ */
26
+ const IGNORE_MARKER_LINE =
27
+ /<!--\s*audit-ignore-citations(?::\s*([^>]+?))?\s*-->/
28
+
29
+ interface IgnoredCitations {
30
+ readonly all: boolean
31
+ readonly paths: ReadonlySet<string>
32
+ }
33
+
34
+ /**
35
+ * Reads which citations on a line the marker excuses.
36
+ *
37
+ * A bare marker excuses every citation the line carries, matching the
38
+ * pre-named-path behavior. A named marker excuses only the paths listed,
39
+ * so a line carrying both a placeholder and a real reference keeps the real
40
+ * one checked rather than losing it to the placeholder beside it.
41
+ */
42
+ function ignoredCitations(line: string): IgnoredCitations | undefined {
43
+ const match = line.match(IGNORE_MARKER_LINE)
44
+ if (!match) return undefined
45
+ if (match[1] === undefined) return { all: true, paths: new Set() }
46
+
47
+ const paths = match[1]
48
+ .split(',')
49
+ .map((path) => path.trim())
50
+ .filter(Boolean)
51
+ return { all: false, paths: new Set(paths) }
52
+ }
53
+
17
54
  /**
18
55
  * Trees holding content authored to be parsed rather than followed.
19
56
  *
@@ -87,9 +124,12 @@ export function collectCitations(
87
124
  fenced = !fenced
88
125
  continue
89
126
  }
90
- if (fenced || line.includes(IGNORE_MARKER)) continue
127
+ if (fenced) continue
128
+
129
+ const ignored = ignoredCitations(line)
91
130
 
92
131
  for (const match of line.matchAll(pattern)) {
132
+ if (ignored && (ignored.all || ignored.paths.has(match[0]))) continue
93
133
  found.push({ file: rel, line: index + 1, path: match[0] })
94
134
  }
95
135
  }
package/src/init/flags.ts CHANGED
@@ -26,11 +26,6 @@ export const INIT_OPTIONS: readonly InitOptionSpec[] = [
26
26
  flags: '--add <rules>',
27
27
  description: 'Comma-separated governance rules to layer on',
28
28
  },
29
- {
30
- key: 'snippets',
31
- flags: '--snippets <category>',
32
- description: "Snippets preset, category, 'none', or 'all'. Omit to skip",
33
- },
34
29
  {
35
30
  key: 'skip',
36
31
  flags: '--skip <list>',
@@ -54,9 +49,9 @@ export function applyInitOptions(command: Command): Command {
54
49
 
55
50
  /**
56
51
  * Whether the operator passed any flag, which is what makes the command
57
- * scriptable by suppressing the confirmation prompt. `--stack` and `--snippets`
58
- * both carry defaults, so presence has to be read from where the value came
59
- * from rather than from the value itself.
52
+ * scriptable by suppressing the confirmation prompt. `--stack` carries a
53
+ * default, so presence has to be read from where the value came from rather
54
+ * than from the value itself.
60
55
  */
61
56
  export function flagsProvided(cmd: Command): boolean {
62
57
  return INIT_OPTIONS.some(
package/src/init/plan.ts CHANGED
@@ -1,4 +1,4 @@
1
- export const SKIPPABLE_DOMAINS = ['wiki', 'governance', 'snippets'] as const
1
+ export const SKIPPABLE_DOMAINS = ['wiki', 'governance'] as const
2
2
 
3
3
  export type SkippableDomain = (typeof SKIPPABLE_DOMAINS)[number]
4
4
 
@@ -30,23 +30,9 @@ export interface InitPlan {
30
30
  export interface InitFlags {
31
31
  readonly stack?: string
32
32
  readonly add?: string
33
- /** Absent when `--snippets` was never passed, which installs none. */
34
- readonly snippets?: string
35
33
  readonly skip: SkipPlan
36
34
  }
37
35
 
38
- /**
39
- * Reads why the snippets step will not run, or `undefined` when it will.
40
- * Omitting `--snippets` reaches the same skip as `--skip snippets` explicitly,
41
- * so a plain `aitk init` installs none and both routes share one recovery
42
- * line rather than two behaviors with one spelling each.
43
- */
44
- export function snippetsSkipReason(flags: InitFlags): string | undefined {
45
- if (flags.skip.skipped.has('snippets')) return '--skip snippets'
46
- if (flags.snippets === undefined) return 'no --snippets given'
47
- return undefined
48
- }
49
-
50
36
  /**
51
37
  * Resolves the stack the run will install. An empty `--stack` reads as absent
52
38
  * rather than as a way to decline, so the flag carries a real name or nothing
@@ -111,12 +97,6 @@ export function planInit(flags: InitFlags): InitPlan {
111
97
  })
112
98
  }
113
99
 
114
- if (snippetsSkipReason(flags) !== undefined) {
115
- preview.push({ level: 'warn', text: 'snippets (skipped)' })
116
- } else {
117
- preview.push({ level: 'info', text: `snippets (${flags.snippets})` })
118
- }
119
-
120
100
  if (!flags.skip.skipped.has('wiki')) {
121
101
  preview.push({
122
102
  level: 'info',
package/src/init/steps.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type InitFlags, resolveStack, snippetsSkipReason } from '@/init/plan'
1
+ import { type InitFlags, resolveStack } from '@/init/plan'
2
2
  import type { DomainStep } from '@/init/run'
3
3
 
4
4
  /** Builds the child-process invocation for one domain. */
@@ -48,26 +48,6 @@ export function buildSteps(
48
48
  })
49
49
  }
50
50
 
51
- // `snippets === undefined` here can never fire on its own: `snippetsSkipReason`
52
- // already returns a reason whenever `flags.snippets` is undefined. It is what
53
- // narrows `snippets` to `string` for the child() call below, since TypeScript
54
- // cannot narrow a property through a function call.
55
- const snippets = flags.snippets
56
- const skipReason = snippetsSkipReason(flags)
57
- if (skipReason !== undefined || snippets === undefined) {
58
- steps.push({
59
- kind: 'skip',
60
- label: 'Snippets',
61
- notice: `Skipped: ${skipReason ?? 'no --snippets given'}. Run 'aitk snippets install essentials ${target}' to install snippets.`,
62
- })
63
- } else {
64
- steps.push({
65
- kind: 'run',
66
- label: 'Snippets',
67
- run: child(['snippets', 'install', snippets, resolved]),
68
- })
69
- }
70
-
71
51
  if (!flags.skip.skipped.has('wiki')) {
72
52
  steps.push({
73
53
  kind: 'run',
package/src/sync/check.ts CHANGED
@@ -4,7 +4,6 @@ import { execa } from 'execa'
4
4
  import { gitEnv } from '@/git-env'
5
5
  import { createGovAdapter, rulesSourceDir } from '@/gov/adapter'
6
6
  import { loadGovStack, resolveMissingRules, resolveRules } from '@/gov/stacks'
7
- import { createSnippetsAdapter } from '@/snippets/adapter'
8
7
  import { planSync, type ScanEntry, type SyncAdapter } from '@/sync/engine'
9
8
  import {
10
9
  collectSuperseded,
@@ -34,12 +33,11 @@ import { readSkew, type SkewReport } from '@/version/skew'
34
33
  /**
35
34
  * Domains the sync engine walks file by file. Tooling is a stamp domain without
36
35
  * being one of these, because `src/tooling/` never calls `planSync`, so the
37
- * three lookups below have no entry to offer it. Standards left the list with
38
- * the install channel: nothing writes the corpus into a target, so there is no
39
- * installed copy to attribute.
36
+ * three lookups below have no entry to offer it. Standards and snippets left
37
+ * the list with their install channels: nothing writes either corpus into a
38
+ * target, so there is no installed copy to attribute.
40
39
  */
41
40
  export const SCANNED_DOMAINS = [
42
- 'snippets',
43
41
  'governance',
44
42
  ] as const satisfies readonly StampDomain[]
45
43
 
@@ -51,17 +49,14 @@ export type ScannedDomain = (typeof SCANNED_DOMAINS)[number]
51
49
  * never go stale and belong in the read-only section instead.
52
50
  */
53
51
  const SYNCED_SOURCES: Record<ScannedDomain, string> = {
54
- snippets: 'snippets/',
55
52
  governance: 'governance/rules/',
56
53
  }
57
54
 
58
55
  const ADAPTERS: Record<ScannedDomain, (root: string) => SyncAdapter> = {
59
- snippets: createSnippetsAdapter,
60
56
  governance: createGovAdapter,
61
57
  }
62
58
 
63
59
  const INSTALL_MARKERS: Record<ScannedDomain, readonly string[]> = {
64
- snippets: ['.claude', 'snippets'],
65
60
  governance: ['.claude', 'rules'],
66
61
  }
67
62
 
@@ -547,9 +542,9 @@ export function baseBands(root: string): Set<string> {
547
542
  * The diff-and-bands path stays as the fallback for a target stamped before
548
543
  * governance recorded a chain. Rules are domain-scoped there too, so it
549
544
  * measures from governance's own anchor rather than from the oldest anchor
550
- * across domains the way `readNewSkills` does. A shared anchor would let a
551
- * snippets sync move the revision rules are measured from and drop a rule out
552
- * of the read.
545
+ * across domains the way `readNewSkills` does. A shared anchor would let
546
+ * another domain's sync move the revision rules are measured from and drop a
547
+ * rule out of the read.
553
548
  *
554
549
  * A target carrying no chain and no governance anchor reports nothing. It has
555
550
  * no date to measure against, and diffing from the beginning of history would
@@ -1,7 +1,6 @@
1
1
  import { existsSync, statSync } from 'node:fs'
2
2
  import { basename, join } from 'node:path'
3
3
  import { SUBDIRS } from '@/claude/seeds'
4
- import { snippetsSourceDir } from '@/snippets/categories'
5
4
  import type { StampDomain } from '@/sync/stamp'
6
5
 
7
6
  const CLAUDE_DIR = '.claude'
@@ -10,18 +9,22 @@ const CLAUDE_DIR = '.claude'
10
9
  * Domains an older toolkit installed at the project root, each with the source
11
10
  * folder naming what it owns. Governance is absent because its rules have always
12
11
  * landed under `.claude/rules/`, so there is no earlier location to be stranded
13
- * at. Standards is absent because no copy installs into a target at all now, so
14
- * a root `standards/` folder there is the project's own authoring surface and
15
- * reporting it as unmigrated would propose moving files nothing installed.
12
+ * at. Standards and snippets are absent because no copy installs into a target
13
+ * at all now, so a root `standards/` or `snippets/` folder there is the
14
+ * project's own authoring surface and reporting it as unmigrated would propose
15
+ * moving files nothing installed.
16
16
  *
17
17
  * A tuple array rather than a partial record, so the domain key stays typed
18
- * without asserting an `Object.entries` result back into the union.
18
+ * without asserting an `Object.entries` result back into the union. Empty
19
+ * now that both former entries retired their install channels. A future
20
+ * domain installing at a target's root, the way an older toolkit installed
21
+ * standards and snippets there, would be the next entry to add.
19
22
  */
20
23
  const ROOT_LAYOUTS: readonly (readonly [
21
24
  StampDomain,
22
25
  string,
23
26
  (root: string) => string,
24
- ])[] = [['snippets', 'snippets', snippetsSourceDir]]
27
+ ])[] = []
25
28
 
26
29
  /**
27
30
  * A target file that a shipped seed folder replaced. Carries no source and
@@ -47,8 +47,7 @@ export interface UnclaimedFolder {
47
47
 
48
48
  /**
49
49
  * A proposal-only skill with a live case in this target. The skills propose and
50
- * never execute, so the report names one rather than queueing a change, which
51
- * is the treatment `unmigrated` already gives `migration-standards`.
50
+ * never execute, so the report names one rather than queueing a change.
52
51
  */
53
52
  export interface MigrationCandidate {
54
53
  readonly skill: string
@@ -208,9 +207,8 @@ function covers(index: HistoryIndex, sourceRel: string): boolean {
208
207
  }
209
208
 
210
209
  /**
211
- * Live cases for the two proposal-only skills no report field has ever named.
212
- * `migration-standards` reaches an operator through `unmigrated`, and without
213
- * this its two siblings are documented and unreachable.
210
+ * Live cases for the two proposal-only skills no other report field names.
211
+ * Without this, both are documented and unreachable.
214
212
  *
215
213
  * Both tests read what the skill itself acts on, so a proposal the report makes
216
214
  * is one the skill has work to do for. A context tier that already holds files
package/src/sync/stamp.ts CHANGED
@@ -5,16 +5,16 @@ import { dirname, join, sep } from 'node:path'
5
5
  import { execa } from 'execa'
6
6
 
7
7
  /**
8
- * Domains the stamp can record. The first two attribute file by file through
9
- * the sync engine. Tooling runs its own inject and manifest machinery, so it
8
+ * Domains the stamp can record. Governance attributes file by file through the
9
+ * sync engine. Tooling runs its own inject and manifest machinery, so it
10
10
  * records the stack chain it resolved instead and carries no file hashes.
11
11
  *
12
- * A stamp written before the standards install channel closed still carries a
13
- * `standards` record. `isStamp` ignores the key and `sortDomains` drops it on
14
- * the next write, so the target loses a domain nothing can refresh rather than
15
- * losing the whole file.
12
+ * A stamp written before the standards or snippets install channel closed
13
+ * still carries a `standards` or `snippets` record. `isStamp` ignores the key
14
+ * and `sortDomains` drops it on the next write, so the target loses a domain
15
+ * nothing can refresh rather than losing the whole file.
16
16
  */
17
- export const STAMP_DOMAINS = ['snippets', 'governance', 'tooling'] as const
17
+ export const STAMP_DOMAINS = ['governance', 'tooling'] as const
18
18
 
19
19
  export type StampDomain = (typeof STAMP_DOMAINS)[number]
20
20
 
@@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
3
  import { isDirectory } from '@/target'
4
4
 
5
- export const SYNC_DOMAINS = ['snippets', 'governance', 'claude'] as const
5
+ export const SYNC_DOMAINS = ['governance', 'claude'] as const
6
6
 
7
7
  export type SyncDomain = (typeof SYNC_DOMAINS)[number]
8
8
 
@@ -12,7 +12,6 @@ export interface DomainState {
12
12
  }
13
13
 
14
14
  const DOMAIN_MARKERS: Record<SyncDomain, string> = {
15
- snippets: join('.claude', 'snippets'),
16
15
  governance: join('.claude', 'rules'),
17
16
  claude: '.claude',
18
17
  }
@@ -24,7 +23,6 @@ const DOMAIN_MARKERS: Record<SyncDomain, string> = {
24
23
  * because that is the only file `aitk claude sync` writes.
25
24
  */
26
25
  const DOMAIN_PATHS: Record<SyncDomain, readonly string[]> = {
27
- snippets: ['.claude/snippets/'],
28
26
  governance: ['.claude/rules/', '.claude/GOV.md'],
29
27
  claude: ['.gitignore'],
30
28
  }
@@ -235,7 +235,7 @@ A skill reads from two roots. Know which one a file lives under before referenci
235
235
  No standard installs into a project, so a body cites one place rather than choosing between two.
236
236
 
237
237
  - Cite `${CLAUDE_SKILL_DIR}/../../standards/X.md`. The plugin ships the whole standards folder beside `skills/`, so the path resolves in every install and needs no fallback behind it.
238
- - Never cite `.claude/standards/X.md` from a shipped body. A target holds no such folder, and one left behind by an older toolkit is a stale copy no resolver reads.
238
+ - Never cite `.claude/standards/X.md` from a shipped body. A target holds no such folder, and one left behind by an older toolkit is a stale copy no resolver reads. <!-- audit-ignore-citations: .claude/standards/X.md -->
239
239
  - Name `aitk standards X` instead where the body wants the document rather than a path to open, such as a value it captures or reports. That verb resolves `standards/` at the project root and then the corpus inside the package.
240
240
  - State the path once per body, at the site that reads the standard. A later mention of a standard the body already read stays bare, since repeating the path at every mention is noise rather than instruction.
241
241
  - A guard on a standard's presence names the file rather than the folder holding it, since a folder test answers for a sibling that happens to be there.