@design-ai/cli 4.55.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 (529) hide show
  1. package/.claude-plugin/plugin.json +164 -0
  2. package/AGENTS.ko.md +126 -0
  3. package/AGENTS.md +182 -0
  4. package/CHANGELOG.md +6883 -0
  5. package/CLAUDE.md +39 -0
  6. package/LICENSE +21 -0
  7. package/README.ko.md +245 -0
  8. package/README.md +277 -0
  9. package/agents/README.md +14 -0
  10. package/agents/a11y-reviewer.md +121 -0
  11. package/agents/component-architect.md +57 -0
  12. package/agents/design-critic.md +46 -0
  13. package/agents/token-extractor.md +63 -0
  14. package/cli/bin/design-ai-mcp.mjs +6 -0
  15. package/cli/bin/design-ai.mjs +40 -0
  16. package/cli/commands/audit.mjs +111 -0
  17. package/cli/commands/check.mjs +226 -0
  18. package/cli/commands/doctor.mjs +141 -0
  19. package/cli/commands/examples.mjs +58 -0
  20. package/cli/commands/help.mjs +271 -0
  21. package/cli/commands/install.mjs +161 -0
  22. package/cli/commands/learn.mjs +1871 -0
  23. package/cli/commands/list.mjs +143 -0
  24. package/cli/commands/mcp.mjs +27 -0
  25. package/cli/commands/pack.mjs +211 -0
  26. package/cli/commands/prompt.mjs +199 -0
  27. package/cli/commands/route.mjs +247 -0
  28. package/cli/commands/search.mjs +60 -0
  29. package/cli/commands/show.mjs +52 -0
  30. package/cli/commands/site.mjs +558 -0
  31. package/cli/commands/status.mjs +161 -0
  32. package/cli/commands/uninstall.mjs +119 -0
  33. package/cli/commands/update.mjs +184 -0
  34. package/cli/commands/version.mjs +94 -0
  35. package/cli/commands/workspace.mjs +176 -0
  36. package/cli/lib/brief.mjs +52 -0
  37. package/cli/lib/check.mjs +642 -0
  38. package/cli/lib/dispatch.mjs +109 -0
  39. package/cli/lib/doctor.mjs +505 -0
  40. package/cli/lib/examples.mjs +258 -0
  41. package/cli/lib/exec.mjs +63 -0
  42. package/cli/lib/help-flags.mjs +5 -0
  43. package/cli/lib/learn.mjs +3694 -0
  44. package/cli/lib/log.mjs +47 -0
  45. package/cli/lib/mcp-server.mjs +426 -0
  46. package/cli/lib/output.mjs +101 -0
  47. package/cli/lib/pack.mjs +599 -0
  48. package/cli/lib/paths.mjs +63 -0
  49. package/cli/lib/prompt.mjs +603 -0
  50. package/cli/lib/route.mjs +740 -0
  51. package/cli/lib/search.mjs +158 -0
  52. package/cli/lib/show.mjs +161 -0
  53. package/cli/lib/signals.mjs +2337 -0
  54. package/cli/lib/site.mjs +6617 -0
  55. package/cli/lib/skill-proposals.mjs +2046 -0
  56. package/cli/lib/suggest.mjs +66 -0
  57. package/cli/lib/workspace.mjs +1056 -0
  58. package/commands/README.md +25 -0
  59. package/commands/component-spec.md +34 -0
  60. package/commands/conversational.md +44 -0
  61. package/commands/design-from-brief.md +141 -0
  62. package/commands/design-review.md +62 -0
  63. package/commands/document-from-brief.md +47 -0
  64. package/commands/extract-tokens.md +38 -0
  65. package/commands/game-ui.md +40 -0
  66. package/commands/illustration.md +37 -0
  67. package/commands/iterate.md +167 -0
  68. package/commands/motion-design.md +54 -0
  69. package/commands/palette-from-brand.md +50 -0
  70. package/commands/print.md +49 -0
  71. package/commands/slide-deck.md +46 -0
  72. package/commands/spatial.md +47 -0
  73. package/commands/stability-review.md +74 -0
  74. package/commands/video.md +42 -0
  75. package/commands/website-improvement.md +77 -0
  76. package/docs/AGENT-DEVELOPMENT.md +658 -0
  77. package/docs/AI-LEARNING.md +429 -0
  78. package/docs/AIDER-INTEGRATION.md +224 -0
  79. package/docs/ARCHITECTURE.ko.md +183 -0
  80. package/docs/ARCHITECTURE.md +105 -0
  81. package/docs/CODEX-INTEGRATION.md +311 -0
  82. package/docs/COMPANY-WEBSITE-DOGFOOD.ko.md +144 -0
  83. package/docs/COMPANY-WEBSITE-DOGFOOD.md +150 -0
  84. package/docs/COMPANY-WEBSITE-INTAKE-TEMPLATE.ko.md +153 -0
  85. package/docs/COMPANY-WEBSITE-INTAKE-TEMPLATE.md +154 -0
  86. package/docs/CONTRIBUTING.ko.md +203 -0
  87. package/docs/CONTRIBUTING.md +242 -0
  88. package/docs/CURSOR-INTEGRATION.md +220 -0
  89. package/docs/DISTRIBUTION.ko.md +248 -0
  90. package/docs/DISTRIBUTION.md +253 -0
  91. package/docs/DOGFOOD-FINDINGS.md +180 -0
  92. package/docs/DOGFOOD-V4-FINDINGS.md +139 -0
  93. package/docs/DOGFOOD-V4-MKDOCS-FINDINGS.md +233 -0
  94. package/docs/DOGFOOD-V4-NPM-FINDINGS.md +157 -0
  95. package/docs/DOGFOOD-V4-VSCODE-FINDINGS.md +135 -0
  96. package/docs/FIGMA-INTEGRATION.md +222 -0
  97. package/docs/MCP-INTEGRATION.md +243 -0
  98. package/docs/MIGRATION-v4.md +121 -0
  99. package/docs/PLUGIN-PACKAGING.md +240 -0
  100. package/docs/PRODUCT-READINESS.md +66 -0
  101. package/docs/QUICKSTART.ko.md +178 -0
  102. package/docs/QUICKSTART.md +179 -0
  103. package/docs/RELEASE-CHECKLIST.md +292 -0
  104. package/docs/ROADMAP.md +19495 -0
  105. package/docs/SESSION-LOG.md +940 -0
  106. package/docs/TOKEN-SYNC.md +237 -0
  107. package/docs/USING.ko.md +206 -0
  108. package/docs/USING.md +218 -0
  109. package/docs/WEBSITE-IMPROVEMENT.ko.md +75 -0
  110. package/docs/WEBSITE-IMPROVEMENT.md +89 -0
  111. package/docs/announcements/README.md +56 -0
  112. package/docs/announcements/dev-to-korea.md +206 -0
  113. package/docs/announcements/hashnode-post.ko.md +232 -0
  114. package/docs/announcements/okky-post.ko.md +158 -0
  115. package/docs/announcements/press-kit.md +101 -0
  116. package/docs/announcements/reddit-r-korea.md +196 -0
  117. package/docs/announcements/show-hn.md +114 -0
  118. package/docs/announcements/twitter-thread.md +255 -0
  119. package/docs/external-status.md +34 -0
  120. package/docs/integrations/aider-walkthrough.ko.md +275 -0
  121. package/docs/integrations/aider-walkthrough.md +274 -0
  122. package/docs/integrations/codex-walkthrough.ko.md +297 -0
  123. package/docs/integrations/codex-walkthrough.md +297 -0
  124. package/docs/integrations/cursor-walkthrough.ko.md +281 -0
  125. package/docs/integrations/cursor-walkthrough.md +284 -0
  126. package/docs/integrations/design-ai-mcp-server.md +169 -0
  127. package/docs/integrations/figma-mcp.md +149 -0
  128. package/docs/integrations/github-mcp.md +178 -0
  129. package/docs/integrations/linear-mcp.md +215 -0
  130. package/docs/integrations/notion-mcp.md +174 -0
  131. package/docs/integrations/sdk-walkthrough.ko.md +375 -0
  132. package/docs/integrations/sdk-walkthrough.md +375 -0
  133. package/docs/integrations/slack-mcp.md +208 -0
  134. package/docs/integrations/vscode-walkthrough.ko.md +202 -0
  135. package/docs/integrations/vscode-walkthrough.md +205 -0
  136. package/docs/requirements.txt +16 -0
  137. package/docs/site-overrides/extra.css +83 -0
  138. package/docs/site-overrides/favicon.svg +4 -0
  139. package/docs/site-overrides/logo.svg +10 -0
  140. package/docs/site-overrides/main.html +18 -0
  141. package/docs/website-console/app.js +2540 -0
  142. package/docs/website-console/index.html +24 -0
  143. package/docs/website-console/styles.css +975 -0
  144. package/examples/README.md +252 -0
  145. package/examples/cases/dogfood-v4-korean-hr-onboarding.md +356 -0
  146. package/examples/component-accordion-actions.md +153 -0
  147. package/examples/component-accordion-details.md +147 -0
  148. package/examples/component-accordion-summary.md +180 -0
  149. package/examples/component-accordion.md +229 -0
  150. package/examples/component-account-card.md +284 -0
  151. package/examples/component-address-input.md +260 -0
  152. package/examples/component-affix.md +190 -0
  153. package/examples/component-alert-dialog.md +210 -0
  154. package/examples/component-alert-title.md +77 -0
  155. package/examples/component-alert.md +192 -0
  156. package/examples/component-amount-input.md +278 -0
  157. package/examples/component-anchor.md +198 -0
  158. package/examples/component-app-bar.md +249 -0
  159. package/examples/component-aspect-ratio.md +156 -0
  160. package/examples/component-auto-complete.md +199 -0
  161. package/examples/component-avatar-group.md +170 -0
  162. package/examples/component-avatar.md +191 -0
  163. package/examples/component-back-top.md +123 -0
  164. package/examples/component-backdrop.md +148 -0
  165. package/examples/component-badge.md +181 -0
  166. package/examples/component-banner.md +237 -0
  167. package/examples/component-biometric-gate.md +281 -0
  168. package/examples/component-blockquote.md +153 -0
  169. package/examples/component-border-beam.md +207 -0
  170. package/examples/component-bottom-navigation.md +226 -0
  171. package/examples/component-box.md +112 -0
  172. package/examples/component-breadcrumb.md +222 -0
  173. package/examples/component-button-base.md +190 -0
  174. package/examples/component-button-group.md +117 -0
  175. package/examples/component-button.md +213 -0
  176. package/examples/component-calendar.md +280 -0
  177. package/examples/component-callout.md +131 -0
  178. package/examples/component-card-actions.md +101 -0
  179. package/examples/component-card-content.md +90 -0
  180. package/examples/component-card-header.md +122 -0
  181. package/examples/component-card-media.md +79 -0
  182. package/examples/component-card.md +233 -0
  183. package/examples/component-carousel.md +266 -0
  184. package/examples/component-cascader.md +135 -0
  185. package/examples/component-category-picker.md +271 -0
  186. package/examples/component-chart.md +237 -0
  187. package/examples/component-chat-interface.md +399 -0
  188. package/examples/component-checkbox.md +149 -0
  189. package/examples/component-click-away-listener.md +87 -0
  190. package/examples/component-code.md +213 -0
  191. package/examples/component-collapsible.md +219 -0
  192. package/examples/component-color-picker.md +110 -0
  193. package/examples/component-combobox.md +267 -0
  194. package/examples/component-command.md +299 -0
  195. package/examples/component-config-provider.md +190 -0
  196. package/examples/component-context-menu.md +179 -0
  197. package/examples/component-css-baseline.md +148 -0
  198. package/examples/component-date-picker.md +249 -0
  199. package/examples/component-descriptions.md +242 -0
  200. package/examples/component-dialog-actions.md +111 -0
  201. package/examples/component-dialog-content-text.md +76 -0
  202. package/examples/component-dialog-content.md +105 -0
  203. package/examples/component-dialog-title.md +104 -0
  204. package/examples/component-dialog.md +176 -0
  205. package/examples/component-divider.md +182 -0
  206. package/examples/component-doc-page.md +217 -0
  207. package/examples/component-drawer.md +257 -0
  208. package/examples/component-dropdown.md +220 -0
  209. package/examples/component-email-layout.md +253 -0
  210. package/examples/component-empty-state.md +269 -0
  211. package/examples/component-empty.md +172 -0
  212. package/examples/component-fade.md +87 -0
  213. package/examples/component-feature-grid.md +201 -0
  214. package/examples/component-field.md +266 -0
  215. package/examples/component-filled-input.md +114 -0
  216. package/examples/component-flex.md +117 -0
  217. package/examples/component-float-button.md +214 -0
  218. package/examples/component-form-control-label.md +134 -0
  219. package/examples/component-form-control.md +113 -0
  220. package/examples/component-form-controls.md +251 -0
  221. package/examples/component-form-group.md +86 -0
  222. package/examples/component-form-helper-text.md +95 -0
  223. package/examples/component-form-label.md +98 -0
  224. package/examples/component-form.md +291 -0
  225. package/examples/component-game-hud.md +362 -0
  226. package/examples/component-game-menu.md +327 -0
  227. package/examples/component-grid.md +140 -0
  228. package/examples/component-grow.md +79 -0
  229. package/examples/component-hero-block.md +252 -0
  230. package/examples/component-hover-card.md +211 -0
  231. package/examples/component-icon-button.md +142 -0
  232. package/examples/component-icon.md +102 -0
  233. package/examples/component-illustration.md +280 -0
  234. package/examples/component-image-list.md +107 -0
  235. package/examples/component-image.md +282 -0
  236. package/examples/component-input-adornment.md +101 -0
  237. package/examples/component-input-base.md +134 -0
  238. package/examples/component-input-number.md +141 -0
  239. package/examples/component-input-otp.md +271 -0
  240. package/examples/component-input.md +213 -0
  241. package/examples/component-item.md +243 -0
  242. package/examples/component-kbd.md +197 -0
  243. package/examples/component-krw-amount.md +219 -0
  244. package/examples/component-label.md +118 -0
  245. package/examples/component-layout.md +217 -0
  246. package/examples/component-link.md +223 -0
  247. package/examples/component-list-item-avatar.md +72 -0
  248. package/examples/component-list-item-button.md +139 -0
  249. package/examples/component-list-item-icon.md +82 -0
  250. package/examples/component-list-item-text.md +98 -0
  251. package/examples/component-list-item.md +116 -0
  252. package/examples/component-list-subheader.md +70 -0
  253. package/examples/component-list.md +198 -0
  254. package/examples/component-loading-button.md +125 -0
  255. package/examples/component-loading-sequence.md +254 -0
  256. package/examples/component-lottie-player.md +279 -0
  257. package/examples/component-masonry.md +191 -0
  258. package/examples/component-mentions.md +189 -0
  259. package/examples/component-menu-item.md +102 -0
  260. package/examples/component-menu-list.md +98 -0
  261. package/examples/component-menu.md +139 -0
  262. package/examples/component-menubar.md +259 -0
  263. package/examples/component-message.md +162 -0
  264. package/examples/component-mobile-stepper.md +155 -0
  265. package/examples/component-modal.md +220 -0
  266. package/examples/component-navigation-menu.md +295 -0
  267. package/examples/component-notification.md +166 -0
  268. package/examples/component-otp-countdown.md +245 -0
  269. package/examples/component-outlined-input.md +145 -0
  270. package/examples/component-page-transition.md +281 -0
  271. package/examples/component-pagination.md +226 -0
  272. package/examples/component-paper.md +199 -0
  273. package/examples/component-pass-auth.md +265 -0
  274. package/examples/component-payment-brand-button.md +253 -0
  275. package/examples/component-payment-method-selector.md +272 -0
  276. package/examples/component-payment-receipt.md +277 -0
  277. package/examples/component-popconfirm.md +144 -0
  278. package/examples/component-popover.md +239 -0
  279. package/examples/component-popper.md +129 -0
  280. package/examples/component-pricing-cards.md +247 -0
  281. package/examples/component-progress.md +195 -0
  282. package/examples/component-qr-code.md +201 -0
  283. package/examples/component-radio.md +167 -0
  284. package/examples/component-rate.md +202 -0
  285. package/examples/component-resizable.md +163 -0
  286. package/examples/component-result.md +270 -0
  287. package/examples/component-scroll-area.md +199 -0
  288. package/examples/component-scroll-reveal.md +320 -0
  289. package/examples/component-segmented.md +178 -0
  290. package/examples/component-select.md +258 -0
  291. package/examples/component-separator.md +177 -0
  292. package/examples/component-sheet.md +277 -0
  293. package/examples/component-sidebar.md +257 -0
  294. package/examples/component-skeleton.md +178 -0
  295. package/examples/component-slide.md +89 -0
  296. package/examples/component-slider.md +208 -0
  297. package/examples/component-snackbar-content.md +92 -0
  298. package/examples/component-snackbar.md +121 -0
  299. package/examples/component-sonner.md +165 -0
  300. package/examples/component-space.md +125 -0
  301. package/examples/component-spatial-locomotion.md +373 -0
  302. package/examples/component-spatial-panel.md +330 -0
  303. package/examples/component-speed-dial-action.md +78 -0
  304. package/examples/component-speed-dial.md +177 -0
  305. package/examples/component-spin.md +173 -0
  306. package/examples/component-spinner.md +204 -0
  307. package/examples/component-splitter.md +210 -0
  308. package/examples/component-stack.md +130 -0
  309. package/examples/component-statistic.md +282 -0
  310. package/examples/component-step-button.md +157 -0
  311. package/examples/component-step-connector.md +157 -0
  312. package/examples/component-step-content.md +121 -0
  313. package/examples/component-step-icon.md +115 -0
  314. package/examples/component-step-label.md +101 -0
  315. package/examples/component-step.md +84 -0
  316. package/examples/component-steps.md +264 -0
  317. package/examples/component-stock-chart.md +251 -0
  318. package/examples/component-swipeable-drawer.md +149 -0
  319. package/examples/component-switch.md +99 -0
  320. package/examples/component-tab-scroll-button.md +180 -0
  321. package/examples/component-tab.md +120 -0
  322. package/examples/component-table-body.md +82 -0
  323. package/examples/component-table-cell.md +118 -0
  324. package/examples/component-table-container.md +75 -0
  325. package/examples/component-table-footer.md +72 -0
  326. package/examples/component-table-head.md +78 -0
  327. package/examples/component-table-pagination.md +137 -0
  328. package/examples/component-table-row.md +113 -0
  329. package/examples/component-table-sort-label.md +109 -0
  330. package/examples/component-table.md +202 -0
  331. package/examples/component-tabs.md +184 -0
  332. package/examples/component-tag-badge.md +309 -0
  333. package/examples/component-tag.md +102 -0
  334. package/examples/component-testimonial-carousel.md +218 -0
  335. package/examples/component-textarea-autosize.md +114 -0
  336. package/examples/component-textarea.md +173 -0
  337. package/examples/component-time-picker.md +205 -0
  338. package/examples/component-timeline.md +170 -0
  339. package/examples/component-toast.md +224 -0
  340. package/examples/component-toggle-button.md +129 -0
  341. package/examples/component-toggle.md +220 -0
  342. package/examples/component-toolbar.md +104 -0
  343. package/examples/component-tooltip.md +178 -0
  344. package/examples/component-tour.md +176 -0
  345. package/examples/component-transaction-list-item.md +243 -0
  346. package/examples/component-transfer.md +156 -0
  347. package/examples/component-tree-select.md +202 -0
  348. package/examples/component-tree.md +297 -0
  349. package/examples/component-typography.md +215 -0
  350. package/examples/component-upload.md +295 -0
  351. package/examples/component-video-hero.md +409 -0
  352. package/examples/component-video-player.md +340 -0
  353. package/examples/component-voice-input.md +408 -0
  354. package/examples/component-watermark.md +217 -0
  355. package/examples/component-zoom.md +92 -0
  356. package/examples/doc-explanation-example.md +196 -0
  357. package/examples/doc-how-to-example.md +205 -0
  358. package/examples/doc-tutorial-example.md +162 -0
  359. package/examples/dogfood-korean-fintech-system.md +433 -0
  360. package/examples/email-transactional-example.md +259 -0
  361. package/examples/palette-saas-violet.md +235 -0
  362. package/examples/print-business-card-spec.md +205 -0
  363. package/examples/print-packaging-spec.md +285 -0
  364. package/examples/report-example.md +228 -0
  365. package/examples/slide-deck-example.md +237 -0
  366. package/examples/website-improvement-report.md +128 -0
  367. package/examples/website-improvement-workspace.json +126 -0
  368. package/install.sh +189 -0
  369. package/knowledge/COVERAGE.md +740 -0
  370. package/knowledge/PRINCIPLES.md +108 -0
  371. package/knowledge/a11y/contrast.md +70 -0
  372. package/knowledge/a11y/keyboard-and-focus.md +108 -0
  373. package/knowledge/colors/color-theory.md +90 -0
  374. package/knowledge/colors/mui-palette-structure.md +105 -0
  375. package/knowledge/colors/palettes-by-product-type.md +876 -0
  376. package/knowledge/components/INDEX.md +231 -0
  377. package/knowledge/components/index.json +1470 -0
  378. package/knowledge/components/shadcn-registry.md +159 -0
  379. package/knowledge/conversational/ai-chat-interfaces.md +323 -0
  380. package/knowledge/conversational/chatbot-design.md +312 -0
  381. package/knowledge/conversational/conversational-ui-fundamentals.md +299 -0
  382. package/knowledge/conversational/korean-voice-conventions.md +281 -0
  383. package/knowledge/conversational/voice-ui-patterns.md +297 -0
  384. package/knowledge/design-tokens/ant-design.md +124 -0
  385. package/knowledge/design-tokens/material-3.md +235 -0
  386. package/knowledge/design-tokens/polaris-and-carbon.md +231 -0
  387. package/knowledge/design-tokens/tailwind-v4.md +284 -0
  388. package/knowledge/game-ui/game-accessibility.md +317 -0
  389. package/knowledge/game-ui/game-ui-fundamentals.md +281 -0
  390. package/knowledge/game-ui/hud-design.md +324 -0
  391. package/knowledge/game-ui/korean-gaming-conventions.md +312 -0
  392. package/knowledge/game-ui/menu-systems.md +388 -0
  393. package/knowledge/i18n/korean-app-store-visual.md +223 -0
  394. package/knowledge/i18n/korean-document-style.md +301 -0
  395. package/knowledge/i18n/korean-payments.md +211 -0
  396. package/knowledge/i18n/korean-product-conventions.md +96 -0
  397. package/knowledge/i18n/korean-publishing.md +139 -0
  398. package/knowledge/i18n/korean-typography.md +123 -0
  399. package/knowledge/icons/curated-sets.md +225 -0
  400. package/knowledge/illustration/hero-illustrations.md +234 -0
  401. package/knowledge/illustration/illustration-systems.md +221 -0
  402. package/knowledge/illustration/mascot-design.md +208 -0
  403. package/knowledge/illustration/spot-illustrations.md +246 -0
  404. package/knowledge/illustration/svg-optimization.md +308 -0
  405. package/knowledge/layout/spacing-and-grid.md +119 -0
  406. package/knowledge/motion/app-loading-sequences.md +277 -0
  407. package/knowledge/motion/choreography-depth.md +297 -0
  408. package/knowledge/motion/marketing-motion.md +223 -0
  409. package/knowledge/motion/micro-interactions.md +266 -0
  410. package/knowledge/motion/motion-tools.md +281 -0
  411. package/knowledge/motion/principles.md +126 -0
  412. package/knowledge/patterns/auth-flow-design.md +316 -0
  413. package/knowledge/patterns/b2b-onboarding-flows.md +182 -0
  414. package/knowledge/patterns/brand-identity.md +238 -0
  415. package/knowledge/patterns/brand-references.md +97 -0
  416. package/knowledge/patterns/chart-color-encoding.md +269 -0
  417. package/knowledge/patterns/chart-types.md +344 -0
  418. package/knowledge/patterns/dashboard-composition.md +299 -0
  419. package/knowledge/patterns/design-system-qa.md +356 -0
  420. package/knowledge/patterns/document-typography.md +248 -0
  421. package/knowledge/patterns/email-design.md +338 -0
  422. package/knowledge/patterns/empty-states.md +263 -0
  423. package/knowledge/patterns/error-states.md +318 -0
  424. package/knowledge/patterns/form-design.md +237 -0
  425. package/knowledge/patterns/information-architecture.md +341 -0
  426. package/knowledge/patterns/landing-hero-design.md +283 -0
  427. package/knowledge/patterns/landing-page-patterns.md +681 -0
  428. package/knowledge/patterns/list-and-feed.md +371 -0
  429. package/knowledge/patterns/mobile-navigation.md +278 -0
  430. package/knowledge/patterns/money-and-amount.md +301 -0
  431. package/knowledge/patterns/onboarding.md +300 -0
  432. package/knowledge/patterns/pricing-page-design.md +261 -0
  433. package/knowledge/patterns/realtime-data.md +280 -0
  434. package/knowledge/patterns/report-design.md +330 -0
  435. package/knowledge/patterns/search-ux.md +373 -0
  436. package/knowledge/patterns/settings-page.md +325 -0
  437. package/knowledge/patterns/slide-deck-design.md +316 -0
  438. package/knowledge/patterns/styles-catalog.md +2599 -0
  439. package/knowledge/patterns/technical-writing.md +306 -0
  440. package/knowledge/patterns/ui-reasoning.md +1853 -0
  441. package/knowledge/patterns/ux-guidelines.md +1454 -0
  442. package/knowledge/platforms/react-native.md +395 -0
  443. package/knowledge/print/brochures-and-flyers.md +276 -0
  444. package/knowledge/print/korean-print-conventions.md +284 -0
  445. package/knowledge/print/packaging.md +321 -0
  446. package/knowledge/print/print-fundamentals.md +238 -0
  447. package/knowledge/print/signage-and-posters.md +235 -0
  448. package/knowledge/print/stationery.md +262 -0
  449. package/knowledge/spatial/ar-patterns.md +253 -0
  450. package/knowledge/spatial/comfort-and-accessibility.md +245 -0
  451. package/knowledge/spatial/spatial-design-fundamentals.md +346 -0
  452. package/knowledge/spatial/spatial-ui-elements.md +343 -0
  453. package/knowledge/spatial/vr-patterns.md +302 -0
  454. package/knowledge/typography/font-pairings.md +332 -0
  455. package/knowledge/typography/mui-type-scale.md +57 -0
  456. package/knowledge/typography/type-scale-fundamentals.md +105 -0
  457. package/knowledge/video/in-product-video.md +277 -0
  458. package/knowledge/video/korean-video-conventions.md +247 -0
  459. package/knowledge/video/marketing-video.md +258 -0
  460. package/knowledge/video/social-and-short-form.md +257 -0
  461. package/knowledge/video/video-fundamentals.md +296 -0
  462. package/package.json +106 -0
  463. package/skills/README.md +39 -0
  464. package/skills/color-palette/PLAYBOOK.md +175 -0
  465. package/skills/color-palette/SKILL.md +6 -0
  466. package/skills/color-palette/TEMPLATE.md +103 -0
  467. package/skills/component-spec-writer/PLAYBOOK.md +169 -0
  468. package/skills/component-spec-writer/SKILL.md +6 -0
  469. package/skills/component-spec-writer/TEMPLATE.md +115 -0
  470. package/skills/conversational-ui-designer/PLAYBOOK.md +231 -0
  471. package/skills/conversational-ui-designer/SKILL.md +6 -0
  472. package/skills/design-broadcast/PLAYBOOK.md +146 -0
  473. package/skills/design-broadcast/SKILL.md +6 -0
  474. package/skills/design-critique/PLAYBOOK.md +137 -0
  475. package/skills/design-critique/SKILL.md +6 -0
  476. package/skills/design-pr-review/PLAYBOOK.md +171 -0
  477. package/skills/design-pr-review/SKILL.md +6 -0
  478. package/skills/design-system-builder/PLAYBOOK.md +218 -0
  479. package/skills/design-system-builder/SKILL.md +6 -0
  480. package/skills/design-system-qa/PLAYBOOK.md +163 -0
  481. package/skills/design-system-qa/SKILL.md +6 -0
  482. package/skills/document-author/PLAYBOOK.md +167 -0
  483. package/skills/document-author/SKILL.md +6 -0
  484. package/skills/figma-token-sync/PLAYBOOK.md +149 -0
  485. package/skills/figma-token-sync/SKILL.md +6 -0
  486. package/skills/game-ui-designer/PLAYBOOK.md +209 -0
  487. package/skills/game-ui-designer/SKILL.md +6 -0
  488. package/skills/handoff-spec/PLAYBOOK.md +170 -0
  489. package/skills/handoff-spec/SKILL.md +6 -0
  490. package/skills/illustration-designer/PLAYBOOK.md +166 -0
  491. package/skills/illustration-designer/SKILL.md +6 -0
  492. package/skills/motion-designer/PLAYBOOK.md +223 -0
  493. package/skills/motion-designer/SKILL.md +6 -0
  494. package/skills/print-designer/PLAYBOOK.md +209 -0
  495. package/skills/print-designer/SKILL.md +6 -0
  496. package/skills/slide-deck-author/PLAYBOOK.md +198 -0
  497. package/skills/slide-deck-author/SKILL.md +6 -0
  498. package/skills/spatial-designer/PLAYBOOK.md +238 -0
  499. package/skills/spatial-designer/SKILL.md +6 -0
  500. package/skills/ux-audit/PLAYBOOK.md +142 -0
  501. package/skills/ux-audit/SKILL.md +6 -0
  502. package/skills/video-designer/PLAYBOOK.md +240 -0
  503. package/skills/video-designer/SKILL.md +6 -0
  504. package/skills/website-improvement/PLAYBOOK.md +118 -0
  505. package/skills/website-improvement/SKILL.md +6 -0
  506. package/skills/website-improvement/TEMPLATE.md +103 -0
  507. package/tools/audit/changelog-generate.py +176 -0
  508. package/tools/audit/check-coverage.py +483 -0
  509. package/tools/audit/doctor_assertions.py +256 -0
  510. package/tools/audit/example-qa.py +108 -0
  511. package/tools/audit/frontmatter-check.py +199 -0
  512. package/tools/audit/integration-check.py +95 -0
  513. package/tools/audit/korean-copy-check.py +161 -0
  514. package/tools/audit/link-check.py +146 -0
  515. package/tools/audit/local-ci.py +558 -0
  516. package/tools/audit/package-contents.py +723 -0
  517. package/tools/audit/package-smoke.py +22443 -0
  518. package/tools/audit/raw-hex-check.py +252 -0
  519. package/tools/audit/registry-smoke.py +8188 -0
  520. package/tools/audit/release-metadata.py +9832 -0
  521. package/tools/audit/run-all.py +351 -0
  522. package/tools/audit/smoke_assertions.py +12742 -0
  523. package/tools/audit/stability-review.py +332 -0
  524. package/tools/audit/stale-check.py +156 -0
  525. package/tools/migrations/add-version-frontmatter.py +132 -0
  526. package/tools/migrations/bump-last-updated.py +131 -0
  527. package/tools/migrations/promote-stability.py +172 -0
  528. package/tools/preview/README.md +58 -0
  529. package/tools/preview/render-tokens.py +454 -0
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env python3
2
+ """Quarterly stability review report.
3
+
4
+ Generates a markdown report listing knowledge files grouped by stability
5
+ level + freshness. Surfaces:
6
+ - experimental files that held up >= 6 months → promote to stable.
7
+ - beta files >= 3 months old without recent edits → promote to stable.
8
+ - stable files > 12 months old → review + bump or revise.
9
+ - deprecated files with no removal plan → flag for next major.
10
+
11
+ Usage:
12
+ python3 tools/audit/stability-review.py
13
+ python3 tools/audit/stability-review.py --output docs/STABILITY-REVIEW.md
14
+ python3 tools/audit/stability-review.py --warn-months 3 --promote-after 6
15
+
16
+ Pairs with:
17
+ - tools/migrations/promote-stability.py (bulk promote/demote)
18
+ - tools/migrations/bump-last-updated.py (bulk freshness bump)
19
+
20
+ Read docs/CONTRIBUTING.md "Quarterly stability review" for the full ritual.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import re
26
+ import sys
27
+ from collections import defaultdict
28
+ from dataclasses import dataclass
29
+ from datetime import date
30
+ from pathlib import Path
31
+
32
+ ROOT = Path(__file__).resolve().parents[2]
33
+ KNOWLEDGE = ROOT / "knowledge"
34
+
35
+ DATE_RE = re.compile(r"^(\d{4})-(\d{2})(?:-(\d{2}))?$")
36
+ STABILITY_VALUES = {"stable", "beta", "experimental", "deprecated"}
37
+
38
+ # Generated artifacts that are auto-regenerated and don't need manual review.
39
+ # These are excluded from the "files without stability metadata" section.
40
+ # (Surfaced as a v4.7 dogfood fix — COVERAGE.md was a false positive.)
41
+ GENERATED_ARTIFACTS = {
42
+ "knowledge/COVERAGE.md",
43
+ }
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class FileInfo:
48
+ path: Path
49
+ rel: Path
50
+ stability: str | None
51
+ last_updated: str | None
52
+ last_updated_date: date | None
53
+ age_months: int | None
54
+
55
+
56
+ def find_frontmatter_bounds(text: str) -> tuple[int, int] | None:
57
+ lines = text.splitlines()
58
+ cursor = 0
59
+ while cursor < len(lines):
60
+ s = lines[cursor].strip()
61
+ if s.startswith("<!--") and s.endswith("-->"):
62
+ cursor += 1
63
+ continue
64
+ if s == "":
65
+ cursor += 1
66
+ continue
67
+ break
68
+ if cursor >= len(lines) or lines[cursor].strip() != "---":
69
+ return None
70
+ start = cursor + 1
71
+ for i in range(start, len(lines)):
72
+ if lines[i].strip() == "---":
73
+ return (start, i)
74
+ return None
75
+
76
+
77
+ def extract_frontmatter_value(text: str, key: str) -> str | None:
78
+ bounds = find_frontmatter_bounds(text)
79
+ if bounds is None:
80
+ return None
81
+ start, end = bounds
82
+ for line in text.splitlines()[start:end]:
83
+ if line.lstrip().startswith(f"{key}:"):
84
+ return line.split(":", 1)[1].strip().strip("'\"")
85
+ return None
86
+
87
+
88
+ def parse_date(value: str | None) -> date | None:
89
+ if value is None:
90
+ return None
91
+ m = DATE_RE.match(value)
92
+ if not m:
93
+ return None
94
+ year, month, day = int(m.group(1)), int(m.group(2)), m.group(3)
95
+ if day is None:
96
+ return date(year, month, 28)
97
+ return date(year, month, int(day))
98
+
99
+
100
+ def months_diff(target: date, today: date) -> int:
101
+ return (today.year - target.year) * 12 + (today.month - target.month)
102
+
103
+
104
+ def collect(today: date) -> list[FileInfo]:
105
+ out: list[FileInfo] = []
106
+ for path in sorted(KNOWLEDGE.rglob("*.md")):
107
+ rel = path.relative_to(ROOT)
108
+ # Skip generated artifacts — they don't carry stability by design.
109
+ if str(rel).replace("\\", "/") in GENERATED_ARTIFACTS:
110
+ continue
111
+ text = path.read_text(encoding="utf-8")
112
+ stability = extract_frontmatter_value(text, "stability")
113
+ last_updated = extract_frontmatter_value(text, "last_updated")
114
+ d = parse_date(last_updated)
115
+ age = months_diff(d, today) if d else None
116
+ out.append(
117
+ FileInfo(
118
+ path=path,
119
+ rel=rel,
120
+ stability=stability,
121
+ last_updated=last_updated,
122
+ last_updated_date=d,
123
+ age_months=age,
124
+ )
125
+ )
126
+ return out
127
+
128
+
129
+ def render_promotion_candidates(files: list[FileInfo], from_level: str, min_age: int) -> list[FileInfo]:
130
+ """Files at `from_level` whose last_updated is at least `min_age` months ago."""
131
+ return [
132
+ f for f in files
133
+ if f.stability == from_level and f.age_months is not None and f.age_months >= min_age
134
+ ]
135
+
136
+
137
+ def render_stale_within_level(files: list[FileInfo], level: str, threshold: int) -> list[FileInfo]:
138
+ return [
139
+ f for f in files
140
+ if f.stability == level and f.age_months is not None and f.age_months >= threshold
141
+ ]
142
+
143
+
144
+ def render_report(files: list[FileInfo], today: date, *, warn_months: int, promote_after: int, stale_months: int) -> str:
145
+ lines: list[str] = []
146
+ lines.append(f"# Stability review — {today.isoformat()}")
147
+ lines.append("")
148
+ lines.append(
149
+ f"Generated by `tools/audit/stability-review.py`. "
150
+ f"Pairs with `tools/migrations/promote-stability.py` (bulk promote/demote) "
151
+ f"and `tools/migrations/bump-last-updated.py` (bulk freshness bump)."
152
+ )
153
+ lines.append("")
154
+
155
+ # Summary table
156
+ by_level: dict[str, list[FileInfo]] = defaultdict(list)
157
+ no_stability: list[FileInfo] = []
158
+ for f in files:
159
+ if f.stability and f.stability in STABILITY_VALUES:
160
+ by_level[f.stability].append(f)
161
+ else:
162
+ no_stability.append(f)
163
+
164
+ lines.append("## Summary")
165
+ lines.append("")
166
+ lines.append("| Level | Count | Oldest (months) |")
167
+ lines.append("| --- | --- | --- |")
168
+ for level in ("stable", "beta", "experimental", "deprecated"):
169
+ items = by_level.get(level, [])
170
+ oldest = max((f.age_months for f in items if f.age_months is not None), default=None)
171
+ oldest_str = f"{oldest}" if oldest is not None else "—"
172
+ lines.append(f"| {level} | {len(items)} | {oldest_str} |")
173
+ if no_stability:
174
+ lines.append(f"| (no stability key) | {len(no_stability)} | — |")
175
+ lines.append("")
176
+
177
+ # Section 1: experimental → stable promotion candidates
178
+ promote_exp = render_promotion_candidates(files, "experimental", promote_after)
179
+ lines.append(f"## Promotion candidates: experimental → stable (held up ≥ {promote_after} months)")
180
+ lines.append("")
181
+ if promote_exp:
182
+ lines.append("Run after review:")
183
+ lines.append("")
184
+ lines.append("```bash")
185
+ lines.append("python3 tools/migrations/promote-stability.py --from experimental --to stable \\")
186
+ for f in promote_exp:
187
+ lines.append(f" {f.rel} \\")
188
+ if lines[-1].endswith(" \\"):
189
+ lines[-1] = lines[-1].rstrip(" \\")
190
+ lines.append("```")
191
+ lines.append("")
192
+ else:
193
+ lines.append("(none)")
194
+ lines.append("")
195
+
196
+ # Section 2: beta → stable promotion candidates
197
+ promote_beta = render_promotion_candidates(files, "beta", warn_months)
198
+ lines.append(f"## Promotion candidates: beta → stable (held up ≥ {warn_months} months)")
199
+ lines.append("")
200
+ if promote_beta:
201
+ lines.append("Run after review:")
202
+ lines.append("")
203
+ lines.append("```bash")
204
+ lines.append("python3 tools/migrations/promote-stability.py --from beta --to stable \\")
205
+ for f in promote_beta:
206
+ lines.append(f" {f.rel} \\")
207
+ if lines[-1].endswith(" \\"):
208
+ lines[-1] = lines[-1].rstrip(" \\")
209
+ lines.append("```")
210
+ lines.append("")
211
+ else:
212
+ lines.append("(none)")
213
+ lines.append("")
214
+
215
+ # Section 3: stable files getting stale
216
+ stale_stable = render_stale_within_level(files, "stable", stale_months)
217
+ lines.append(f"## Stable files due for re-review (last_updated ≥ {stale_months} months ago)")
218
+ lines.append("")
219
+ if stale_stable:
220
+ lines.append(
221
+ f"Either bump `last_updated` (still accurate, no edits needed) or revise + bump."
222
+ )
223
+ lines.append("")
224
+ lines.append("Bulk-bump after review:")
225
+ lines.append("")
226
+ lines.append("```bash")
227
+ lines.append("python3 tools/migrations/bump-last-updated.py \\")
228
+ for f in stale_stable[:20]:
229
+ lines.append(f" {f.rel} \\")
230
+ if len(stale_stable) > 20:
231
+ lines.append(f" # ... and {len(stale_stable) - 20} more")
232
+ if lines[-1].endswith(" \\"):
233
+ lines[-1] = lines[-1].rstrip(" \\")
234
+ lines.append("```")
235
+ lines.append("")
236
+ lines.append("Files:")
237
+ lines.append("")
238
+ for f in stale_stable:
239
+ lines.append(f"- `{f.rel}` — {f.age_months} months old")
240
+ lines.append("")
241
+ else:
242
+ lines.append("(none)")
243
+ lines.append("")
244
+
245
+ # Section 4: deprecated files
246
+ deprecated = by_level.get("deprecated", [])
247
+ lines.append("## Deprecated files (review for next major removal)")
248
+ lines.append("")
249
+ if deprecated:
250
+ lines.append("These should be removed in the next major version. Confirm removal plan exists in CHANGELOG.")
251
+ lines.append("")
252
+ for f in deprecated:
253
+ age_str = f"{f.age_months} months" if f.age_months is not None else "no last_updated"
254
+ lines.append(f"- `{f.rel}` — {age_str}")
255
+ lines.append("")
256
+ else:
257
+ lines.append("(none)")
258
+ lines.append("")
259
+
260
+ # Section 5: files without stability metadata
261
+ if no_stability:
262
+ lines.append("## Files without `stability` metadata")
263
+ lines.append("")
264
+ lines.append("Add `stability:` to frontmatter (one of stable / beta / experimental / deprecated).")
265
+ lines.append("")
266
+ for f in no_stability:
267
+ lines.append(f"- `{f.rel}`")
268
+ lines.append("")
269
+
270
+ # Closing
271
+ lines.append("---")
272
+ lines.append("")
273
+ lines.append("## Ritual checklist")
274
+ lines.append("")
275
+ lines.append("Walk through each section above. For each candidate:")
276
+ lines.append("")
277
+ lines.append("- [ ] Read the file. Is the content still accurate?")
278
+ lines.append("- [ ] If yes: bump `last_updated` (use `tools/migrations/bump-last-updated.py`).")
279
+ lines.append("- [ ] If revisions needed: edit + bump.")
280
+ lines.append("- [ ] If obsolete: mark `stability: deprecated` and add deprecation note in body.")
281
+ lines.append("- [ ] For promotion candidates: confirm content stable, then promote.")
282
+ lines.append("")
283
+ lines.append("After the ritual, document outcome in `CHANGELOG.md` under the next minor release.")
284
+ lines.append("")
285
+
286
+ return "\n".join(lines)
287
+
288
+
289
+ def main() -> None:
290
+ parser = argparse.ArgumentParser(description=__doc__)
291
+ parser.add_argument(
292
+ "--output",
293
+ type=Path,
294
+ help="Write report to this path (otherwise stdout)",
295
+ )
296
+ parser.add_argument("--warn-months", type=int, default=3, help="Beta promotion threshold (default: 3)")
297
+ parser.add_argument(
298
+ "--promote-after",
299
+ type=int,
300
+ default=6,
301
+ help="Experimental → stable threshold (default: 6)",
302
+ )
303
+ parser.add_argument(
304
+ "--stale-months",
305
+ type=int,
306
+ default=12,
307
+ help="Stable file re-review threshold (default: 12)",
308
+ )
309
+ parser.add_argument("--today", help="Override today (YYYY-MM-DD); for testing")
310
+ args = parser.parse_args()
311
+
312
+ today = date.fromisoformat(args.today) if args.today else date.today()
313
+
314
+ files = collect(today)
315
+ report = render_report(
316
+ files,
317
+ today,
318
+ warn_months=args.warn_months,
319
+ promote_after=args.promote_after,
320
+ stale_months=args.stale_months,
321
+ )
322
+
323
+ if args.output:
324
+ args.output.parent.mkdir(parents=True, exist_ok=True)
325
+ args.output.write_text(report, encoding="utf-8")
326
+ print(f"Wrote: {args.output}")
327
+ else:
328
+ sys.stdout.write(report)
329
+
330
+
331
+ if __name__ == "__main__":
332
+ main()
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env python3
2
+ """Flag knowledge files whose `last_updated` is too old.
3
+
4
+ Operationalizes the versioned frontmatter (added in v3.11). Default:
5
+ - Warn: last_updated > 6 months ago.
6
+ - Error: last_updated > 12 months ago (with --strict).
7
+
8
+ Files without `last_updated` are skipped (backward-compatible).
9
+
10
+ Usage:
11
+ python3 tools/audit/stale-check.py
12
+ python3 tools/audit/stale-check.py --strict
13
+ python3 tools/audit/stale-check.py --warn-months 3 --error-months 6
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import re
19
+ import sys
20
+ from datetime import date
21
+ from pathlib import Path
22
+
23
+ ROOT = Path(__file__).resolve().parents[2]
24
+ KNOWLEDGE = ROOT / "knowledge"
25
+
26
+ DATE_RE = re.compile(r"^(\d{4})-(\d{2})(?:-(\d{2}))?$")
27
+
28
+
29
+ def find_frontmatter_bounds(text: str) -> tuple[int, int] | None:
30
+ lines = text.splitlines()
31
+ cursor = 0
32
+ while cursor < len(lines):
33
+ s = lines[cursor].strip()
34
+ if s.startswith("<!--") and s.endswith("-->"):
35
+ cursor += 1
36
+ continue
37
+ if s == "":
38
+ cursor += 1
39
+ continue
40
+ break
41
+ if cursor >= len(lines) or lines[cursor].strip() != "---":
42
+ return None
43
+ start = cursor + 1
44
+ for i in range(start, len(lines)):
45
+ if lines[i].strip() == "---":
46
+ return (start, i)
47
+ return None
48
+
49
+
50
+ def extract_last_updated(text: str) -> str | None:
51
+ bounds = find_frontmatter_bounds(text)
52
+ if bounds is None:
53
+ return None
54
+ start, end = bounds
55
+ for line in text.splitlines()[start:end]:
56
+ if line.lstrip().startswith("last_updated:"):
57
+ value = line.split(":", 1)[1].strip().strip("'\"")
58
+ return value
59
+ return None
60
+
61
+
62
+ def parse_date(value: str) -> date | None:
63
+ """Parse YYYY-MM or YYYY-MM-DD; return last day of month if no day given."""
64
+ m = DATE_RE.match(value)
65
+ if not m:
66
+ return None
67
+ year, month, day = int(m.group(1)), int(m.group(2)), m.group(3)
68
+ if day is None:
69
+ # Treat YYYY-MM as the LAST day of that month (most generous reading).
70
+ # Pick day 28 — safe for all months — then advance to month-end.
71
+ d = date(year, month, 28)
72
+ else:
73
+ d = date(year, month, int(day))
74
+ return d
75
+
76
+
77
+ def months_diff(target: date, today: date) -> int:
78
+ """Approximate month difference (today − target). Negative if target is in the future."""
79
+ return (today.year - target.year) * 12 + (today.month - target.month)
80
+
81
+
82
+ def main() -> None:
83
+ parser = argparse.ArgumentParser(description=__doc__)
84
+ parser.add_argument("--strict", action="store_true", help="Exit 1 if any file exceeds the error threshold")
85
+ parser.add_argument("--warn-months", type=int, default=6, help="Months before warning (default: 6)")
86
+ parser.add_argument("--error-months", type=int, default=12, help="Months before error (default: 12)")
87
+ parser.add_argument("--today", default=None, help="Override today's date (YYYY-MM-DD) — for testing")
88
+ args = parser.parse_args()
89
+
90
+ today = date.fromisoformat(args.today) if args.today else date.today()
91
+
92
+ warns: list[str] = []
93
+ errors: list[str] = []
94
+ skipped = 0
95
+ invalid = 0
96
+ fresh = 0
97
+
98
+ for path in sorted(KNOWLEDGE.rglob("*.md")):
99
+ text = path.read_text(encoding="utf-8")
100
+ last_updated = extract_last_updated(text)
101
+ rel = path.relative_to(ROOT)
102
+
103
+ if last_updated is None:
104
+ skipped += 1
105
+ continue
106
+
107
+ d = parse_date(last_updated)
108
+ if d is None:
109
+ invalid += 1
110
+ errors.append(f"{rel}: invalid last_updated format '{last_updated}'")
111
+ continue
112
+
113
+ diff = months_diff(d, today)
114
+ if diff >= args.error_months:
115
+ errors.append(f"{rel}: {diff} months stale (last_updated={last_updated})")
116
+ elif diff >= args.warn_months:
117
+ warns.append(f"{rel}: {diff} months old (last_updated={last_updated})")
118
+ else:
119
+ fresh += 1
120
+
121
+ total = fresh + len(warns) + len(errors) + skipped + invalid
122
+ print()
123
+ print(f" Fresh (≤ {args.warn_months} months): {fresh}")
124
+ print(f" Warning (>{args.warn_months} months): {len(warns)}")
125
+ print(f" Stale (>{args.error_months} months): {len(errors)}")
126
+ print(f" Skipped (no last_updated): {skipped}")
127
+ print(f" Invalid format: {invalid}")
128
+ print(f" Total knowledge files: {total}")
129
+ print()
130
+
131
+ if warns and not args.strict:
132
+ print("⚠ Warnings:")
133
+ for w in warns[:10]:
134
+ print(f" - {w}")
135
+ if len(warns) > 10:
136
+ print(f" ... and {len(warns) - 10} more")
137
+
138
+ if errors:
139
+ print("✗ Stale files:")
140
+ for e in errors[:20]:
141
+ print(f" - {e}")
142
+ if len(errors) > 20:
143
+ print(f" ... and {len(errors) - 20} more")
144
+
145
+ if errors:
146
+ if args.strict:
147
+ sys.exit(1)
148
+ else:
149
+ print()
150
+ print("Use --strict to fail CI on stale files.")
151
+ else:
152
+ print("All knowledge files within freshness window ✓")
153
+
154
+
155
+ if __name__ == "__main__":
156
+ main()
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+ """One-shot migration: add `version`, `last_updated`, `stability` keys to
3
+ the YAML frontmatter of every knowledge file.
4
+
5
+ Behavior:
6
+ - Locates every .md file in knowledge/ that already has a frontmatter
7
+ block (between `---` markers, optionally preceded by an HTML comment
8
+ like `<!-- hand-written -->`).
9
+ - If `version` is already present, the file is skipped (idempotent).
10
+ - Otherwise inserts `version: 1.0.0`, `last_updated: 2026-05`, and
11
+ `stability: stable` BEFORE the closing `---` line. Existing keys
12
+ are preserved as-is.
13
+
14
+ Usage:
15
+ python3 tools/migrations/add-version-frontmatter.py # dry-run
16
+ python3 tools/migrations/add-version-frontmatter.py --write # apply
17
+
18
+ After running, verify with:
19
+ python3 tools/audit/frontmatter-check.py
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ from pathlib import Path
25
+
26
+ ROOT = Path(__file__).resolve().parents[2]
27
+ KNOWLEDGE = ROOT / "knowledge"
28
+
29
+ DEFAULT_VERSION = "1.0.0"
30
+ DEFAULT_LAST_UPDATED = "2026-05"
31
+ DEFAULT_STABILITY = "stable"
32
+
33
+
34
+ def find_frontmatter_bounds(text: str) -> tuple[int, int] | None:
35
+ """Return (start_line_idx, end_line_idx) of frontmatter, exclusive of
36
+ the closing `---` line marker (so insertions go ABOVE end_line_idx).
37
+
38
+ Skips leading HTML comments and blank lines before the opening `---`.
39
+ Returns None if no frontmatter block found.
40
+ """
41
+ lines = text.splitlines()
42
+ cursor = 0
43
+
44
+ # Skip leading HTML comments + blank lines
45
+ while cursor < len(lines):
46
+ stripped = lines[cursor].strip()
47
+ if stripped.startswith("<!--") and stripped.endswith("-->"):
48
+ cursor += 1
49
+ continue
50
+ if stripped == "":
51
+ cursor += 1
52
+ continue
53
+ break
54
+
55
+ # Now expect opening `---`
56
+ if cursor >= len(lines) or lines[cursor].strip() != "---":
57
+ return None
58
+
59
+ start = cursor + 1
60
+ # Find closing `---`
61
+ for i in range(start, len(lines)):
62
+ if lines[i].strip() == "---":
63
+ return (start, i)
64
+ return None
65
+
66
+
67
+ def has_version_key(text: str, start: int, end: int) -> bool:
68
+ lines = text.splitlines()
69
+ for line in lines[start:end]:
70
+ if line.lstrip().startswith("version:"):
71
+ return True
72
+ return False
73
+
74
+
75
+ def insert_version_keys(text: str, start: int, end: int) -> str:
76
+ lines = text.splitlines(keepends=True)
77
+ insertion = (
78
+ f"version: {DEFAULT_VERSION}\n"
79
+ f"last_updated: {DEFAULT_LAST_UPDATED}\n"
80
+ f"stability: {DEFAULT_STABILITY}\n"
81
+ )
82
+ # Insert before the closing `---` line (line index `end`)
83
+ lines.insert(end, insertion)
84
+ return "".join(lines)
85
+
86
+
87
+ def process_file(path: Path, write: bool) -> str:
88
+ """Returns one of: 'skip-no-frontmatter', 'skip-already-versioned',
89
+ 'updated', 'would-update'."""
90
+ text = path.read_text(encoding="utf-8")
91
+ bounds = find_frontmatter_bounds(text)
92
+ if bounds is None:
93
+ return "skip-no-frontmatter"
94
+
95
+ start, end = bounds
96
+ if has_version_key(text, start, end):
97
+ return "skip-already-versioned"
98
+
99
+ new_text = insert_version_keys(text, start, end)
100
+ if write:
101
+ path.write_text(new_text, encoding="utf-8")
102
+ return "updated"
103
+ return "would-update"
104
+
105
+
106
+ def main() -> None:
107
+ parser = argparse.ArgumentParser(description=__doc__)
108
+ parser.add_argument("--write", action="store_true", help="Apply changes (without this, dry-run)")
109
+ args = parser.parse_args()
110
+
111
+ files = sorted(KNOWLEDGE.rglob("*.md"))
112
+ counts = {"updated": 0, "would-update": 0, "skip-already-versioned": 0, "skip-no-frontmatter": 0}
113
+
114
+ for path in files:
115
+ result = process_file(path, args.write)
116
+ counts[result] += 1
117
+
118
+ print()
119
+ if args.write:
120
+ print(f" ✓ Updated: {counts['updated']}")
121
+ else:
122
+ print(f" Would update: {counts['would-update']}")
123
+ print(f" Skipped (already versioned): {counts['skip-already-versioned']}")
124
+ print(f" Skipped (no frontmatter): {counts['skip-no-frontmatter']}")
125
+ print(f" Total scanned: {sum(counts.values())}")
126
+ print()
127
+ if not args.write and counts["would-update"] > 0:
128
+ print("Re-run with --write to apply.")
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()