@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,391 @@
1
+ import type PptxGenJS from 'pptxgenjs'
2
+ import type { Slide } from '@/slides/parse'
3
+ import { FONTS, type Theme, TYPE } from '@/slides/styles'
4
+
5
+ type Deck = InstanceType<typeof PptxGenJS>
6
+ type PptxSlide = ReturnType<Deck['addSlide']>
7
+
8
+ export interface LayoutInfo {
9
+ name: string
10
+ description: string
11
+ }
12
+
13
+ export interface NavEntry {
14
+ title: string
15
+ slideNumber: number
16
+ }
17
+
18
+ export interface DeckNav {
19
+ deckTitle: string
20
+ tocSlide?: number
21
+ sections: NavEntry[]
22
+ }
23
+
24
+ export const LAYOUTS: LayoutInfo[] = [
25
+ { name: 'title', description: 'Cover slide with deck title and subtitle' },
26
+ { name: 'toc', description: 'Contents slide linking to each section' },
27
+ { name: 'section', description: 'Section divider with one large heading' },
28
+ { name: 'bullets', description: 'Title above a bullet list' },
29
+ { name: 'two-column', description: 'Two labeled columns of bullets' },
30
+ { name: 'stat-callout', description: 'Row of large stats with captions' },
31
+ { name: 'grid', description: 'Two-by-two cards, each a term and detail' },
32
+ { name: 'quote', description: 'Large pull quote with attribution' },
33
+ ]
34
+
35
+ const W = 13.333
36
+ const H = 7.5
37
+ const MX = 0.7
38
+ const CW = W - MX * 2
39
+ const BODY_Y = 1.7
40
+
41
+ const RENDERERS: Record<
42
+ string,
43
+ (s: PptxSlide, slide: Slide, theme: Theme) => void
44
+ > = {
45
+ title: renderTitle,
46
+ section: renderSection,
47
+ bullets: renderBullets,
48
+ 'two-column': renderTwoColumn,
49
+ 'stat-callout': renderStatCallout,
50
+ grid: renderGrid,
51
+ quote: renderQuote,
52
+ }
53
+
54
+ export function renderDeckSlide(
55
+ s: PptxSlide,
56
+ slide: Slide,
57
+ theme: Theme,
58
+ ): void {
59
+ const renderer = RENDERERS[slide.layout] ?? renderBullets
60
+ renderer(s, slide, theme)
61
+ }
62
+
63
+ export function renderToc(
64
+ s: PptxSlide,
65
+ slide: Slide,
66
+ theme: Theme,
67
+ nav: DeckNav,
68
+ ): void {
69
+ addTitle(s, slide.title || 'Contents', theme)
70
+ if (nav.sections.length === 0) return
71
+ s.addText(
72
+ nav.sections.map((entry) => ({
73
+ text: entry.title,
74
+ options: { breakLine: true, hyperlink: { slide: entry.slideNumber } },
75
+ })),
76
+ {
77
+ x: MX,
78
+ y: BODY_Y,
79
+ w: CW,
80
+ h: 5,
81
+ fontFace: FONTS.body,
82
+ fontSize: TYPE.body + 2,
83
+ color: theme.accent,
84
+ valign: 'top',
85
+ lineSpacingMultiple: 1.6,
86
+ },
87
+ )
88
+ }
89
+
90
+ export function addFooter(
91
+ s: PptxSlide,
92
+ slide: Slide,
93
+ theme: Theme,
94
+ nav: DeckNav,
95
+ ): void {
96
+ if (slide.layout === 'title' || slide.layout === 'toc') return
97
+ if (nav.deckTitle) {
98
+ s.addText(nav.deckTitle, {
99
+ x: MX,
100
+ y: H - 0.5,
101
+ w: CW / 2,
102
+ h: 0.3,
103
+ fontFace: FONTS.body,
104
+ fontSize: TYPE.caption,
105
+ color: theme.muted,
106
+ valign: 'middle',
107
+ })
108
+ }
109
+ if (nav.tocSlide) {
110
+ s.addText('Contents', {
111
+ x: MX + CW / 2,
112
+ y: H - 0.5,
113
+ w: CW / 2,
114
+ h: 0.3,
115
+ fontFace: FONTS.body,
116
+ fontSize: TYPE.caption,
117
+ color: theme.muted,
118
+ align: 'right',
119
+ valign: 'middle',
120
+ hyperlink: { slide: nav.tocSlide },
121
+ })
122
+ }
123
+ }
124
+
125
+ function renderTitle(s: PptxSlide, slide: Slide, theme: Theme): void {
126
+ s.addText(clean(slide.title), {
127
+ x: MX,
128
+ y: 2.6,
129
+ w: CW,
130
+ h: 1.4,
131
+ fontFace: FONTS.heading,
132
+ fontSize: TYPE.cover,
133
+ bold: true,
134
+ color: theme.ink,
135
+ valign: 'top',
136
+ })
137
+ const subtitle = slide.content.join(' ').trim()
138
+ if (subtitle) {
139
+ s.addText(clean(subtitle), {
140
+ x: MX,
141
+ y: 4.1,
142
+ w: CW,
143
+ h: 1,
144
+ fontFace: FONTS.body,
145
+ fontSize: TYPE.body + 4,
146
+ color: theme.muted,
147
+ valign: 'top',
148
+ })
149
+ }
150
+ }
151
+
152
+ function renderSection(s: PptxSlide, slide: Slide, theme: Theme): void {
153
+ s.addText(clean(slide.title), {
154
+ x: MX,
155
+ y: 2.9,
156
+ w: CW,
157
+ h: 1.6,
158
+ fontFace: FONTS.heading,
159
+ fontSize: TYPE.section,
160
+ bold: true,
161
+ color: theme.accent,
162
+ valign: 'middle',
163
+ })
164
+ }
165
+
166
+ function renderBullets(s: PptxSlide, slide: Slide, theme: Theme): void {
167
+ addTitle(s, slide.title, theme)
168
+ const items = bullets(slide.content)
169
+ if (items.length === 0) return
170
+ s.addText(
171
+ items.map((text) => ({
172
+ text,
173
+ options: { bullet: { code: '2022' }, breakLine: true },
174
+ })),
175
+ {
176
+ x: MX,
177
+ y: BODY_Y,
178
+ w: CW,
179
+ h: 5,
180
+ fontFace: FONTS.body,
181
+ fontSize: TYPE.body,
182
+ color: theme.ink,
183
+ valign: 'top',
184
+ lineSpacingMultiple: 1.3,
185
+ paraSpaceAfter: 10,
186
+ },
187
+ )
188
+ }
189
+
190
+ function renderTwoColumn(s: PptxSlide, slide: Slide, theme: Theme): void {
191
+ addTitle(s, slide.title, theme)
192
+ const cols = columns(slide.content)
193
+ const gap = 0.6
194
+ const colWidth = (CW - gap) / 2
195
+ cols.slice(0, 2).forEach((col, index) => {
196
+ const x = MX + index * (colWidth + gap)
197
+ s.addText(col.heading, {
198
+ x,
199
+ y: BODY_Y,
200
+ w: colWidth,
201
+ h: 0.6,
202
+ fontFace: FONTS.heading,
203
+ fontSize: TYPE.body + 4,
204
+ bold: true,
205
+ color: theme.accent,
206
+ valign: 'top',
207
+ })
208
+ if (col.items.length === 0) return
209
+ s.addText(
210
+ col.items.map((text) => ({
211
+ text,
212
+ options: { bullet: { code: '2022' }, breakLine: true },
213
+ })),
214
+ {
215
+ x,
216
+ y: BODY_Y + 0.7,
217
+ w: colWidth,
218
+ h: 4.3,
219
+ fontFace: FONTS.body,
220
+ fontSize: TYPE.body,
221
+ color: theme.ink,
222
+ valign: 'top',
223
+ lineSpacingMultiple: 1.3,
224
+ paraSpaceAfter: 8,
225
+ },
226
+ )
227
+ })
228
+ }
229
+
230
+ function renderStatCallout(s: PptxSlide, slide: Slide, theme: Theme): void {
231
+ addTitle(s, slide.title, theme)
232
+ const stats = pairs(slide.content).slice(0, 4)
233
+ if (stats.length === 0) return
234
+ const gap = 0.5
235
+ const cellWidth = (CW - gap * (stats.length - 1)) / stats.length
236
+ stats.forEach((stat, index) => {
237
+ const x = MX + index * (cellWidth + gap)
238
+ s.addText(stat.left, {
239
+ x,
240
+ y: 2.6,
241
+ w: cellWidth,
242
+ h: 1.3,
243
+ fontFace: FONTS.heading,
244
+ fontSize: TYPE.stat,
245
+ bold: true,
246
+ color: theme.accent,
247
+ align: 'left',
248
+ valign: 'top',
249
+ })
250
+ s.addText(stat.right, {
251
+ x,
252
+ y: 3.9,
253
+ w: cellWidth,
254
+ h: 1.4,
255
+ fontFace: FONTS.body,
256
+ fontSize: TYPE.body,
257
+ color: theme.muted,
258
+ align: 'left',
259
+ valign: 'top',
260
+ })
261
+ })
262
+ }
263
+
264
+ function renderGrid(s: PptxSlide, slide: Slide, theme: Theme): void {
265
+ addTitle(s, slide.title, theme)
266
+ const cards = pairs(slide.content).slice(0, 4)
267
+ const gap = 0.5
268
+ const cardWidth = (CW - gap) / 2
269
+ const cardHeight = 2.1
270
+ cards.forEach((card, index) => {
271
+ const col = index % 2
272
+ const row = Math.floor(index / 2)
273
+ const x = MX + col * (cardWidth + gap)
274
+ const y = BODY_Y + row * (cardHeight + gap)
275
+ s.addShape('roundRect', {
276
+ x,
277
+ y,
278
+ w: cardWidth,
279
+ h: cardHeight,
280
+ fill: { color: theme.surface },
281
+ line: { type: 'none' },
282
+ rectRadius: 0.08,
283
+ })
284
+ s.addText(card.left, {
285
+ x: x + 0.3,
286
+ y: y + 0.25,
287
+ w: cardWidth - 0.6,
288
+ h: 0.6,
289
+ fontFace: FONTS.heading,
290
+ fontSize: TYPE.body + 4,
291
+ bold: true,
292
+ color: theme.ink,
293
+ valign: 'top',
294
+ })
295
+ s.addText(card.right, {
296
+ x: x + 0.3,
297
+ y: y + 0.95,
298
+ w: cardWidth - 0.6,
299
+ h: cardHeight - 1.2,
300
+ fontFace: FONTS.body,
301
+ fontSize: TYPE.body - 2,
302
+ color: theme.ink,
303
+ valign: 'top',
304
+ lineSpacingMultiple: 1.2,
305
+ })
306
+ })
307
+ }
308
+
309
+ function renderQuote(s: PptxSlide, slide: Slide, theme: Theme): void {
310
+ const lines = slide.content
311
+ const attribution = lines.find((line) => /^(—|-)\s/.test(line.trim()))
312
+ const quote = lines
313
+ .filter((line) => line !== attribution)
314
+ .join(' ')
315
+ .trim()
316
+ s.addText(clean(quote), {
317
+ x: MX,
318
+ y: 2.2,
319
+ w: CW,
320
+ h: 2.6,
321
+ fontFace: FONTS.heading,
322
+ fontSize: TYPE.quote,
323
+ italic: true,
324
+ color: theme.ink,
325
+ valign: 'top',
326
+ lineSpacingMultiple: 1.25,
327
+ })
328
+ if (attribution) {
329
+ s.addText(clean(attribution.replace(/^(—|-)\s+/, '')), {
330
+ x: MX,
331
+ y: 5,
332
+ w: CW,
333
+ h: 0.6,
334
+ fontFace: FONTS.body,
335
+ fontSize: TYPE.body,
336
+ bold: true,
337
+ color: theme.accent,
338
+ valign: 'top',
339
+ })
340
+ }
341
+ }
342
+
343
+ function addTitle(s: PptxSlide, title: string, theme: Theme): void {
344
+ if (!title) return
345
+ s.addText(clean(title), {
346
+ x: MX,
347
+ y: 0.5,
348
+ w: CW,
349
+ h: 0.9,
350
+ fontFace: FONTS.heading,
351
+ fontSize: TYPE.title,
352
+ bold: true,
353
+ color: theme.ink,
354
+ valign: 'top',
355
+ })
356
+ }
357
+
358
+ function clean(text: string): string {
359
+ return text.replace(/\*\*/g, '').replace(/`/g, '').trim()
360
+ }
361
+
362
+ function bullets(content: string[]): string[] {
363
+ return content
364
+ .filter((line) => line.trim().startsWith('-'))
365
+ .map((line) => clean(line.replace(/^\s*-\s+/, '')))
366
+ }
367
+
368
+ function pairs(content: string[]): { left: string; right: string }[] {
369
+ return content
370
+ .filter((line) => line.trim().startsWith('-'))
371
+ .map((line) => line.replace(/^\s*-\s+/, ''))
372
+ .map((line) => {
373
+ const [left, ...rest] = line.split(/\s*:\s*/)
374
+ return { left: clean(left), right: clean(rest.join(': ')) }
375
+ })
376
+ }
377
+
378
+ function columns(content: string[]): { heading: string; items: string[] }[] {
379
+ const cols: { heading: string; items: string[] }[] = []
380
+ for (const line of content) {
381
+ const heading = line.match(/^##\s+(.+)/)
382
+ if (heading) {
383
+ cols.push({ heading: clean(heading[1]), items: [] })
384
+ continue
385
+ }
386
+ if (line.trim().startsWith('-') && cols.length > 0) {
387
+ cols[cols.length - 1].items.push(clean(line.replace(/^\s*-\s+/, '')))
388
+ }
389
+ }
390
+ return cols
391
+ }
@@ -0,0 +1,18 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { execa } from 'execa'
3
+
4
+ export async function openDeck(path: string): Promise<boolean> {
5
+ const windowsPath = await execa('wslpath', ['-w', path], { reject: false })
6
+ if (windowsPath.exitCode === 0) {
7
+ const cwd = existsSync('/mnt/c') ? '/mnt/c' : undefined
8
+ const result = await execa(
9
+ 'cmd.exe',
10
+ ['/c', 'start', '', windowsPath.stdout.trim()],
11
+ { cwd, reject: false },
12
+ )
13
+ return result.exitCode === 0
14
+ }
15
+ const opener = process.platform === 'darwin' ? 'open' : 'xdg-open'
16
+ const result = await execa(opener, [path], { reject: false })
17
+ return result.exitCode === 0
18
+ }
@@ -0,0 +1,84 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import type { Variant } from '@/slides/styles'
3
+
4
+ export interface DeckMeta {
5
+ title: string
6
+ variant: Variant
7
+ }
8
+
9
+ export interface Slide {
10
+ layout: string
11
+ title: string
12
+ content: string[]
13
+ }
14
+
15
+ export interface Deck {
16
+ meta: DeckMeta
17
+ slides: Slide[]
18
+ }
19
+
20
+ const DEFAULT_META: DeckMeta = {
21
+ title: '',
22
+ variant: 'light',
23
+ }
24
+
25
+ export function parseSlidesDoc(path: string): Deck {
26
+ return parseSlides(readFileSync(path, 'utf8'))
27
+ }
28
+
29
+ export function parseSlides(raw: string): Deck {
30
+ const { meta, body } = splitFrontmatter(raw)
31
+ const slides = body
32
+ .split(/^---\s*$/m)
33
+ .map(parseSlide)
34
+ .filter((slide): slide is Slide => slide !== null)
35
+ return { meta, slides }
36
+ }
37
+
38
+ function splitFrontmatter(raw: string): { meta: DeckMeta; body: string } {
39
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n?/)
40
+ if (!match) return { meta: { ...DEFAULT_META }, body: raw }
41
+ return { meta: parseMeta(match[1]), body: raw.slice(match[0].length) }
42
+ }
43
+
44
+ function parseMeta(block: string): DeckMeta {
45
+ const meta: DeckMeta = { ...DEFAULT_META }
46
+ for (const line of block.split('\n')) {
47
+ const match = line.match(/^(\w+):\s*(.+?)\s*$/)
48
+ if (!match) continue
49
+ const [, key, value] = match
50
+ if (key === 'title') meta.title = value
51
+ if (key === 'variant' && (value === 'light' || value === 'dark')) {
52
+ meta.variant = value
53
+ }
54
+ }
55
+ return meta
56
+ }
57
+
58
+ function parseSlide(chunk: string): Slide | null {
59
+ let title = ''
60
+ let layout = ''
61
+ const content: string[] = []
62
+ for (const line of chunk.split('\n')) {
63
+ const heading = line.match(/^#\s+(.+?)\s*$/)
64
+ const declared = line.match(/^layout:\s*(.+?)\s*$/)
65
+ if (heading && !title) {
66
+ title = heading[1]
67
+ continue
68
+ }
69
+ if (declared && !layout) {
70
+ layout = declared[1].trim()
71
+ continue
72
+ }
73
+ content.push(line)
74
+ }
75
+ const trimmed = content.filter((line) => line.trim())
76
+ if (!title && !layout && trimmed.length === 0) return null
77
+ return { layout: layout || inferLayout(trimmed), title, content: trimmed }
78
+ }
79
+
80
+ function inferLayout(content: string[]): string {
81
+ return content.some((line) => line.trim().startsWith('-'))
82
+ ? 'bullets'
83
+ : 'title'
84
+ }
@@ -0,0 +1,88 @@
1
+ import { copyFileSync, mkdirSync } from 'node:fs'
2
+ import { basename, join } from 'node:path'
3
+ import PptxGenJS from 'pptxgenjs'
4
+ import {
5
+ addFooter,
6
+ type DeckNav,
7
+ renderDeckSlide,
8
+ renderToc,
9
+ } from '@/slides/layouts'
10
+ import { type Deck, parseSlidesDoc } from '@/slides/parse'
11
+ import { buildTheme, type Variant } from '@/slides/styles'
12
+
13
+ export interface RenderResult {
14
+ pptxPath: string
15
+ mirrorPath?: string
16
+ slideCount: number
17
+ }
18
+
19
+ export interface RenderOptions {
20
+ variant?: Variant
21
+ mirror?: string
22
+ }
23
+
24
+ export async function renderSlidesDoc(
25
+ sourcePath: string,
26
+ outDir: string,
27
+ options: RenderOptions = {},
28
+ ): Promise<RenderResult> {
29
+ const deck = parseSlidesDoc(sourcePath)
30
+ const variant = options.variant ?? deck.meta.variant
31
+ const theme = buildTheme(variant)
32
+
33
+ const pptx = new PptxGenJS()
34
+ pptx.layout = 'LAYOUT_WIDE'
35
+ if (deck.meta.title) pptx.title = deck.meta.title
36
+
37
+ const nav = buildNav(deck)
38
+ for (const slide of deck.slides) {
39
+ const target = pptx.addSlide()
40
+ target.background = { color: theme.background }
41
+ if (slide.layout === 'toc') {
42
+ renderToc(target, slide, theme, nav)
43
+ } else {
44
+ renderDeckSlide(target, slide, theme)
45
+ }
46
+ addFooter(target, slide, theme, nav)
47
+ }
48
+
49
+ mkdirSync(outDir, { recursive: true })
50
+ const fileName = outputName(sourcePath)
51
+ const pptxPath = join(outDir, fileName)
52
+ await pptx.writeFile({ fileName: pptxPath })
53
+
54
+ const mirrorPath = options.mirror
55
+ ? mirrorDeck(pptxPath, options.mirror, fileName)
56
+ : undefined
57
+ return { pptxPath, mirrorPath, slideCount: deck.slides.length }
58
+ }
59
+
60
+ function mirrorDeck(
61
+ pptxPath: string,
62
+ mirrorDir: string,
63
+ fileName: string,
64
+ ): string {
65
+ mkdirSync(mirrorDir, { recursive: true })
66
+ const mirrorPath = join(mirrorDir, fileName)
67
+ copyFileSync(pptxPath, mirrorPath)
68
+ return mirrorPath
69
+ }
70
+
71
+ export function buildNav(deck: Deck): DeckNav {
72
+ let tocSlide: number | undefined
73
+ const sections: DeckNav['sections'] = []
74
+ deck.slides.forEach((slide, index) => {
75
+ const slideNumber = index + 1
76
+ if (slide.layout === 'toc' && tocSlide === undefined) {
77
+ tocSlide = slideNumber
78
+ }
79
+ if (slide.layout === 'section') {
80
+ sections.push({ title: slide.title, slideNumber })
81
+ }
82
+ })
83
+ return { deckTitle: deck.meta.title, tocSlide, sections }
84
+ }
85
+
86
+ function outputName(sourcePath: string): string {
87
+ return basename(sourcePath).replace(/\.md$/, '') + '.pptx'
88
+ }
@@ -0,0 +1,44 @@
1
+ export type Variant = 'light' | 'dark'
2
+
3
+ export interface Theme {
4
+ background: string
5
+ surface: string
6
+ ink: string
7
+ muted: string
8
+ accent: string
9
+ }
10
+
11
+ const LIGHT: Theme = {
12
+ background: 'FAF7F2',
13
+ surface: 'F4EFE6',
14
+ ink: '1A1815',
15
+ muted: '7A736A',
16
+ accent: 'A4471C',
17
+ }
18
+
19
+ const DARK: Theme = {
20
+ background: '1A1815',
21
+ surface: '23201C',
22
+ ink: 'F4EFE6',
23
+ muted: 'A39C92',
24
+ accent: 'C8602E',
25
+ }
26
+
27
+ export const FONTS = {
28
+ heading: 'Arial',
29
+ body: 'Calibri',
30
+ } as const
31
+
32
+ export const TYPE = {
33
+ cover: 44,
34
+ section: 40,
35
+ title: 30,
36
+ body: 16,
37
+ stat: 54,
38
+ quote: 30,
39
+ caption: 11,
40
+ } as const
41
+
42
+ export function buildTheme(variant: Variant): Theme {
43
+ return variant === 'dark' ? DARK : LIGHT
44
+ }
@@ -0,0 +1,34 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { snippetsSourceDir } from '@/snippets/categories'
4
+ import type { InstalledFile, SyncAdapter } from '@/sync/engine'
5
+
6
+ /**
7
+ * Matches installed snippets to sources by relative path, where gov matches by
8
+ * rule name. `derive_dest_rel_path` in `scripts/snippets/install.sh` keeps the
9
+ * immediate parent for a nested snippet and flattens a root-level one to its
10
+ * filename, which is the shape a source-relative path already has, so
11
+ * destination and source rel paths agree for everything the toolkit ships.
12
+ */
13
+ export function createSnippetsAdapter(root: string): SyncAdapter {
14
+ const sourceDir = snippetsSourceDir(root)
15
+
16
+ return {
17
+ banner: 'aitk snippets sync',
18
+ label: 'snippets',
19
+ missingMessage:
20
+ "No .claude/snippets/ found in target. Run 'aitk snippets install' first.",
21
+ unit: 'snippets',
22
+ installedRoot: (target: string) => join(target, '.claude', 'snippets'),
23
+ locateSource: (file: InstalledFile) => locateSource(sourceDir, file),
24
+ stamp: { domain: 'snippets', toolkitRoot: root },
25
+ }
26
+ }
27
+
28
+ function locateSource(
29
+ sourceDir: string,
30
+ file: InstalledFile,
31
+ ): string | undefined {
32
+ const source = join(sourceDir, file.relToRoot)
33
+ return existsSync(source) ? source : undefined
34
+ }