@erclx/aitk 0.8.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 (359) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/claude/.claude-plugin/plugin.json +19 -0
  4. package/claude/skills/bash-script/SKILL.md +96 -0
  5. package/claude/skills/bash-script/references/patterns.md +349 -0
  6. package/claude/skills/ci-workflow/SKILL.md +52 -0
  7. package/claude/skills/ci-workflow/references/workflows.md +74 -0
  8. package/claude/skills/claude-address-review/SKILL.md +107 -0
  9. package/claude/skills/claude-autoship/SKILL.md +137 -0
  10. package/claude/skills/claude-design-extract/SKILL.md +69 -0
  11. package/claude/skills/claude-design-propose/SKILL.md +75 -0
  12. package/claude/skills/claude-diagram/SKILL.md +126 -0
  13. package/claude/skills/claude-docs/SKILL.md +204 -0
  14. package/claude/skills/claude-feature/REQUIREMENT.md +35 -0
  15. package/claude/skills/claude-feature/SKILL.md +148 -0
  16. package/claude/skills/claude-groundwork/SKILL.md +148 -0
  17. package/claude/skills/claude-groundwork/references/folder-format.md +107 -0
  18. package/claude/skills/claude-memory-capture/SKILL.md +80 -0
  19. package/claude/skills/claude-memory-review/REQUIREMENT.md +37 -0
  20. package/claude/skills/claude-memory-review/SKILL.md +206 -0
  21. package/claude/skills/claude-orchestrate/SKILL.md +125 -0
  22. package/claude/skills/claude-pr-review/SKILL.md +150 -0
  23. package/claude/skills/claude-review/SKILL.md +136 -0
  24. package/claude/skills/claude-roadmap/SKILL.md +61 -0
  25. package/claude/skills/claude-roadmap/references/roadmap.md +52 -0
  26. package/claude/skills/claude-screencast/SKILL.md +160 -0
  27. package/claude/skills/claude-seed-sync/SKILL.md +141 -0
  28. package/claude/skills/claude-slides-draft/SKILL.md +62 -0
  29. package/claude/skills/claude-standards-audit/REQUIREMENT.md +33 -0
  30. package/claude/skills/claude-standards-audit/SKILL.md +79 -0
  31. package/claude/skills/claude-standards-audit/references/branch.md +49 -0
  32. package/claude/skills/claude-standards-audit/references/pr.md +124 -0
  33. package/claude/skills/claude-tasks/SKILL.md +123 -0
  34. package/claude/skills/claude-ui-test/SKILL.md +75 -0
  35. package/claude/skills/claude-ux-audit/SKILL.md +78 -0
  36. package/claude/skills/claude-worktree/SKILL.md +79 -0
  37. package/claude/skills/cli-script/SKILL.md +47 -0
  38. package/claude/skills/cli-script/references/template.md +43 -0
  39. package/claude/skills/create-rule/SKILL.md +68 -0
  40. package/claude/skills/create-skill/REQUIREMENT.md +30 -0
  41. package/claude/skills/create-skill/SKILL.md +24 -0
  42. package/claude/skills/create-snippet/SKILL.md +28 -0
  43. package/claude/skills/create-snippet/references/snippets.md +57 -0
  44. package/claude/skills/create-standard/SKILL.md +31 -0
  45. package/claude/skills/create-standard/references/standard.md +52 -0
  46. package/claude/skills/docs-sync/SKILL.md +87 -0
  47. package/claude/skills/git-branch/REQUIREMENT.md +38 -0
  48. package/claude/skills/git-branch/SKILL.md +60 -0
  49. package/claude/skills/git-branch/references/branch.md +49 -0
  50. package/claude/skills/git-commit/REQUIREMENT.md +33 -0
  51. package/claude/skills/git-commit/SKILL.md +51 -0
  52. package/claude/skills/git-commit/references/commit.md +63 -0
  53. package/claude/skills/git-followup/REQUIREMENT.md +38 -0
  54. package/claude/skills/git-followup/SKILL.md +35 -0
  55. package/claude/skills/git-issue/REQUIREMENT.md +38 -0
  56. package/claude/skills/git-issue/SKILL.md +69 -0
  57. package/claude/skills/git-issue/references/issue.md +84 -0
  58. package/claude/skills/git-pr/REQUIREMENT.md +37 -0
  59. package/claude/skills/git-pr/SKILL.md +119 -0
  60. package/claude/skills/git-pr/references/branch.md +49 -0
  61. package/claude/skills/git-pr/references/pr.md +124 -0
  62. package/claude/skills/git-ship/REQUIREMENT.md +34 -0
  63. package/claude/skills/git-ship/SKILL.md +39 -0
  64. package/claude/skills/git-split/REQUIREMENT.md +38 -0
  65. package/claude/skills/git-split/SKILL.md +160 -0
  66. package/claude/skills/git-split/references/branch.md +49 -0
  67. package/claude/skills/git-split/references/pr.md +124 -0
  68. package/claude/skills/git-stage/REQUIREMENT.md +36 -0
  69. package/claude/skills/git-stage/SKILL.md +74 -0
  70. package/claude/skills/git-stage/references/commit.md +63 -0
  71. package/claude/skills/git-worktree/REQUIREMENT.md +37 -0
  72. package/claude/skills/git-worktree/SKILL.md +120 -0
  73. package/claude/skills/migration-claude-md/SKILL.md +74 -0
  74. package/claude/skills/migration-context/SKILL.md +93 -0
  75. package/claude/skills/migration-standards/SKILL.md +75 -0
  76. package/claude/skills/project-commands/SKILL.md +73 -0
  77. package/claude/skills/session-resume/SKILL.md +36 -0
  78. package/claude/skills/setup-gov/SKILL.md +71 -0
  79. package/claude/skills/setup-indexes/SKILL.md +148 -0
  80. package/claude/skills/setup-init/SKILL.md +114 -0
  81. package/claude/skills/setup-plugins/SKILL.md +81 -0
  82. package/claude/skills/setup-plugins/references/plugin-catalog.md +53 -0
  83. package/claude/skills/setup-verify/SKILL.md +51 -0
  84. package/claude/skills/systematic-debugging/SKILL.md +70 -0
  85. package/claude/skills/toolkit-cli/SKILL.md +38 -0
  86. package/claude/skills/toolkit-feedback/SKILL.md +80 -0
  87. package/claude/skills/toolkit-operator/SKILL.md +45 -0
  88. package/claude/skills/toolkit-triage/SKILL.md +63 -0
  89. package/claude/skills/youtube-transcripts/SKILL.md +34 -0
  90. package/docs/agents.md +371 -0
  91. package/docs/ai-workflow.md +157 -0
  92. package/docs/index.md +20 -0
  93. package/docs/operating-model.md +106 -0
  94. package/docs/target-projects.md +183 -0
  95. package/docs/visual-design-workflow.md +136 -0
  96. package/docs/zshrc-aliases.md +65 -0
  97. package/governance/rules/claude/500-prose.md +12 -0
  98. package/governance/rules/claude/510-context.md +16 -0
  99. package/governance/rules/claude/520-wireframes.md +12 -0
  100. package/governance/rules/claude/530-requirements.md +11 -0
  101. package/governance/rules/claude/540-architecture.md +11 -0
  102. package/governance/rules/claude/550-design.md +11 -0
  103. package/governance/rules/claude/555-tasks.md +12 -0
  104. package/governance/rules/claude/560-diagrams.md +11 -0
  105. package/governance/rules/claude/570-skill.md +18 -0
  106. package/governance/rules/claude/580-readme.md +11 -0
  107. package/governance/rules/claude/590-rule-authoring.md +12 -0
  108. package/governance/rules/core/000-constitution.md +30 -0
  109. package/governance/rules/core/010-testing.md +35 -0
  110. package/governance/rules/core/020-concurrency.md +22 -0
  111. package/governance/rules/core/030-error-handling.md +31 -0
  112. package/governance/rules/core/040-performance.md +20 -0
  113. package/governance/rules/core/050-logging.md +20 -0
  114. package/governance/rules/core/060-naming.md +19 -0
  115. package/governance/rules/core/070-planning.md +13 -0
  116. package/governance/rules/core/080-config-comments.md +22 -0
  117. package/governance/rules/framework/200-react.md +51 -0
  118. package/governance/rules/framework/210-astro.md +41 -0
  119. package/governance/rules/framework/220-fastapi.md +43 -0
  120. package/governance/rules/framework/230-nextjs.md +48 -0
  121. package/governance/rules/framework/250-tailwind.md +32 -0
  122. package/governance/rules/framework/260-shadcn.md +34 -0
  123. package/governance/rules/lang/100-typescript.md +40 -0
  124. package/governance/rules/lang/110-python.md +42 -0
  125. package/governance/rules/lib/300-testing-ts.md +38 -0
  126. package/governance/rules/lib/310-zod.md +25 -0
  127. package/governance/rules/lib/320-tanstack-query.md +32 -0
  128. package/governance/rules/lib/330-testing-py.md +44 -0
  129. package/governance/rules/lib/340-pydantic.md +38 -0
  130. package/governance/rules/lib/350-security-web.md +31 -0
  131. package/governance/rules/ui/400-ui.md +34 -0
  132. package/governance/rules/ui/410-a11y.md +44 -0
  133. package/governance/rules/ui/420-forms.md +34 -0
  134. package/governance/rules/ui/430-ux-completeness.md +62 -0
  135. package/governance/stacks/astro.toml +2 -0
  136. package/governance/stacks/base.toml +2 -0
  137. package/governance/stacks/node.toml +2 -0
  138. package/governance/stacks/python-fastapi.toml +2 -0
  139. package/governance/stacks/python.toml +2 -0
  140. package/governance/stacks/react.toml +2 -0
  141. package/package.json +67 -0
  142. package/scripts/config.sh +11 -0
  143. package/scripts/core/bootstrap.sh +81 -0
  144. package/scripts/core/check-plugin-boundary.sh +45 -0
  145. package/scripts/core/check-skill-paths.sh +25 -0
  146. package/scripts/core/clean.sh +36 -0
  147. package/scripts/core/install-check.sh +93 -0
  148. package/scripts/core/regen-claude-copies.sh +26 -0
  149. package/scripts/core/regen-indexes.sh +10 -0
  150. package/scripts/core/regen-skill-references.sh +27 -0
  151. package/scripts/core/snapshot.sh +132 -0
  152. package/scripts/core/update.sh +35 -0
  153. package/scripts/core/verify.sh +201 -0
  154. package/scripts/docs/list.sh +129 -0
  155. package/scripts/gov/list.sh +234 -0
  156. package/scripts/lib/frontmatter.sh +30 -0
  157. package/scripts/lib/gov.sh +14 -0
  158. package/scripts/lib/sandbox-fixtures.sh +109 -0
  159. package/scripts/lib/sandbox-git.sh +73 -0
  160. package/scripts/lib/tooling.sh +24 -0
  161. package/scripts/lib/ui.sh +173 -0
  162. package/scripts/lib/worktree.sh +20 -0
  163. package/scripts/manage-sandbox.sh +453 -0
  164. package/scripts/snippets/create.sh +156 -0
  165. package/scripts/standards/list.sh +91 -0
  166. package/scripts/tooling/create.sh +109 -0
  167. package/scripts/tooling/ref.sh +141 -0
  168. package/scripts/tooling/verify.sh +179 -0
  169. package/snippets/claude/decision-memo.md +39 -0
  170. package/snippets/claude/feature-recap.md +19 -0
  171. package/snippets/claude/figma-steps.md +18 -0
  172. package/snippets/claude/orchestrator-resume.md +23 -0
  173. package/snippets/claude/orchestrator-sweep.md +10 -0
  174. package/snippets/compact-summary.md +5 -0
  175. package/snippets/create-snippet.md +7 -0
  176. package/snippets/decision-help.md +6 -0
  177. package/snippets/meta-prompt.md +14 -0
  178. package/snippets/research-prompt.md +7 -0
  179. package/snippets/session-notes.md +11 -0
  180. package/snippets/snippets.toml +6 -0
  181. package/snippets/step-by-step.md +10 -0
  182. package/snippets/web-research.md +21 -0
  183. package/src/claude/gitignore.ts +51 -0
  184. package/src/claude/seeds-list.ts +47 -0
  185. package/src/claude/seeds.ts +124 -0
  186. package/src/claude/settings.ts +151 -0
  187. package/src/cli-run.ts +43 -0
  188. package/src/cli.ts +111 -0
  189. package/src/commands/claude.ts +349 -0
  190. package/src/commands/design.ts +39 -0
  191. package/src/commands/docs.ts +59 -0
  192. package/src/commands/feedback-format.ts +23 -0
  193. package/src/commands/feedback.ts +103 -0
  194. package/src/commands/gov.ts +268 -0
  195. package/src/commands/indexes.ts +182 -0
  196. package/src/commands/init.ts +117 -0
  197. package/src/commands/pass-through.ts +39 -0
  198. package/src/commands/sandbox.ts +315 -0
  199. package/src/commands/slides.ts +106 -0
  200. package/src/commands/snippets.ts +236 -0
  201. package/src/commands/standards.ts +89 -0
  202. package/src/commands/sync.ts +211 -0
  203. package/src/commands/tasks.ts +185 -0
  204. package/src/commands/tooling.ts +432 -0
  205. package/src/commands/transcripts.ts +48 -0
  206. package/src/commands/wiki.ts +101 -0
  207. package/src/copy.ts +30 -0
  208. package/src/design/parse.ts +79 -0
  209. package/src/design/render.ts +170 -0
  210. package/src/docs/read.ts +59 -0
  211. package/src/exec.ts +17 -0
  212. package/src/frontmatter.ts +13 -0
  213. package/src/github-format.ts +13 -0
  214. package/src/github.ts +20 -0
  215. package/src/gov/adapter.ts +73 -0
  216. package/src/gov/install.ts +103 -0
  217. package/src/gov/payload.ts +39 -0
  218. package/src/gov/stacks.ts +124 -0
  219. package/src/indexes/frontmatter.ts +46 -0
  220. package/src/indexes/regen.ts +84 -0
  221. package/src/indexes/render.ts +181 -0
  222. package/src/indexes/walk.ts +111 -0
  223. package/src/init/flags.ts +66 -0
  224. package/src/init/plan.ts +128 -0
  225. package/src/init/run.ts +46 -0
  226. package/src/init/steps.ts +91 -0
  227. package/src/sandbox/coverage.ts +115 -0
  228. package/src/sandbox/expect.ts +412 -0
  229. package/src/slides/layouts.ts +391 -0
  230. package/src/slides/open.ts +18 -0
  231. package/src/slides/parse.ts +84 -0
  232. package/src/slides/render.ts +88 -0
  233. package/src/slides/styles.ts +44 -0
  234. package/src/snippets/adapter.ts +34 -0
  235. package/src/snippets/categories.ts +59 -0
  236. package/src/snippets/install.ts +125 -0
  237. package/src/snippets/list.ts +32 -0
  238. package/src/snippets/presets.ts +50 -0
  239. package/src/standards/adapter.ts +51 -0
  240. package/src/standards/index-refresh.ts +44 -0
  241. package/src/standards/install.ts +52 -0
  242. package/src/sync/check.ts +269 -0
  243. package/src/sync/engine.ts +416 -0
  244. package/src/sync/git.ts +204 -0
  245. package/src/sync/stamp.ts +202 -0
  246. package/src/sync/target.ts +79 -0
  247. package/src/sync/workflow.ts +201 -0
  248. package/src/target.ts +43 -0
  249. package/src/tasks/archive.ts +304 -0
  250. package/src/tooling/gitignore.ts +122 -0
  251. package/src/tooling/inject.ts +214 -0
  252. package/src/tooling/list.ts +39 -0
  253. package/src/tooling/manifest.ts +178 -0
  254. package/src/tooling/package.ts +166 -0
  255. package/src/tooling/scan.ts +186 -0
  256. package/src/transcripts/fetch.ts +156 -0
  257. package/src/transcripts/metadata.ts +54 -0
  258. package/src/transcripts/vtt.ts +114 -0
  259. package/src/ui.ts +187 -0
  260. package/src/wiki/init.ts +85 -0
  261. package/standards/architecture.md +41 -0
  262. package/standards/bundled/branch.md +49 -0
  263. package/standards/bundled/commit.md +63 -0
  264. package/standards/bundled/issue.md +84 -0
  265. package/standards/bundled/pr.md +124 -0
  266. package/standards/bundled/roadmap.md +52 -0
  267. package/standards/bundled/snippets.md +57 -0
  268. package/standards/bundled/standard.md +52 -0
  269. package/standards/context.md +132 -0
  270. package/standards/design.md +31 -0
  271. package/standards/diagrams.md +55 -0
  272. package/standards/index.md +21 -0
  273. package/standards/prose.md +122 -0
  274. package/standards/readme.md +163 -0
  275. package/standards/requirements.md +61 -0
  276. package/standards/rule.md +79 -0
  277. package/standards/skill.md +257 -0
  278. package/standards/tasks.md +140 -0
  279. package/standards/versioning.md +47 -0
  280. package/standards/wireframes.md +67 -0
  281. package/tooling/astro/configs/astro.config.mjs +17 -0
  282. package/tooling/astro/configs/eslint.config.js +79 -0
  283. package/tooling/astro/configs/playwright.config.ts +25 -0
  284. package/tooling/astro/configs/tsconfig.json +12 -0
  285. package/tooling/astro/configs/vitest.config.ts +22 -0
  286. package/tooling/astro/manifest.toml +32 -0
  287. package/tooling/astro/reference.md +60 -0
  288. package/tooling/base/configs/.editorconfig +5 -0
  289. package/tooling/base/configs/.github/pull_request_template.md +18 -0
  290. package/tooling/base/configs/.github/workflows/verify.yml +35 -0
  291. package/tooling/base/configs/.husky/commit-msg +1 -0
  292. package/tooling/base/configs/.husky/post-merge +61 -0
  293. package/tooling/base/configs/.husky/post-rewrite +21 -0
  294. package/tooling/base/configs/.husky/pre-commit +1 -0
  295. package/tooling/base/configs/.husky/pre-push +1 -0
  296. package/tooling/base/configs/.prettierrc +12 -0
  297. package/tooling/base/configs/.shellcheckrc +1 -0
  298. package/tooling/base/configs/.vscode/extensions.json +9 -0
  299. package/tooling/base/configs/.vscode/settings.json +3 -0
  300. package/tooling/base/configs/commitlint.config.js +11 -0
  301. package/tooling/base/configs/scripts/clean.sh +45 -0
  302. package/tooling/base/configs/scripts/update.sh +49 -0
  303. package/tooling/base/configs/scripts/verify.sh +64 -0
  304. package/tooling/base/manifest.toml +32 -0
  305. package/tooling/base/reference.md +114 -0
  306. package/tooling/base/seeds/.claude/context/ci.md +27 -0
  307. package/tooling/base/seeds/.claude/context/development.md +32 -0
  308. package/tooling/base/seeds/.cspell/project-terms.txt +0 -0
  309. package/tooling/base/seeds/.cspell/tech-stack.txt +19 -0
  310. package/tooling/base/seeds/.lintstagedrc +8 -0
  311. package/tooling/base/seeds/.prettierignore +0 -0
  312. package/tooling/base/seeds/cspell.json +20 -0
  313. package/tooling/claude/manifest.toml +11 -0
  314. package/tooling/claude/reference.md +62 -0
  315. package/tooling/claude/seeds/.claude/ARCHITECTURE.md +13 -0
  316. package/tooling/claude/seeds/.claude/DESIGN.md +62 -0
  317. package/tooling/claude/seeds/.claude/REQUIREMENTS.md +18 -0
  318. package/tooling/claude/seeds/.claude/context/index.md +8 -0
  319. package/tooling/claude/seeds/.claude/hooks/index-reminder.sh +43 -0
  320. package/tooling/claude/seeds/.claude/hooks/scratch-guard.sh +45 -0
  321. package/tooling/claude/seeds/.claude/hooks/standards-audit.sh +61 -0
  322. package/tooling/claude/seeds/.claude/hooks/tasks-index.sh +60 -0
  323. package/tooling/claude/seeds/.claude/settings.json +39 -0
  324. package/tooling/claude/seeds/.claude/tasks/index.md +8 -0
  325. package/tooling/claude/seeds/.claude/wireframes/index.md +8 -0
  326. package/tooling/claude/seeds/CLAUDE.md +87 -0
  327. package/tooling/claude/user/settings.template.json +10 -0
  328. package/tooling/claude/user/statusline-command.sh +53 -0
  329. package/tooling/python/configs/.coveragerc +14 -0
  330. package/tooling/python/configs/.python-version +1 -0
  331. package/tooling/python/configs/mypy.ini +6 -0
  332. package/tooling/python/configs/pytest.ini +4 -0
  333. package/tooling/python/configs/ruff.toml +15 -0
  334. package/tooling/python/configs/scripts/verify.sh +77 -0
  335. package/tooling/python/manifest.toml +16 -0
  336. package/tooling/python/reference.md +66 -0
  337. package/tooling/python/seeds/.cspell/tech-stack.txt +19 -0
  338. package/tooling/python/seeds/tests/test_smoke.py +2 -0
  339. package/tooling/vite-react/configs/playwright.config.ts +25 -0
  340. package/tooling/vite-react/configs/tsconfig.json +35 -0
  341. package/tooling/vite-react/configs/vite.config.ts +14 -0
  342. package/tooling/vite-react/configs/vitest.config.ts +26 -0
  343. package/tooling/vite-react/manifest.toml +21 -0
  344. package/tooling/vite-react/reference.md +55 -0
  345. package/tooling/vite-react/seeds/.cspell/project-terms.txt +1 -0
  346. package/tooling/vite-react/seeds/.cspell/tech-stack.txt +1 -0
  347. package/tooling/web/configs/.github/workflows/verify.yml +134 -0
  348. package/tooling/web/configs/.vscode/extensions.json +13 -0
  349. package/tooling/web/configs/.vscode/settings.json +10 -0
  350. package/tooling/web/configs/e2e/home.spec.ts +6 -0
  351. package/tooling/web/configs/e2e/screenshot.ts +41 -0
  352. package/tooling/web/configs/eslint.config.js +82 -0
  353. package/tooling/web/configs/scripts/screenshot.sh +28 -0
  354. package/tooling/web/configs/scripts/verify.sh +80 -0
  355. package/tooling/web/configs/src/test/setup.ts +8 -0
  356. package/tooling/web/manifest.toml +57 -0
  357. package/tooling/web/reference.md +95 -0
  358. package/tooling/web/seeds/.cspell/tech-stack.txt +18 -0
  359. package/tsconfig.json +14 -0
