@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,432 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { execScript, PROJECT_ROOT } from '@/exec'
4
+ import {
5
+ applyReferences,
6
+ injectConfigs,
7
+ injectGitignore,
8
+ injectManifest,
9
+ injectSeeds,
10
+ pruneGitignore,
11
+ } from '@/tooling/inject'
12
+ import { buildStackSummaries, describeStack } from '@/tooling/list'
13
+ import {
14
+ isStackExcluded,
15
+ listStacks,
16
+ type Manifest,
17
+ resolveChain,
18
+ stackExists,
19
+ } from '@/tooling/manifest'
20
+ import { scan, type ScanResult } from '@/tooling/scan'
21
+ import { intro, logAdd, logInfo, logStep, logWarn, outro, select } from '@/ui'
22
+
23
+ const GREEN = '\x1b[0;32m'
24
+ const NC = '\x1b[0m'
25
+
26
+ const PASS_THROUGH_VERBS = ['ref', 'create', 'verify'] as const
27
+
28
+ interface SyncOptions {
29
+ readonly ref?: boolean
30
+ readonly skip?: string
31
+ }
32
+
33
+ interface InjectOptions {
34
+ readonly configs?: boolean
35
+ readonly seeds?: boolean
36
+ readonly manifest?: boolean
37
+ readonly gitignore?: boolean
38
+ readonly nested?: boolean
39
+ }
40
+
41
+ interface PruneOptions {
42
+ readonly nested?: boolean
43
+ }
44
+
45
+ interface ListOptions {
46
+ readonly json?: boolean
47
+ }
48
+
49
+ type Prepared =
50
+ | { readonly ok: true; readonly chain: Manifest[]; readonly target: string }
51
+ | { readonly ok: false; readonly error: string }
52
+
53
+ export function register(program: Command): void {
54
+ const tooling = program
55
+ .command('tooling')
56
+ .description('Manage tooling stacks (sync, ref, create)')
57
+ .helpOption('-h, --help', 'Show this help message')
58
+
59
+ tooling
60
+ .command('sync')
61
+ .description('Sync configs, seeds, deps, scripts, and gitignore entries')
62
+ .argument('[stack]', 'Tooling stack name (e.g. base, vite-react)')
63
+ .argument('[target]', 'Target directory', '.')
64
+ .helpOption('-h, --help', 'Show this help message')
65
+ .option('--no-ref', 'Skip dropping reference docs')
66
+ .option('--skip <stack>', 'Drop a layer from the extends chain')
67
+ .action(
68
+ async (stack: string | undefined, target: string, opts: SyncOptions) => {
69
+ process.exitCode = await runSync(stack, target, opts)
70
+ },
71
+ )
72
+
73
+ tooling
74
+ .command('inject')
75
+ .description('Apply one stack to a target without scanning or prompting')
76
+ .argument('<stack>', 'Tooling stack name')
77
+ .argument('[target]', 'Target directory', '.')
78
+ .helpOption('-h, --help', 'Show this help message')
79
+ .option('--configs', 'Copy stack configs')
80
+ .option('--seeds', 'Merge stack seeds')
81
+ .option('--manifest', 'Install deps, apply scripts, merge gitignore')
82
+ .option('--gitignore', 'Merge gitignore entries only')
83
+ .option('--nested', 'Suppress the frame when a caller already opened one')
84
+ .action(async (stack: string, target: string, opts: InjectOptions) => {
85
+ process.exitCode = await runInject(stack, target, opts)
86
+ })
87
+
88
+ tooling
89
+ .command('prune-gitignore')
90
+ .description('Remove managed gitignore entries no longer in the manifest')
91
+ .argument('<stack>', 'Tooling stack name')
92
+ .argument('[target]', 'Target directory', '.')
93
+ .helpOption('-h, --help', 'Show this help message')
94
+ .option('--nested', 'Suppress the frame when a caller already opened one')
95
+ .action(async (stack: string, target: string, opts: PruneOptions) => {
96
+ process.exitCode = await runPrune(stack, target, opts)
97
+ })
98
+
99
+ tooling
100
+ .command('list')
101
+ .description('List installable tooling stacks')
102
+ .helpOption('-h, --help', 'Show this help message')
103
+ .option('--json', 'Emit machine-readable JSON')
104
+ .action((opts: ListOptions) => {
105
+ process.exitCode = runList(opts)
106
+ })
107
+
108
+ for (const verb of PASS_THROUGH_VERBS) {
109
+ tooling
110
+ .command(verb)
111
+ .description(`Run the tooling ${verb} command`)
112
+ .allowUnknownOption()
113
+ .allowExcessArguments(true)
114
+ .passThroughOptions()
115
+ .helpOption(false)
116
+ .action(async (_opts: unknown, cmd: Command) => {
117
+ await execScript(`tooling/${verb}.sh`, cmd.args)
118
+ })
119
+ }
120
+ }
121
+
122
+ /**
123
+ * `JSON.stringify` replaces a `printf` that interpolated manifest fields into a
124
+ * JSON string literal unescaped, so a stack name or description carrying a
125
+ * quote emitted output a consuming skill could not parse.
126
+ *
127
+ * The frame opens after the `--json` return. The bash emitted a closing `└`
128
+ * from its EXIT trap with no `┌` above it, because the hand-rolled
129
+ * pass-through loop below skips the `intro` the shared helper carries.
130
+ */
131
+ function runList(opts: ListOptions): number {
132
+ const stacks = buildStackSummaries(PROJECT_ROOT)
133
+
134
+ if (opts.json) {
135
+ process.stdout.write(`${JSON.stringify({ stacks })}\n`)
136
+ return 0
137
+ }
138
+
139
+ intro('aitk tooling list')
140
+ logStep('Stacks')
141
+ for (const summary of stacks) logInfo(describeStack(summary))
142
+ outro()
143
+ return 0
144
+ }
145
+
146
+ /**
147
+ * Resolves the chain and rejects the same inputs the bash rejected, so a bad
148
+ * stack name fails before anything touches the target.
149
+ *
150
+ * The excluded-stack guard is deliberately not here. It lived in the sync
151
+ * entry point, so `aitk claude` can still drive injection for the `claude`
152
+ * stack the way it drove `merge_gitignore` before.
153
+ */
154
+ function prepare(stack: string, target: string, skip?: string): Prepared {
155
+ if (!stackExists(PROJECT_ROOT, stack)) {
156
+ return { ok: false, error: `Stack not found: ${stack}` }
157
+ }
158
+
159
+ if (skip !== undefined) {
160
+ if (skip === stack) {
161
+ return {
162
+ ok: false,
163
+ error: `Cannot --skip the stack being synced: ${skip}`,
164
+ }
165
+ }
166
+ if (!stackExists(PROJECT_ROOT, skip)) {
167
+ return { ok: false, error: `Stack to skip not found: ${skip}` }
168
+ }
169
+ }
170
+
171
+ const resolved = resolve(target)
172
+ if (resolved === PROJECT_ROOT) {
173
+ return {
174
+ ok: false,
175
+ error:
176
+ 'Cannot run against toolkit root. Files here are the source of truth.',
177
+ }
178
+ }
179
+
180
+ return {
181
+ ok: true,
182
+ chain: resolveChain(PROJECT_ROOT, stack, { skipStack: skip }),
183
+ target: resolved,
184
+ }
185
+ }
186
+
187
+ async function runSync(
188
+ stack: string | undefined,
189
+ target: string,
190
+ opts: SyncOptions,
191
+ ): Promise<number> {
192
+ intro('aitk tooling sync')
193
+
194
+ const selected = stack ?? (await promptForStack())
195
+ if (selected === undefined) {
196
+ logWarn('No tooling stacks found')
197
+ outro()
198
+ return 1
199
+ }
200
+
201
+ if (isStackExcluded(selected)) {
202
+ logWarn(
203
+ 'Claude is managed by `aitk claude`, not `aitk tooling`. Run `aitk claude sync` instead.',
204
+ )
205
+ outro()
206
+ return 1
207
+ }
208
+
209
+ const prepared = prepare(selected, target, opts.skip)
210
+ if (!prepared.ok) {
211
+ logWarn(prepared.error)
212
+ outro()
213
+ return 1
214
+ }
215
+
216
+ const includeReferences = opts.ref !== false
217
+ const result = scan(prepared.chain, prepared.target, { includeReferences })
218
+
219
+ report(result, includeReferences)
220
+
221
+ if (result.totalChanges === 0) {
222
+ outro()
223
+ process.stderr.write(`${GREEN}✓ Everything up to date${NC}\n`)
224
+ return 0
225
+ }
226
+
227
+ const shouldApply = await select({
228
+ message: `Apply ${result.totalChanges} changes (${summarize(result)})?`,
229
+ options: [
230
+ { value: true, label: 'Apply all' },
231
+ { value: false, label: 'Cancel' },
232
+ ],
233
+ nonInteractiveDefault: true,
234
+ })
235
+
236
+ if (!shouldApply) {
237
+ logWarn('Sync cancelled')
238
+ outro()
239
+ return 0
240
+ }
241
+
242
+ if (result.configs.some((entry) => entry.state !== 'matching')) {
243
+ await injectConfigs(prepared.chain, prepared.target)
244
+ }
245
+
246
+ await injectSeeds(prepared.chain, prepared.target)
247
+ await injectManifest(prepared.chain, prepared.target)
248
+
249
+ const pending = result.references
250
+ .filter((entry) => entry.state === 'pending')
251
+ .map((entry) => entry.stack)
252
+
253
+ if (pending.length > 0) {
254
+ logStep('Applying references')
255
+ await applyReferences(prepared.chain, prepared.target, pending)
256
+ }
257
+
258
+ outro()
259
+ process.stderr.write(`${GREEN}✓ Tooling sync complete${NC}\n`)
260
+ return 0
261
+ }
262
+
263
+ async function runInject(
264
+ stack: string,
265
+ target: string,
266
+ opts: InjectOptions,
267
+ ): Promise<number> {
268
+ const framed = opts.nested !== true
269
+ if (framed) intro('aitk tooling inject')
270
+
271
+ const prepared = prepare(stack, target)
272
+ if (!prepared.ok) {
273
+ logWarn(prepared.error)
274
+ if (framed) outro()
275
+ return 1
276
+ }
277
+
278
+ const applyAll =
279
+ !opts.configs && !opts.seeds && !opts.manifest && !opts.gitignore
280
+
281
+ if (opts.configs || applyAll) {
282
+ await injectConfigs(prepared.chain, prepared.target)
283
+ }
284
+ if (opts.seeds || applyAll) {
285
+ await injectSeeds(prepared.chain, prepared.target)
286
+ }
287
+ if (opts.manifest || applyAll) {
288
+ await injectManifest(prepared.chain, prepared.target)
289
+ } else if (opts.gitignore) {
290
+ await injectGitignore(prepared.chain, prepared.target)
291
+ }
292
+
293
+ if (framed) outro()
294
+ return 0
295
+ }
296
+
297
+ /**
298
+ * Emits the number of pruned entries on stdout so a caller can branch on it
299
+ * without parsing the timeline, replacing the bash nameref return.
300
+ */
301
+ async function runPrune(
302
+ stack: string,
303
+ target: string,
304
+ opts: PruneOptions,
305
+ ): Promise<number> {
306
+ const framed = opts.nested !== true
307
+ if (framed) intro('aitk tooling prune-gitignore')
308
+
309
+ const prepared = prepare(stack, target)
310
+ if (!prepared.ok) {
311
+ logWarn(prepared.error)
312
+ if (framed) outro()
313
+ return 1
314
+ }
315
+
316
+ const removed = await pruneGitignore(prepared.chain, prepared.target)
317
+ if (framed) outro()
318
+
319
+ process.stdout.write(`${removed.length}\n`)
320
+ return 0
321
+ }
322
+
323
+ async function promptForStack(): Promise<string | undefined> {
324
+ const stacks = listStacks(PROJECT_ROOT)
325
+ if (stacks.length === 0) return undefined
326
+
327
+ return select({
328
+ message: 'Select tooling stack:',
329
+ options: stacks.map((name) => ({ value: name, label: name })),
330
+ nonInteractiveDefault: true,
331
+ })
332
+ }
333
+
334
+ function report(result: ScanResult, includeReferences: boolean): void {
335
+ logStep('Scanning configs')
336
+ for (const entry of result.configs) {
337
+ if (entry.state === 'matching') logInfo(entry.rel)
338
+ }
339
+ for (const entry of result.configs) {
340
+ if (entry.state === 'drifted') logWarn(entry.rel)
341
+ }
342
+ for (const entry of result.configs) {
343
+ if (entry.state === 'new') logAdd(entry.rel)
344
+ }
345
+
346
+ logStep('Scanning seeds')
347
+ for (const entry of result.seeds) {
348
+ if (entry.state === 'present') logInfo(entry.rel)
349
+ }
350
+ for (const entry of result.seeds) {
351
+ if (entry.state === 'missing') logAdd(entry.rel)
352
+ }
353
+
354
+ if (result.hasPackageJson) {
355
+ reportPackage(result)
356
+ } else {
357
+ logWarn(
358
+ "Skipped scripts and deps: no package.json found (run 'bun init' to enable)",
359
+ )
360
+ }
361
+
362
+ logStep('Scanning gitignore')
363
+ for (const entry of result.gitignore) {
364
+ if (entry.state === 'present') logInfo(entry.entry)
365
+ }
366
+ for (const entry of result.gitignore) {
367
+ if (entry.state === 'missing') logAdd(entry.entry)
368
+ }
369
+
370
+ if (!includeReferences) return
371
+
372
+ logStep('Scanning references')
373
+ for (const entry of result.references) {
374
+ if (entry.state === 'matching') logInfo(`.claude/tooling/${entry.stack}.md`)
375
+ }
376
+ for (const entry of result.references) {
377
+ if (entry.state === 'pending') logAdd(`.claude/tooling/${entry.stack}.md`)
378
+ }
379
+ }
380
+
381
+ function reportPackage(result: ScanResult): void {
382
+ if (result.scripts.length > 0) {
383
+ logStep('Scanning scripts')
384
+ for (const entry of result.scripts) {
385
+ if (entry.state === 'matching') logInfo(entry.key)
386
+ }
387
+ for (const entry of result.scripts) {
388
+ if (entry.state === 'drifted') logWarn(entry.key)
389
+ }
390
+ for (const entry of result.scripts) {
391
+ if (entry.state === 'missing') logAdd(entry.key)
392
+ }
393
+ }
394
+
395
+ if (result.deps.length > 0) {
396
+ logStep('Scanning dependencies')
397
+ for (const entry of result.deps) {
398
+ if (entry.state === 'present') logInfo(entry.name)
399
+ }
400
+ for (const entry of result.deps) {
401
+ if (entry.state === 'missing') logAdd(entry.spec)
402
+ }
403
+ }
404
+ }
405
+
406
+ function summarize(result: ScanResult): string {
407
+ const parts: string[] = []
408
+ const add = (count: number, label: string): void => {
409
+ if (count > 0) parts.push(`${count} ${label}`)
410
+ }
411
+
412
+ add(
413
+ result.configs.filter((entry) => entry.state !== 'matching').length,
414
+ 'configs',
415
+ )
416
+ add(result.seeds.filter((entry) => entry.state === 'missing').length, 'seeds')
417
+ add(
418
+ result.scripts.filter((entry) => entry.state !== 'matching').length,
419
+ 'scripts',
420
+ )
421
+ add(result.deps.filter((entry) => entry.state === 'missing').length, 'deps')
422
+ add(
423
+ result.gitignore.filter((entry) => entry.state === 'missing').length,
424
+ 'gitignore',
425
+ )
426
+ add(
427
+ result.references.filter((entry) => entry.state === 'pending').length,
428
+ 'refs',
429
+ )
430
+
431
+ return parts.join(', ')
432
+ }
@@ -0,0 +1,48 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { ensureYtDlp, fetchOne } from '@/transcripts/fetch'
4
+
5
+ const GREY = '\x1b[0;90m'
6
+ const WHITE = '\x1b[1;37m'
7
+ const RED = '\x1b[0;31m'
8
+ const GREEN = '\x1b[0;32m'
9
+ const NC = '\x1b[0m'
10
+
11
+ interface TranscriptOptions {
12
+ out: string
13
+ keepTimestamps?: boolean
14
+ }
15
+
16
+ export function register(program: Command): void {
17
+ program
18
+ .command('transcripts <url>')
19
+ .description('Fetch a YouTube transcript with metadata frontmatter')
20
+ .option('-o, --out <path>', 'Output directory', 'transcripts')
21
+ .option(
22
+ '--keep-timestamps',
23
+ 'Prefix each line with [mm:ss] instead of prose',
24
+ )
25
+ .action(async (url: string, opts: TranscriptOptions) => {
26
+ const outDir = resolve(process.cwd(), opts.out)
27
+ process.stderr.write(
28
+ `${GREY}┌${NC}\n${GREY}│${NC} ${WHITE}aitk transcripts${NC}\n`,
29
+ )
30
+ try {
31
+ ensureYtDlp()
32
+ const target = await fetchOne(url, {
33
+ outDir,
34
+ keepTimestamps: opts.keepTimestamps ?? false,
35
+ })
36
+ process.stdout.write(`${target}\n`)
37
+ process.stderr.write(
38
+ `${GREY}│${NC} ${GREEN}✓${NC} ${target}\n${GREY}└${NC}\n`,
39
+ )
40
+ } catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error)
42
+ process.stderr.write(
43
+ `${GREY}│${NC} ${RED}✗${NC} ${message}\n${GREY}└${NC}\n`,
44
+ )
45
+ process.exit(1)
46
+ }
47
+ })
48
+ }
@@ -0,0 +1,101 @@
1
+ import { resolve } from 'node:path'
2
+ import type { Command } from 'commander'
3
+ import { PROJECT_ROOT } from '@/exec'
4
+ import {
5
+ intro,
6
+ logAdd,
7
+ logError,
8
+ logInfo,
9
+ logStep,
10
+ logWarn,
11
+ outro,
12
+ select,
13
+ } from '@/ui'
14
+ import {
15
+ applyWikiInit,
16
+ isWikiTarget,
17
+ LEGACY_WIKI_DIR_REL,
18
+ planWikiInit,
19
+ WIKI_DIR_REL,
20
+ WIKI_INDEX_REL,
21
+ } from '@/wiki/init'
22
+
23
+ const GREEN = '\x1b[0;32m'
24
+ const NC = '\x1b[0m'
25
+
26
+ export function register(program: Command): void {
27
+ const wiki = program
28
+ .command('wiki')
29
+ .description('Wiki commands (init)')
30
+ .helpOption('-h, --help', 'Show this help message')
31
+
32
+ wiki
33
+ .command('init')
34
+ .description('Scaffold .claude/wiki/ with a stub index.md')
35
+ .argument('[target]', 'Target directory', '.')
36
+ .helpOption('-h, --help', 'Show this help message')
37
+ .action(async (target: string) => {
38
+ process.exitCode = await runInit(target)
39
+ })
40
+ }
41
+
42
+ async function runInit(target: string): Promise<number> {
43
+ intro('aitk wiki')
44
+
45
+ const resolved = resolve(target)
46
+
47
+ if (!isWikiTarget(resolved)) {
48
+ logError(`Target directory not found: ${target}`)
49
+ outro()
50
+ return 1
51
+ }
52
+
53
+ if (resolved === PROJECT_ROOT) {
54
+ logError(
55
+ 'Cannot run against toolkit root. Files here are the source of truth.',
56
+ )
57
+ outro()
58
+ return 1
59
+ }
60
+
61
+ const plan = planWikiInit(resolved)
62
+
63
+ logStep(`Scanning ${WIKI_DIR_REL}`)
64
+ if (plan.changes.includes('dir')) logAdd(WIKI_DIR_REL)
65
+ if (plan.hasIndex) logInfo(WIKI_INDEX_REL)
66
+ else logAdd(WIKI_INDEX_REL)
67
+
68
+ if (plan.hasLegacyWiki) {
69
+ logWarn(`${LEGACY_WIKI_DIR_REL} found at the target root, not migrated`)
70
+ }
71
+
72
+ const count = plan.changes.length
73
+ if (count === 0) {
74
+ outro()
75
+ process.stderr.write(`${GREEN}✓ Wiki already initialized${NC}\n`)
76
+ return 0
77
+ }
78
+
79
+ const shouldApply = await select({
80
+ message: `Apply ${count} change(s)?`,
81
+ options: [
82
+ { value: true, label: 'Apply all' },
83
+ { value: false, label: 'Cancel' },
84
+ ],
85
+ nonInteractiveDefault: true,
86
+ })
87
+
88
+ if (!shouldApply) {
89
+ logWarn('Cancelled')
90
+ outro()
91
+ return 0
92
+ }
93
+
94
+ logStep('Applying changes')
95
+ await applyWikiInit(resolved, plan)
96
+ if (plan.changes.includes('index')) logAdd(WIKI_INDEX_REL)
97
+
98
+ outro()
99
+ process.stderr.write(`${GREEN}✓ Wiki ready${NC}\n`)
100
+ return 0
101
+ }
package/src/copy.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { chmod, copyFile, mkdir, stat } from 'node:fs/promises'
2
+ import { dirname } from 'node:path'
3
+
4
+ /**
5
+ * Copies with `cp` semantics. Two behaviors matter and neither is the default
6
+ * in the obvious alternatives. `Bun.write` drops the executable bit, which
7
+ * would land husky hooks and `scripts/verify.sh` non-executable. Plain
8
+ * `copyFile` imposes the source mode on an existing destination, so a file the
9
+ * target deliberately restricted to 600, or an earlier stack set executable,
10
+ * comes back at the source's mode. `cp` leaves an existing destination's mode
11
+ * alone.
12
+ *
13
+ * Every domain that writes into a target routes through here. The tooling
14
+ * inject path found the trap first, and the sync engine inherits it for every
15
+ * adapter.
16
+ */
17
+ export async function copyPreservingMode(
18
+ src: string,
19
+ dest: string,
20
+ ): Promise<void> {
21
+ await mkdir(dirname(dest), { recursive: true })
22
+
23
+ const existingMode = await stat(dest)
24
+ .then((info) => info.mode)
25
+ .catch(() => undefined)
26
+
27
+ await copyFile(src, dest)
28
+
29
+ if (existingMode !== undefined) await chmod(dest, existingMode)
30
+ }
@@ -0,0 +1,79 @@
1
+ import { readFileSync } from 'node:fs'
2
+
3
+ export type Row = Record<string, string>
4
+
5
+ export interface DesignDoc {
6
+ personality: string
7
+ color: Row[]
8
+ typography: Row[]
9
+ spacing: Row[]
10
+ borders: Row[]
11
+ motion: string
12
+ iconography: string
13
+ }
14
+
15
+ export function parseDesignDoc(path: string): DesignDoc {
16
+ const raw = readFileSync(path, 'utf8')
17
+ const sections = splitSections(raw)
18
+ return {
19
+ personality: prose(sections['Personality']),
20
+ color: table(sections['Color']),
21
+ typography: table(sections['Typography']),
22
+ spacing: table(sections['Spacing']),
23
+ borders: table(sections['Borders']),
24
+ motion: prose(sections['Motion']),
25
+ iconography: prose(sections['Iconography']),
26
+ }
27
+ }
28
+
29
+ function splitSections(raw: string): Record<string, string> {
30
+ const out: Record<string, string> = {}
31
+ const lines = raw.split('\n')
32
+ let current: string | null = null
33
+ let buffer: string[] = []
34
+ for (const line of lines) {
35
+ const match = line.match(/^##\s+(.+?)\s*$/)
36
+ if (match) {
37
+ if (current) out[current] = buffer.join('\n')
38
+ current = match[1]
39
+ buffer = []
40
+ } else if (current) {
41
+ buffer.push(line)
42
+ }
43
+ }
44
+ if (current) out[current] = buffer.join('\n')
45
+ return out
46
+ }
47
+
48
+ function prose(body: string | undefined): string {
49
+ if (!body) return ''
50
+ return body
51
+ .split('\n')
52
+ .filter((l) => l.trim() && !l.startsWith('|') && !l.startsWith('<'))
53
+ .join('\n')
54
+ .trim()
55
+ }
56
+
57
+ function table(body: string | undefined): Row[] {
58
+ if (!body) return []
59
+ const rows = body.split('\n').filter((l) => l.trim().startsWith('|'))
60
+ if (rows.length < 2) return []
61
+ const headers = splitRow(rows[0])
62
+ const data = rows.slice(2)
63
+ return data.map((line) => {
64
+ const cells = splitRow(line)
65
+ const row: Row = {}
66
+ headers.forEach((h, i) => {
67
+ row[h] = (cells[i] ?? '').trim()
68
+ })
69
+ return row
70
+ })
71
+ }
72
+
73
+ function splitRow(line: string): string[] {
74
+ return line
75
+ .replace(/^\s*\|/, '')
76
+ .replace(/\|\s*$/, '')
77
+ .split('|')
78
+ .map((c) => c.trim().replace(/\s*\?\s*verify\s*$/, ''))
79
+ }