@@ -0,0 +1,84 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readField, readFrontmatter } from '@/indexes/frontmatter'
3
+ import { computeIndex } from '@/indexes/render'
4
+ import { INDEX_FILE } from '@/indexes/walk'
5
+
6
+ export const REGEN_ACTIONS = [
7
+ 'written',
8
+ 'would-write',
9
+ 'unchanged',
10
+ 'skipped',
11
+ 'error',
12
+ ] as const
13
+
14
+ export type RegenAction = (typeof REGEN_ACTIONS)[number]
15
+
16
+ export interface RegenResult {
17
+ readonly path: string
18
+ readonly action: RegenAction
19
+ readonly reason?: string
20
+ readonly errors?: string[]
21
+ }
22
+
23
+ export interface RegenOptions {
24
+ readonly dryRun: boolean
25
+ }
26
+
27
+ /**
28
+ * Regenerates one folder's index and reports what happened.
29
+ *
30
+ * A folder marked `auto: false` is skipped rather than errored, which is how
31
+ * hand-maintained catalogs opt out of the walker.
32
+ */
33
+ export async function regenOne(
34
+ dir: string,
35
+ options: RegenOptions,
36
+ ): Promise<RegenResult> {
37
+ const path = `${dir}/${INDEX_FILE}`
38
+
39
+ if (!existsSync(path)) {
40
+ return { path, action: 'error', reason: 'no index.md' }
41
+ }
42
+
43
+ const auto = readField(await readFrontmatter(path), 'auto')
44
+ if (auto === 'false') {
45
+ return { path, action: 'skipped', reason: 'auto:false' }
46
+ }
47
+
48
+ const rendered = await computeIndex(dir)
49
+ if (!rendered.ok) {
50
+ return {
51
+ path,
52
+ action: 'error',
53
+ reason: 'frontmatter',
54
+ errors: rendered.errors,
55
+ }
56
+ }
57
+
58
+ if (rendered.content === (await Bun.file(path).text())) {
59
+ return { path, action: 'unchanged' }
60
+ }
61
+
62
+ if (options.dryRun) {
63
+ return { path, action: 'would-write' }
64
+ }
65
+
66
+ await Bun.write(path, rendered.content)
67
+ return { path, action: 'written' }
68
+ }
69
+
70
+ /**
71
+ * Maps a run's results onto the documented exit codes: 0 clean, 1 error,
72
+ * 2 drift found under `--dry-run`. Skills branch on the JSON instead, but the
73
+ * codes are load-bearing for `bun run check`.
74
+ */
75
+ export function exitCodeFor(
76
+ results: readonly RegenResult[],
77
+ options: RegenOptions,
78
+ ): 0 | 1 | 2 {
79
+ if (results.some((result) => result.action === 'error')) return 1
80
+ if (options.dryRun && results.some((r) => r.action === 'would-write')) {
81
+ return 2
82
+ }
83
+ return 0
84
+ }
@@ -0,0 +1,181 @@
1
+ import { basename, dirname, resolve } from 'node:path'
2
+ import { readField, readFrontmatter } from '@/indexes/frontmatter'
3
+ import { INDEX_FILE, listIndexes } from '@/indexes/walk'
4
+
5
+ export interface IndexEntry {
6
+ readonly name: string
7
+ readonly title: string
8
+ readonly description: string
9
+ readonly category?: string
10
+ }
11
+
12
+ export interface SubCatalog {
13
+ readonly name: string
14
+ readonly title: string
15
+ readonly subtitle: string
16
+ }
17
+
18
+ export type RenderResult =
19
+ | { readonly ok: true; readonly content: string }
20
+ | { readonly ok: false; readonly errors: string[] }
21
+
22
+ interface IndexHead {
23
+ readonly raw: string
24
+ readonly title: string
25
+ readonly subtitle: string
26
+ }
27
+
28
+ /**
29
+ * Builds a folder's `index.md` from its own frontmatter plus every sibling's.
30
+ *
31
+ * Frontmatter is re-emitted verbatim rather than re-serialized, so key order
32
+ * and any `auto: false` marker survive a regeneration untouched.
33
+ */
34
+ export async function computeIndex(dir: string): Promise<RenderResult> {
35
+ const indexPath = `${dir}/${INDEX_FILE}`
36
+ const head = await readIndexHead(indexPath)
37
+ if (!head.ok) return head
38
+
39
+ const entries = await collectEntries(dir)
40
+ if (!entries.ok) return entries
41
+
42
+ const subCatalogs = await collectSubCatalogs(dir)
43
+
44
+ return {
45
+ ok: true,
46
+ content: renderIndex(head.head, entries.entries, subCatalogs),
47
+ }
48
+ }
49
+
50
+ async function readIndexHead(
51
+ indexPath: string,
52
+ ): Promise<{ ok: true; head: IndexHead } | { ok: false; errors: string[] }> {
53
+ const frontmatter = await readFrontmatter(indexPath)
54
+ const title = readField(frontmatter, 'title')
55
+ const subtitle = readField(frontmatter, 'subtitle')
56
+
57
+ const errors: string[] = []
58
+ if (!title) errors.push(`missing frontmatter field "title" in ${indexPath}`)
59
+ if (!subtitle) {
60
+ errors.push(`missing frontmatter field "subtitle" in ${indexPath}`)
61
+ }
62
+ if (!frontmatter || !title || !subtitle) return { ok: false, errors }
63
+
64
+ return { ok: true, head: { raw: frontmatter.raw, title, subtitle } }
65
+ }
66
+
67
+ async function collectEntries(
68
+ dir: string,
69
+ ): Promise<
70
+ { ok: true; entries: IndexEntry[] } | { ok: false; errors: string[] }
71
+ > {
72
+ const glob = new Bun.Glob('*.md')
73
+ const paths: string[] = []
74
+
75
+ for await (const rel of glob.scan({ cwd: dir, onlyFiles: true, dot: true })) {
76
+ if (rel === INDEX_FILE) continue
77
+ paths.push(resolve(dir, rel))
78
+ }
79
+ paths.sort()
80
+
81
+ const entries: IndexEntry[] = []
82
+ const errors: string[] = []
83
+
84
+ for (const path of paths) {
85
+ const frontmatter = await readFrontmatter(path)
86
+ const title = readField(frontmatter, 'title')
87
+ const description = readField(frontmatter, 'description')
88
+
89
+ if (!title) errors.push(`missing frontmatter field "title" in ${path}`)
90
+ if (!description) {
91
+ errors.push(`missing frontmatter field "description" in ${path}`)
92
+ }
93
+ if (!title || !description) continue
94
+
95
+ entries.push({
96
+ name: basename(path),
97
+ title,
98
+ description,
99
+ category: readField(frontmatter, 'category'),
100
+ })
101
+ }
102
+
103
+ if (errors.length > 0) {
104
+ errors.push(`${errors.length} missing frontmatter field(s) in ${dir}`)
105
+ return { ok: false, errors }
106
+ }
107
+
108
+ return { ok: true, entries }
109
+ }
110
+
111
+ /**
112
+ * Reuses the shared walker so nested discovery inherits the same prune and
113
+ * gitignore rules, then keeps only direct children of this folder.
114
+ */
115
+ async function collectSubCatalogs(dir: string): Promise<SubCatalog[]> {
116
+ const nested = await listIndexes(dir)
117
+ const catalogs: SubCatalog[] = []
118
+
119
+ for (const indexPath of nested.sort()) {
120
+ const childDir = dirname(indexPath)
121
+ if (dirname(childDir) !== resolve(dir)) continue
122
+
123
+ const frontmatter = await readFrontmatter(indexPath)
124
+ const title = readField(frontmatter, 'title')
125
+ const subtitle = readField(frontmatter, 'subtitle')
126
+
127
+ if (!title || !subtitle) {
128
+ process.stderr.write(
129
+ `WARNING: skipping nested index ${childDir}/${INDEX_FILE} (missing title or subtitle)\n`,
130
+ )
131
+ continue
132
+ }
133
+
134
+ catalogs.push({ name: basename(childDir), title, subtitle })
135
+ }
136
+
137
+ return catalogs
138
+ }
139
+
140
+ function renderIndex(
141
+ head: IndexHead,
142
+ entries: IndexEntry[],
143
+ subCatalogs: SubCatalog[],
144
+ ): string {
145
+ const hasCategories = entries.some((entry) => Boolean(entry.category))
146
+
147
+ let out = `${head.raw}\n\n# ${head.title}\n\n${head.subtitle}\n`
148
+ if (entries.length > 0 || subCatalogs.length > 0) out += '\n'
149
+
150
+ if (hasCategories) {
151
+ const categories = [
152
+ ...new Set(entries.map((entry) => entry.category).filter(Boolean)),
153
+ ].sort() as string[]
154
+
155
+ categories.forEach((category, position) => {
156
+ if (position > 0) out += '\n'
157
+ out += `## ${category}\n\n`
158
+ for (const entry of entries) {
159
+ if (entry.category !== category) continue
160
+ out += formatEntry(entry)
161
+ }
162
+ })
163
+ } else {
164
+ for (const entry of entries) {
165
+ out += formatEntry(entry)
166
+ }
167
+ }
168
+
169
+ if (subCatalogs.length > 0) {
170
+ if (hasCategories) out += '\n## Sub-catalogs\n\n'
171
+ for (const catalog of subCatalogs) {
172
+ out += `- [${catalog.title}](${catalog.name}/${INDEX_FILE}): ${catalog.subtitle}\n`
173
+ }
174
+ }
175
+
176
+ return out
177
+ }
178
+
179
+ function formatEntry(entry: IndexEntry): string {
180
+ return `- [${entry.title}](${entry.name}): ${entry.description}\n`
181
+ }
@@ -0,0 +1,111 @@
1
+ import { existsSync, statSync } from 'node:fs'
2
+ import { dirname, resolve } from 'node:path'
3
+ import { $ } from 'bun'
4
+
5
+ const INDEX_FILE = 'index.md'
6
+
7
+ const PRUNED_SEGMENTS = ['node_modules', '.git']
8
+
9
+ function isPruned(relativePath: string): boolean {
10
+ return relativePath
11
+ .split('/')
12
+ .some((segment) => PRUNED_SEGMENTS.includes(segment))
13
+ }
14
+
15
+ /**
16
+ * Lists every folder index under `root`.
17
+ *
18
+ * Vendored and git trees are pruned by path segment, since `Bun.Glob` has no
19
+ * exclude. Everything else is dropped via `git check-ignore`, so a project's
20
+ * own `.gitignore` governs what the walker sees. Outside a git repo the
21
+ * segment prune is the only filter that applies.
22
+ */
23
+ export async function listIndexes(root: string): Promise<string[]> {
24
+ const glob = new Bun.Glob(`**/${INDEX_FILE}`)
25
+ const candidates: string[] = []
26
+
27
+ for await (const rel of glob.scan({
28
+ cwd: root,
29
+ onlyFiles: true,
30
+ dot: true,
31
+ })) {
32
+ if (isPruned(rel)) continue
33
+ candidates.push(resolve(root, rel))
34
+ }
35
+ candidates.sort()
36
+
37
+ if (candidates.length === 0) return []
38
+
39
+ const ignored = await listIgnored(root, candidates)
40
+ if (ignored.size === 0) return candidates
41
+
42
+ return candidates.filter((path) => !ignored.has(path))
43
+ }
44
+
45
+ async function listIgnored(
46
+ root: string,
47
+ candidates: string[],
48
+ ): Promise<Set<string>> {
49
+ const isRepo = await $`git -C ${root} rev-parse --git-dir`
50
+ .quiet()
51
+ .nothrow()
52
+ .then((result) => result.exitCode === 0)
53
+
54
+ if (!isRepo) return new Set()
55
+
56
+ const stdin = Buffer.from(`${candidates.join('\n')}\n`)
57
+
58
+ const result = await $`git -C ${root} check-ignore --stdin < ${stdin}`
59
+ .quiet()
60
+ .nothrow()
61
+
62
+ if (result.exitCode > 1) return new Set()
63
+
64
+ return new Set(
65
+ result
66
+ .text()
67
+ .split('\n')
68
+ .filter(Boolean)
69
+ .map((path) => resolve(root, path)),
70
+ )
71
+ }
72
+
73
+ /**
74
+ * Reports whether git ignores `path`.
75
+ *
76
+ * Positional regen reaches an ignored folder, because `findIndexedAncestor`
77
+ * walks the filesystem and never consults git. Staging what it writes there
78
+ * always fails, so the caller checks this first and skips the attempt rather
79
+ * than warning on a no-op.
80
+ */
81
+ export async function isIgnored(root: string, path: string): Promise<boolean> {
82
+ const ignored = await listIgnored(root, [resolve(path)])
83
+ return ignored.has(resolve(path))
84
+ }
85
+
86
+ /**
87
+ * Walks up from `path` to the nearest folder holding an index, stopping at
88
+ * `root`. Returns undefined when the path escapes the boundary or nothing
89
+ * indexed is found.
90
+ */
91
+ export function findIndexedAncestor(
92
+ path: string,
93
+ root: string,
94
+ ): string | undefined {
95
+ if (!existsSync(path)) return undefined
96
+
97
+ const rootAbs = resolve(root)
98
+ let dir = statSync(path).isDirectory()
99
+ ? resolve(path)
100
+ : dirname(resolve(path))
101
+
102
+ while (dir === rootAbs || dir.startsWith(`${rootAbs}/`)) {
103
+ if (existsSync(`${dir}/${INDEX_FILE}`)) return dir
104
+ if (dir === rootAbs) return undefined
105
+ dir = dirname(dir)
106
+ }
107
+
108
+ return undefined
109
+ }
110
+
111
+ export { INDEX_FILE, PRUNED_SEGMENTS }
@@ -0,0 +1,66 @@
1
+ import type { Command } from 'commander'
2
+ import { DEFAULT_STACK, SKIPPABLE_DOMAINS } from '@/init/plan'
3
+
4
+ interface InitOptionSpec {
5
+ /** The option value key commander stores the parsed value under. */
6
+ readonly key: string
7
+ readonly flags: string
8
+ readonly description: string
9
+ readonly defaultValue?: string
10
+ }
11
+
12
+ /**
13
+ * The option surface of `aitk init`, declared once so the flag list and the
14
+ * provenance check cannot drift apart. Reading the key from the spec is what
15
+ * keeps a renamed flag from silently dropping out of `flagsProvided`.
16
+ */
17
+ export const INIT_OPTIONS: readonly InitOptionSpec[] = [
18
+ {
19
+ key: 'stack',
20
+ flags: '--stack <name>',
21
+ description: 'Governance stack (e.g., base, astro, react)',
22
+ defaultValue: DEFAULT_STACK,
23
+ },
24
+ {
25
+ key: 'add',
26
+ flags: '--add <rules>',
27
+ description: 'Comma-separated governance rules to layer on',
28
+ },
29
+ {
30
+ key: 'snippets',
31
+ flags: '--snippets <category>',
32
+ description: "Snippets preset, category, or 'all'",
33
+ defaultValue: 'essentials',
34
+ },
35
+ {
36
+ key: 'skip',
37
+ flags: '--skip <list>',
38
+ description: `Skip core domains: ${SKIPPABLE_DOMAINS.join(', ')}`,
39
+ },
40
+ ]
41
+
42
+ /** Declares every init option on the command, defaults included. */
43
+ export function applyInitOptions(command: Command): Command {
44
+ for (const option of INIT_OPTIONS) {
45
+ if (option.defaultValue === undefined) {
46
+ command.option(option.flags, option.description)
47
+ continue
48
+ }
49
+
50
+ command.option(option.flags, option.description, option.defaultValue)
51
+ }
52
+
53
+ return command
54
+ }
55
+
56
+ /**
57
+ * Whether the operator passed any flag, which is what makes the command
58
+ * scriptable by suppressing the confirmation prompt. `--snippets` and `--stack`
59
+ * both carry defaults, so presence has to be read from where the value came
60
+ * from rather than from the value itself.
61
+ */
62
+ export function flagsProvided(cmd: Command): boolean {
63
+ return INIT_OPTIONS.some(
64
+ (option) => cmd.getOptionValueSource(option.key) === 'cli',
65
+ )
66
+ }
@@ -0,0 +1,128 @@
1
+ export const SKIPPABLE_DOMAINS = ['wiki', 'standards', 'governance'] as const
2
+
3
+ export type SkippableDomain = (typeof SKIPPABLE_DOMAINS)[number]
4
+
5
+ /**
6
+ * The stack a caller gets without asking. Governance installs on every init so
7
+ * the standards that install alongside it arrive with the rules that route to
8
+ * them, and `--skip governance` is the one spelling for declining.
9
+ */
10
+ export const DEFAULT_STACK = 'base'
11
+
12
+ export interface SkipPlan {
13
+ readonly skipped: ReadonlySet<SkippableDomain>
14
+ /** Values that named nothing skippable. Warned about, never fatal. */
15
+ readonly unknown: readonly string[]
16
+ }
17
+
18
+ export type PreviewLevel = 'info' | 'warn'
19
+
20
+ export interface PreviewLine {
21
+ readonly level: PreviewLevel
22
+ readonly text: string
23
+ }
24
+
25
+ export interface InitPlan {
26
+ readonly preview: readonly PreviewLine[]
27
+ readonly total: number
28
+ }
29
+
30
+ export interface InitFlags {
31
+ readonly stack?: string
32
+ readonly add?: string
33
+ readonly snippets: string
34
+ readonly skip: SkipPlan
35
+ }
36
+
37
+ /**
38
+ * Resolves the stack the run will install. An empty `--stack` reads as absent
39
+ * rather than as a way to decline, so the flag carries a real name or nothing
40
+ * and `--skip governance` stays the only spelling for opting out.
41
+ */
42
+ export function resolveStack(stack: string | undefined): string {
43
+ return stack === undefined || stack === '' ? DEFAULT_STACK : stack
44
+ }
45
+
46
+ /**
47
+ * Reads the `--skip` list. An unrecognized value is reported and dropped rather
48
+ * than aborting, because the flag names optional domains and a typo should not
49
+ * cost the operator the whole init.
50
+ */
51
+ export function parseSkip(csv: string | undefined): SkipPlan {
52
+ const skipped = new Set<SkippableDomain>()
53
+ const unknown: string[] = []
54
+
55
+ for (const raw of (csv ?? '').split(',')) {
56
+ const item = raw.trim()
57
+ if (item === '') continue
58
+
59
+ if (isSkippable(item)) skipped.add(item)
60
+ else unknown.push(item)
61
+ }
62
+
63
+ return { skipped, unknown }
64
+ }
65
+
66
+ /**
67
+ * Builds the preview and the count from one pass, so the two cannot disagree.
68
+ * Every `info` line is a domain that will run, which is what makes the count a
69
+ * filter rather than a second tally kept in step by hand. A skipped domain
70
+ * prints nothing at all and declined governance prints a warning, so neither
71
+ * reaches the total.
72
+ *
73
+ * Governance is the one skip that costs another domain something, so its
74
+ * warning names the consequence rather than only the action. Declining
75
+ * standards too removes that consequence, and the warning drops it.
76
+ *
77
+ * The skip also drops `--add`, which names rules the caller asked for. Input
78
+ * that goes nowhere is reported for the same reason `parseSkip` reports an
79
+ * unrecognized value, so the warning names the flag rather than dropping it
80
+ * without a word.
81
+ */
82
+ export function planInit(flags: InitFlags): InitPlan {
83
+ const preview: PreviewLine[] = [
84
+ { level: 'info', text: 'base tooling (configs, seeds, deps, scripts)' },
85
+ { level: 'info', text: 'claude (workflow docs, settings)' },
86
+ ]
87
+
88
+ const stack = resolveStack(flags.stack)
89
+
90
+ if (flags.skip.skipped.has('governance')) {
91
+ const notes: string[] = []
92
+ if (flags.add !== undefined && flags.add !== '')
93
+ notes.push(`--add ${flags.add} not installed`)
94
+ if (!flags.skip.skipped.has('standards'))
95
+ notes.push('standards land without the rules that route to them')
96
+
97
+ const detail = notes.length === 0 ? '' : `, ${notes.join(', ')}`
98
+ preview.push({ level: 'warn', text: `governance (skipped${detail})` })
99
+ } else if (flags.add === undefined || flags.add === '') {
100
+ preview.push({ level: 'info', text: `governance (stack: ${stack})` })
101
+ } else {
102
+ preview.push({
103
+ level: 'info',
104
+ text: `governance (stack: ${stack}, extras: ${flags.add})`,
105
+ })
106
+ }
107
+
108
+ if (!flags.skip.skipped.has('standards')) {
109
+ preview.push({ level: 'info', text: 'standards (authoring conventions)' })
110
+ }
111
+
112
+ preview.push({ level: 'info', text: `snippets (${flags.snippets})` })
113
+
114
+ if (!flags.skip.skipped.has('wiki')) {
115
+ preview.push({
116
+ level: 'info',
117
+ text: 'wiki (.claude/wiki/ with a stub index)',
118
+ })
119
+ }
120
+
121
+ const total = preview.filter((line) => line.level === 'info').length
122
+
123
+ return { preview, total }
124
+ }
125
+
126
+ function isSkippable(value: string): value is SkippableDomain {
127
+ return (SKIPPABLE_DOMAINS as readonly string[]).includes(value)
128
+ }
@@ -0,0 +1,46 @@
1
+ import { logInfo, logStep, logWarn } from '@/ui'
2
+
3
+ /**
4
+ * A domain either runs or announces why it did not. Governance under
5
+ * `--skip governance` is the second kind: it reports in sequence so the
6
+ * timeline still accounts for every core domain, without counting as a failure.
7
+ */
8
+ export type DomainStep =
9
+ | { readonly kind: 'run'; readonly label: string; run(): Promise<boolean> }
10
+ | { readonly kind: 'skip'; readonly label: string; readonly notice: string }
11
+
12
+ /**
13
+ * Runs each step in order and returns the labels that failed.
14
+ *
15
+ * Partial failure is a first-class outcome rather than an error path. An init
16
+ * that aborts on the first failing domain would look tidier and leave the
17
+ * operator with no idea which of the remaining domains would have worked, so
18
+ * the run continues and the caller reports the failures at the end.
19
+ *
20
+ * The sequence is deliberate. Base tooling seeds the files the later domains
21
+ * install alongside, so these are not independent operations to batch.
22
+ */
23
+ export async function runDomains(
24
+ steps: readonly DomainStep[],
25
+ ): Promise<string[]> {
26
+ const failed: string[] = []
27
+
28
+ for (const step of steps) {
29
+ logStep(step.label)
30
+
31
+ if (step.kind === 'skip') {
32
+ logWarn(step.notice)
33
+ continue
34
+ }
35
+
36
+ if (await step.run()) {
37
+ logInfo('Done')
38
+ continue
39
+ }
40
+
41
+ logWarn('Failed, run manually')
42
+ failed.push(step.label)
43
+ }
44
+
45
+ return failed
46
+ }
@@ -0,0 +1,91 @@
1
+ import { type InitFlags, resolveStack } from '@/init/plan'
2
+ import type { DomainStep } from '@/init/run'
3
+
4
+ /** Builds the child-process invocation for one domain. */
5
+ export type RunFactory = (args: readonly string[]) => () => Promise<boolean>
6
+
7
+ /**
8
+ * Orders the domains an init installs. Base tooling seeds the files the later
9
+ * domains install alongside, so the sequence is part of the contract rather
10
+ * than an arbitrary listing.
11
+ *
12
+ * The caller supplies the run factory so the list can be read for its labels
13
+ * and kinds without spawning anything.
14
+ */
15
+ export function buildSteps(
16
+ target: string,
17
+ resolved: string,
18
+ flags: InitFlags,
19
+ child: RunFactory,
20
+ ): DomainStep[] {
21
+ const steps: DomainStep[] = [
22
+ {
23
+ kind: 'run',
24
+ label: 'Base tooling',
25
+ run: child(['tooling', 'sync', 'base', resolved]),
26
+ },
27
+ {
28
+ kind: 'run',
29
+ label: 'Claude workflow',
30
+ run: child(['claude', 'init', resolved]),
31
+ },
32
+ ]
33
+
34
+ const stack = resolveStack(flags.stack)
35
+
36
+ if (flags.skip.skipped.has('governance')) {
37
+ const recovery = govArgs(stack, flags.add, target).join(' ')
38
+ steps.push({
39
+ kind: 'skip',
40
+ label: 'Governance',
41
+ notice: `Skipped: --skip governance. Run 'aitk ${recovery}' to install rules.`,
42
+ })
43
+ } else {
44
+ steps.push({
45
+ kind: 'run',
46
+ label: 'Governance',
47
+ run: child(govArgs(stack, flags.add, resolved)),
48
+ })
49
+ }
50
+
51
+ if (!flags.skip.skipped.has('standards')) {
52
+ steps.push({
53
+ kind: 'run',
54
+ label: 'Standards',
55
+ run: child(['standards', 'install', resolved]),
56
+ })
57
+ }
58
+
59
+ steps.push({
60
+ kind: 'run',
61
+ label: 'Snippets',
62
+ run: child(['snippets', 'install', flags.snippets, resolved]),
63
+ })
64
+
65
+ if (!flags.skip.skipped.has('wiki')) {
66
+ steps.push({
67
+ kind: 'run',
68
+ label: 'Wiki',
69
+ run: child(['wiki', 'init', resolved]),
70
+ })
71
+ }
72
+
73
+ return steps
74
+ }
75
+
76
+ /**
77
+ * Builds the `gov install` argv. The run and the recovery command a skip prints
78
+ * come from here both, so the command a caller is told to paste installs what
79
+ * the run would have.
80
+ */
81
+ function govArgs(
82
+ stack: string,
83
+ add: string | undefined,
84
+ path: string,
85
+ ): string[] {
86
+ const args = ['gov', 'install', stack]
87
+ if (add !== undefined && add !== '') args.push('--add', add)
88
+ args.push(path)
89
+
90
+ return args
91
+ }