@geraldmaron/construct 1.1.0 → 1.2.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 (425) hide show
  1. package/README.md +30 -18
  2. package/bin/construct +392 -91
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +112 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/audit-skills.mjs +4 -0
  20. package/lib/audit-specialists.mjs +285 -0
  21. package/lib/audit-trail.mjs +77 -10
  22. package/lib/auto-docs.mjs +87 -18
  23. package/lib/beads/drift.mjs +27 -6
  24. package/lib/beads-client.mjs +49 -121
  25. package/lib/beads-optimistic.mjs +5 -12
  26. package/lib/brand-fonts.mjs +93 -0
  27. package/lib/brand-prose.mjs +214 -0
  28. package/lib/brand-tokens.mjs +92 -0
  29. package/lib/bridges/copilot-proxy.mjs +13 -26
  30. package/lib/capability-ledger.mjs +156 -0
  31. package/lib/certification/artifact-fixtures.mjs +132 -0
  32. package/lib/certification/artifact-gates.mjs +63 -0
  33. package/lib/certification/artifact-provenance.mjs +97 -0
  34. package/lib/certification/canonical-scenarios.mjs +78 -0
  35. package/lib/certification/cli.mjs +191 -0
  36. package/lib/certification/dashboard-api.mjs +71 -0
  37. package/lib/certification/demo-parity.mjs +116 -0
  38. package/lib/certification/document-io-fixtures.mjs +246 -0
  39. package/lib/certification/document-workflow.mjs +97 -0
  40. package/lib/certification/eval-bridge.mjs +77 -0
  41. package/lib/certification/model-routing.mjs +149 -0
  42. package/lib/certification/prompt-budget.mjs +119 -0
  43. package/lib/certification/rc-gate.mjs +206 -0
  44. package/lib/certification/real-llm-scenarios.mjs +303 -0
  45. package/lib/certification/role-cards.mjs +113 -0
  46. package/lib/certification/role-overlays.mjs +117 -0
  47. package/lib/certification/run.mjs +122 -0
  48. package/lib/certification/runner.mjs +323 -0
  49. package/lib/certification/scenarios.mjs +51 -0
  50. package/lib/certification/skill-inventory.mjs +289 -0
  51. package/lib/certification/skill-scenarios.mjs +147 -0
  52. package/lib/certification/specialist-contracts.mjs +85 -0
  53. package/lib/certification/specialist-scenarios.mjs +175 -0
  54. package/lib/certification/stale-impact.mjs +146 -0
  55. package/lib/certification/status.mjs +252 -0
  56. package/lib/certification/store.mjs +77 -0
  57. package/lib/chat/cli.mjs +333 -0
  58. package/lib/chat/command-suggest.mjs +161 -0
  59. package/lib/chat/commands.mjs +215 -0
  60. package/lib/chat/config.mjs +142 -0
  61. package/lib/chat/context-compactor.mjs +250 -0
  62. package/lib/chat/context-continuation.mjs +253 -0
  63. package/lib/chat/continuation-source.mjs +58 -0
  64. package/lib/chat/demo-guide.mjs +61 -0
  65. package/lib/chat/design-tokens.mjs +91 -0
  66. package/lib/chat/desktop-binary.mjs +79 -0
  67. package/lib/chat/desktop-build.mjs +130 -0
  68. package/lib/chat/desktop-launcher.mjs +133 -0
  69. package/lib/chat/evidence.mjs +145 -0
  70. package/lib/chat/export.mjs +74 -0
  71. package/lib/chat/harness/driver.mjs +91 -0
  72. package/lib/chat/list-picker.mjs +112 -0
  73. package/lib/chat/model-picker.mjs +356 -0
  74. package/lib/chat/openrouter-fallback.mjs +151 -0
  75. package/lib/chat/permission-prompt.mjs +33 -0
  76. package/lib/chat/picker-catalog.mjs +45 -0
  77. package/lib/chat/policy-telemetry.mjs +34 -0
  78. package/lib/chat/present.mjs +245 -0
  79. package/lib/chat/session-context.mjs +39 -0
  80. package/lib/chat/session-persist.mjs +73 -0
  81. package/lib/chat/session-restore.mjs +71 -0
  82. package/lib/chat/session-settings.mjs +53 -0
  83. package/lib/chat/system-prompt.mjs +52 -0
  84. package/lib/chat/transparency.mjs +93 -0
  85. package/lib/chat/tui/color-scheme.mjs +42 -0
  86. package/lib/chat/tui/markdown.mjs +123 -0
  87. package/lib/chat/tui/presentation.mjs +100 -0
  88. package/lib/chat/tui/render.mjs +500 -0
  89. package/lib/chat/tui/turn-block.mjs +284 -0
  90. package/lib/chat/tui/turn-present.mjs +18 -0
  91. package/lib/chat/tui/turn-state.mjs +88 -0
  92. package/lib/chat/tui/usage.mjs +122 -0
  93. package/lib/chat/web-commands.mjs +146 -0
  94. package/lib/chat/web-launcher.mjs +63 -0
  95. package/lib/chat/web-picker-keys.mjs +46 -0
  96. package/lib/chat/web-session.mjs +159 -0
  97. package/lib/cli-commands.mjs +232 -11
  98. package/lib/comment-lint.mjs +6 -4
  99. package/lib/config/schema.mjs +36 -5
  100. package/lib/contract-schemas/decision.json +50 -0
  101. package/lib/contract-schemas/implementation.json +51 -0
  102. package/lib/contract-schemas/review-report.json +32 -0
  103. package/lib/contract-schemas/test-report.json +43 -0
  104. package/lib/contracts/construct-handoff.mjs +60 -0
  105. package/lib/contracts/validate.mjs +32 -3
  106. package/lib/contracts/violation-log.mjs +58 -5
  107. package/lib/dashboard-demo.mjs +71 -0
  108. package/lib/dashboard-static.mjs +19 -5
  109. package/lib/decisions/registry.mjs +5 -3
  110. package/lib/deck-export-pptx.mjs +1152 -0
  111. package/lib/demo-recording.mjs +142 -0
  112. package/lib/demo-script.mjs +114 -0
  113. package/lib/demo-surface.mjs +249 -0
  114. package/lib/demo.mjs +703 -0
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +302 -0
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +4 -2
  119. package/lib/doctor/project-adapters.mjs +44 -0
  120. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  121. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  122. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  123. package/lib/doctor/watchers/service-health.mjs +1 -1
  124. package/lib/document-export.mjs +360 -36
  125. package/lib/document-extract.mjs +88 -0
  126. package/lib/document-ingest.mjs +32 -18
  127. package/lib/embed/cli.mjs +2 -2
  128. package/lib/embed/daemon.mjs +18 -46
  129. package/lib/embed/semantic.mjs +5 -2
  130. package/lib/embed/worker.mjs +2 -2
  131. package/lib/embedded-contract/model-resolve.mjs +10 -9
  132. package/lib/env-config.mjs +26 -0
  133. package/lib/evals/dataset.mjs +137 -0
  134. package/lib/evals/gates.mjs +175 -0
  135. package/lib/graph/build-co-change.mjs +54 -0
  136. package/lib/graph/build-from-registry.mjs +136 -0
  137. package/lib/graph/build-import-graph.mjs +153 -0
  138. package/lib/graph/cli.mjs +110 -0
  139. package/lib/graph/impact-cli.mjs +89 -0
  140. package/lib/graph/impact.mjs +108 -0
  141. package/lib/graph/staleness.mjs +44 -0
  142. package/lib/graph/store.mjs +172 -0
  143. package/lib/handoffs/cleanup.mjs +1 -1
  144. package/lib/health-check.mjs +90 -76
  145. package/lib/hooks/artifact-release-gate.mjs +43 -0
  146. package/lib/hooks/audit-trail.mjs +14 -28
  147. package/lib/hooks/brand-prose-lint.mjs +38 -0
  148. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  149. package/lib/hooks/session-optimize.mjs +84 -207
  150. package/lib/hooks/session-start.mjs +4 -5
  151. package/lib/host-disposition.mjs +7 -0
  152. package/lib/improvement/cli.mjs +189 -0
  153. package/lib/improvement/controller.mjs +137 -0
  154. package/lib/improvement/proposal.mjs +120 -0
  155. package/lib/improvement/specialist-loop.mjs +192 -0
  156. package/lib/improvement/store.mjs +89 -0
  157. package/lib/improvement/surface.mjs +219 -0
  158. package/lib/ingest-tooling.mjs +97 -0
  159. package/lib/init/doc-lanes.mjs +165 -0
  160. package/lib/init-docs.mjs +31 -178
  161. package/lib/init-unified.mjs +27 -97
  162. package/lib/init-update-guide.mjs +102 -0
  163. package/lib/init-update.mjs +32 -1
  164. package/lib/init.mjs +8 -0
  165. package/lib/install/desktop-binary-download.mjs +85 -0
  166. package/lib/install/legacy-global-cleanup.mjs +189 -0
  167. package/lib/intake/daemon.mjs +2 -0
  168. package/lib/intake/git-queue.mjs +9 -8
  169. package/lib/intake/queue.mjs +2 -1
  170. package/lib/intake/session-prelude.mjs +90 -1
  171. package/lib/libreoffice-export.mjs +97 -0
  172. package/lib/logging/rotate.mjs +9 -0
  173. package/lib/maintenance/docker-reclaim.mjs +206 -0
  174. package/lib/mcp/external-schema-cost.mjs +74 -0
  175. package/lib/mcp/server.mjs +84 -9
  176. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  177. package/lib/mcp/tool-budget.mjs +55 -4
  178. package/lib/mcp/tools/document.mjs +18 -4
  179. package/lib/mcp/tools/skills.mjs +32 -3
  180. package/lib/mcp/tools/workflow.mjs +13 -1
  181. package/lib/model-free-selector.mjs +18 -0
  182. package/lib/model-registry.mjs +13 -229
  183. package/lib/model-router.mjs +425 -93
  184. package/lib/models/behavior-matrix.mjs +289 -0
  185. package/lib/models/catalog.mjs +209 -0
  186. package/lib/models/execution-capability-profile.mjs +196 -0
  187. package/lib/models/execution-policy.mjs +307 -0
  188. package/lib/models/provider-poll.mjs +383 -0
  189. package/lib/npm-spawn-env.mjs +17 -0
  190. package/lib/ollama/installed-models.mjs +129 -0
  191. package/lib/oracle/actions.mjs +187 -0
  192. package/lib/oracle/artifact-gate.mjs +99 -0
  193. package/lib/oracle/cli.mjs +204 -0
  194. package/lib/oracle/daemon-entry.mjs +14 -0
  195. package/lib/oracle/dispatch.mjs +81 -0
  196. package/lib/oracle/execute.mjs +143 -0
  197. package/lib/oracle/gaps.mjs +76 -0
  198. package/lib/oracle/index.mjs +84 -0
  199. package/lib/oracle/issues.mjs +164 -0
  200. package/lib/oracle/org-graph.mjs +170 -0
  201. package/lib/oracle/policy.mjs +80 -0
  202. package/lib/oracle/read-model.mjs +398 -0
  203. package/lib/oracle/reconcile.mjs +191 -0
  204. package/lib/oracle/routing.mjs +89 -0
  205. package/lib/oracle/synthesize.mjs +384 -0
  206. package/lib/oracle/verdicts.mjs +51 -0
  207. package/lib/orchestration/worker.mjs +88 -27
  208. package/lib/orchestration-policy.mjs +50 -0
  209. package/lib/parity.mjs +96 -2
  210. package/lib/persona-sections.mjs +66 -0
  211. package/lib/playwright-demo.mjs +252 -0
  212. package/lib/project-init-shared.mjs +4 -1
  213. package/lib/prompt-composer.js +9 -4
  214. package/lib/prompt-validation-contract.mjs +24 -0
  215. package/lib/provider-capabilities.js +57 -11
  216. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  217. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  218. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  219. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  220. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  221. package/lib/providers/contract/contract-tests.mjs +57 -0
  222. package/lib/providers/contract/errors.mjs +48 -0
  223. package/lib/providers/contract/interface.mjs +50 -0
  224. package/lib/providers/contract/registry.mjs +102 -0
  225. package/lib/providers/copilot-auth.mjs +297 -0
  226. package/lib/providers/credential-bootstrap.mjs +178 -0
  227. package/lib/providers/credential-catalog.mjs +46 -0
  228. package/lib/providers/credential-sources.mjs +63 -0
  229. package/lib/providers/creds.mjs +5 -2
  230. package/lib/providers/op-run.mjs +59 -0
  231. package/lib/providers/secret-resolver.mjs +159 -0
  232. package/lib/publish-template.mjs +163 -0
  233. package/lib/publish-tooling.mjs +119 -0
  234. package/lib/publish.mjs +305 -0
  235. package/lib/registry/cli.mjs +82 -0
  236. package/lib/registry/consolidation.mjs +147 -0
  237. package/lib/registry/generate-docs.mjs +75 -0
  238. package/lib/registry/skill-verification.mjs +57 -0
  239. package/lib/registry/surface-map.mjs +76 -0
  240. package/lib/registry/validate.mjs +135 -0
  241. package/lib/resources/budget.mjs +82 -0
  242. package/lib/resources/process-budget.mjs +45 -0
  243. package/lib/rules-delivery.mjs +9 -2
  244. package/lib/rules-read.mjs +26 -0
  245. package/lib/runtime-env.mjs +23 -0
  246. package/lib/runtime-pressure.mjs +51 -7
  247. package/lib/schema-infer.mjs +13 -2
  248. package/lib/server/chat-loop.mjs +622 -0
  249. package/lib/server/demo-preview.mjs +63 -0
  250. package/lib/server/index.mjs +229 -8
  251. package/lib/server/langfuse-login.mjs +3 -3
  252. package/lib/service-manager.mjs +61 -9
  253. package/lib/session-store.mjs +1 -1
  254. package/lib/setup.mjs +102 -2
  255. package/lib/specialists/prompt-schema.mjs +19 -10
  256. package/lib/specialists/roster.mjs +43 -0
  257. package/lib/specialists/scaffold.mjs +56 -0
  258. package/lib/storage/backend.mjs +6 -6
  259. package/lib/storage/embeddings-local.mjs +6 -3
  260. package/lib/storage/file-lock.mjs +18 -11
  261. package/lib/storage/hybrid-query.mjs +7 -4
  262. package/lib/storage/state-source.mjs +5 -5
  263. package/lib/storage/sync.mjs +2 -3
  264. package/lib/telemetry/rule-calls.mjs +52 -0
  265. package/lib/template-registry.mjs +2 -2
  266. package/lib/templates/visual-requirements.mjs +27 -51
  267. package/lib/test-corpus-inventory.mjs +313 -0
  268. package/lib/uninstall/uninstall.mjs +19 -7
  269. package/lib/update.mjs +12 -0
  270. package/lib/upgrade.mjs +14 -0
  271. package/lib/wireframe.mjs +20 -14
  272. package/lib/worker/run.mjs +17 -4
  273. package/lib/worker/trace.mjs +11 -3
  274. package/package.json +29 -13
  275. package/personas/construct.md +13 -13
  276. package/platforms/claude/settings.template.json +36 -0
  277. package/rules/common/patterns.md +1 -1
  278. package/rules/common/release-gates.md +4 -3
  279. package/scripts/sync-specialists.mjs +187 -60
  280. package/skills/devops/data-engineering.md +1 -1
  281. package/skills/docs/adr-workflow.md +1 -0
  282. package/skills/docs/backlog-proposal-workflow.md +1 -0
  283. package/skills/docs/codebase-research-workflow.md +40 -0
  284. package/skills/docs/customer-profile-workflow.md +1 -0
  285. package/skills/docs/document-ingest-workflow.md +1 -0
  286. package/skills/docs/evidence-ingest-workflow.md +1 -0
  287. package/skills/docs/init-docs.md +2 -2
  288. package/skills/docs/init-project.md +8 -3
  289. package/skills/docs/prd-workflow.md +24 -1
  290. package/skills/docs/prfaq-workflow.md +1 -0
  291. package/skills/docs/product-intelligence-workflow.md +1 -0
  292. package/skills/docs/product-signal-workflow.md +1 -0
  293. package/skills/docs/research-workflow.md +54 -37
  294. package/skills/docs/runbook-workflow.md +1 -0
  295. package/skills/docs/strategy-workflow.md +1 -0
  296. package/skills/docs/user-research-workflow.md +40 -0
  297. package/skills/operating/orchestration-reference.md +1 -1
  298. package/skills/roles/architect.md +5 -0
  299. package/skills/roles/operator.docs.md +4 -0
  300. package/skills/routing.md +4 -2
  301. package/specialists/artifact-manifest.json +480 -0
  302. package/specialists/artifact-manifest.schema.json +53 -0
  303. package/specialists/audit-enrichments.json +454 -0
  304. package/specialists/contracts.json +37 -25
  305. package/specialists/prompts/_shared/validation-contract.md +26 -0
  306. package/specialists/prompts/cx-accessibility.md +21 -2
  307. package/specialists/prompts/cx-ai-engineer.md +24 -1
  308. package/specialists/prompts/cx-architect.md +4 -1
  309. package/specialists/prompts/cx-business-strategist.md +23 -2
  310. package/specialists/prompts/cx-data-analyst.md +22 -1
  311. package/specialists/prompts/cx-data-engineer.md +23 -2
  312. package/specialists/prompts/cx-debugger.md +21 -2
  313. package/specialists/prompts/cx-designer.md +26 -3
  314. package/specialists/prompts/cx-devil-advocate.md +19 -2
  315. package/specialists/prompts/cx-docs-keeper.md +28 -1
  316. package/specialists/prompts/cx-engineer.md +22 -1
  317. package/specialists/prompts/cx-evaluator.md +18 -1
  318. package/specialists/prompts/cx-explorer.md +21 -2
  319. package/specialists/prompts/cx-legal-compliance.md +21 -2
  320. package/specialists/prompts/cx-operations.md +21 -2
  321. package/specialists/prompts/cx-oracle.md +94 -0
  322. package/specialists/prompts/cx-orchestrator.md +20 -1
  323. package/specialists/prompts/cx-platform-engineer.md +25 -2
  324. package/specialists/prompts/cx-product-manager.md +32 -1
  325. package/specialists/prompts/cx-qa.md +24 -2
  326. package/specialists/prompts/cx-rd-lead.md +23 -2
  327. package/specialists/prompts/cx-release-manager.md +23 -2
  328. package/specialists/prompts/cx-researcher.md +22 -2
  329. package/specialists/prompts/cx-reviewer.md +18 -1
  330. package/specialists/prompts/cx-security.md +22 -2
  331. package/specialists/prompts/cx-sre.md +30 -3
  332. package/specialists/prompts/cx-test-automation.md +7 -1
  333. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  334. package/specialists/prompts/cx-ux-researcher.md +21 -2
  335. package/specialists/registry.json +52 -173
  336. package/specialists/tone-profiles.json +42 -0
  337. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  338. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  339. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  340. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  341. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  342. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  343. package/templates/demos/vhs/construct-cockpit.json +24 -0
  344. package/templates/distribution/construct-brand.typ +446 -0
  345. package/templates/distribution/construct-decision.typ +38 -0
  346. package/templates/distribution/construct-deck.html +95 -0
  347. package/templates/distribution/construct-pdf.typ +38 -0
  348. package/templates/distribution/construct-prd.typ +38 -0
  349. package/templates/distribution/construct-research.typ +38 -0
  350. package/templates/distribution/construct-web.html +92 -0
  351. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  352. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  353. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  354. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  355. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  356. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  357. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  358. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  359. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  360. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  361. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  362. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  363. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  364. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  365. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  366. package/templates/distribution/fonts/README.md +36 -0
  367. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  368. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  369. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  370. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  371. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  372. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  373. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  374. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  375. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  376. package/templates/distribution/icons/activity.svg +15 -0
  377. package/templates/distribution/icons/alert-triangle.svg +17 -0
  378. package/templates/distribution/icons/book-open.svg +16 -0
  379. package/templates/distribution/icons/bot.svg +20 -0
  380. package/templates/distribution/icons/brain.svg +22 -0
  381. package/templates/distribution/icons/circle-check.svg +16 -0
  382. package/templates/distribution/icons/clipboard-check.svg +17 -0
  383. package/templates/distribution/icons/cpu.svg +28 -0
  384. package/templates/distribution/icons/database.svg +17 -0
  385. package/templates/distribution/icons/eye.svg +16 -0
  386. package/templates/distribution/icons/file-text.svg +19 -0
  387. package/templates/distribution/icons/gauge.svg +16 -0
  388. package/templates/distribution/icons/git-branch.svg +17 -0
  389. package/templates/distribution/icons/key.svg +17 -0
  390. package/templates/distribution/icons/layers.svg +17 -0
  391. package/templates/distribution/icons/list-checks.svg +19 -0
  392. package/templates/distribution/icons/lock.svg +16 -0
  393. package/templates/distribution/icons/message-square.svg +15 -0
  394. package/templates/distribution/icons/network.svg +19 -0
  395. package/templates/distribution/icons/route.svg +17 -0
  396. package/templates/distribution/icons/scale.svg +19 -0
  397. package/templates/distribution/icons/search.svg +16 -0
  398. package/templates/distribution/icons/send.svg +16 -0
  399. package/templates/distribution/icons/server.svg +18 -0
  400. package/templates/distribution/icons/shield-check.svg +16 -0
  401. package/templates/distribution/icons/users.svg +18 -0
  402. package/templates/distribution/icons/webhook.svg +17 -0
  403. package/templates/distribution/icons/workflow.svg +17 -0
  404. package/templates/distribution/icons/wrench.svg +15 -0
  405. package/templates/distribution/run.mjs +18 -1
  406. package/templates/docs/adr.md +7 -0
  407. package/templates/docs/construct_guide.md +10 -10
  408. package/templates/docs/customer-profile.md +4 -0
  409. package/templates/docs/one-pager.md +4 -0
  410. package/templates/docs/postmortem.md +31 -0
  411. package/templates/docs/prd-platform.md +19 -0
  412. package/templates/docs/prd.md +15 -0
  413. package/templates/docs/prfaq.md +7 -0
  414. package/templates/docs/qa-strategy.md +103 -0
  415. package/templates/docs/research-brief.md +8 -0
  416. package/templates/docs/rfc-platform.md +12 -0
  417. package/templates/docs/test-plan.md +7 -0
  418. package/lib/ingest/chunker.mjs +0 -94
  419. package/lib/ingest/pipeline.mjs +0 -53
  420. package/lib/ingest/store.mjs +0 -82
  421. package/lib/mode-commands.mjs +0 -122
  422. package/lib/policy/unified-gates.mjs +0 -96
  423. package/lib/profiles/validate-custom.mjs +0 -114
  424. package/lib/services/telemetry-backend.mjs +0 -177
  425. package/lib/storage/fusion.mjs +0 -95
@@ -0,0 +1,192 @@
1
+ /**
2
+ * lib/diagram-export.mjs — distribution diagram styling for publish exports.
3
+ *
4
+ * Publish path renders hand-drawn D2 (--sketch geometry) and Mermaid (handDrawn
5
+ * look, Caveat handwriting) in a monochrome ink palette — black/grey strokes,
6
+ * light surfaces, near-black text — for an Excalidraw-adjacent human aesthetic
7
+ * that stays consistent with the document brand. Color is reserved for explicit
8
+ * per-node emphasis in the source, not the default theme.
9
+ */
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
16
+ const ICON_DIR = path.join(REPO_ROOT, 'templates', 'distribution', 'icons');
17
+
18
+ // Monochrome diagram palette, mirroring the ink ramp in construct-brand.typ so
19
+ // embedded diagrams and surrounding body share one design language.
20
+
21
+ const MONO = {
22
+ ink: '#0a0c10',
23
+ inkBody: '#23272e',
24
+ muted: '#565c66',
25
+ faint: '#9499a2',
26
+ hairline: '#e3e4e8',
27
+ hairlineStrong: '#cdd0d6',
28
+ surface: '#fafafa',
29
+ surfaceAlt: '#f3f4f6',
30
+ paper: '#ffffff',
31
+ };
32
+
33
+ export const DISTRIBUTION_D2_THEME = '0';
34
+ export const DISTRIBUTION_D2_PAD = '16';
35
+ export const DISTRIBUTION_D2_SCALE = '0.72';
36
+ export const DISTRIBUTION_D2_SKETCH = '1';
37
+ export const DISTRIBUTION_D2_FONT_SIZE = '14';
38
+ export const DISTRIBUTION_MERMAID_MIME = 'image/png';
39
+ export const DISTRIBUTION_MERMAID_WIDTH = '480';
40
+ export const DISTRIBUTION_MERMAID_SCALE = '1';
41
+ export const DISTRIBUTION_FIGURE_MAX_WIDTH = '58%';
42
+
43
+ // Handwritten font for Mermaid labels so its hand-drawn look matches D2 sketch
44
+ // mode. Caveat ships bundled (templates/distribution/fonts/handwritten) and is
45
+ // installed for the headless Chrome that mmdc drives; the fallbacks stay in the
46
+ // handwriting register so a missing face never drops to a serif.
47
+
48
+ const MERMAID_FONT = 'Caveat, Comic Sans MS, cursive';
49
+
50
+ const MERMAID_INIT = `%%{init: {
51
+ 'theme': 'base',
52
+ 'look': 'handDrawn',
53
+ 'fontFamily': '${MERMAID_FONT}',
54
+ 'themeVariables': {
55
+ 'fontFamily': '${MERMAID_FONT}',
56
+ 'fontSize': '19px',
57
+ 'primaryColor': '${MONO.surfaceAlt}',
58
+ 'primaryTextColor': '${MONO.ink}',
59
+ 'primaryBorderColor': '${MONO.ink}',
60
+ 'lineColor': '${MONO.muted}',
61
+ 'secondaryColor': '${MONO.paper}',
62
+ 'secondaryTextColor': '${MONO.ink}',
63
+ 'secondaryBorderColor': '${MONO.ink}',
64
+ 'tertiaryColor': '${MONO.surface}',
65
+ 'tertiaryTextColor': '${MONO.ink}',
66
+ 'tertiaryBorderColor': '${MONO.hairlineStrong}',
67
+ 'clusterBkg': '${MONO.surface}',
68
+ 'clusterBorder': '${MONO.hairlineStrong}',
69
+ 'edgeLabelBackground': '${MONO.paper}',
70
+ 'titleColor': '${MONO.ink}',
71
+ 'noteBkgColor': '${MONO.surface}',
72
+ 'noteTextColor': '${MONO.inkBody}',
73
+ 'noteBorderColor': '${MONO.hairlineStrong}',
74
+ 'actorBkg': '${MONO.surfaceAlt}',
75
+ 'actorBorder': '${MONO.ink}',
76
+ 'actorTextColor': '${MONO.ink}',
77
+ 'actorLineColor': '${MONO.hairlineStrong}',
78
+ 'signalColor': '${MONO.inkBody}',
79
+ 'signalTextColor': '${MONO.inkBody}',
80
+ 'labelBoxBkgColor': '${MONO.surface}',
81
+ 'labelBoxBorderColor': '${MONO.hairlineStrong}',
82
+ 'labelTextColor': '${MONO.ink}',
83
+ 'loopTextColor': '${MONO.inkBody}',
84
+ 'activationBkgColor': '${MONO.hairline}',
85
+ 'activationBorderColor': '${MONO.muted}'
86
+ }
87
+ }}%%`;
88
+
89
+ const CHROME_CANDIDATES = [
90
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
91
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
92
+ '/usr/bin/google-chrome',
93
+ '/usr/bin/chromium',
94
+ '/usr/bin/chromium-browser',
95
+ ];
96
+
97
+ export function resolvePuppeteerExecutable(env = process.env) {
98
+ if (env.PUPPETEER_EXECUTABLE_PATH) return env.PUPPETEER_EXECUTABLE_PATH;
99
+ for (const candidate of CHROME_CANDIDATES) {
100
+ try {
101
+ if (fs.existsSync(candidate)) return candidate;
102
+ } catch {
103
+ /* skip */
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+
109
+ export function injectMermaidBrandTheme(code) {
110
+ const body = String(code || '').trim();
111
+ if (!body) return body;
112
+ if (/%%\{init:/.test(body)) return body;
113
+ return `${MERMAID_INIT}\n${body}\n`;
114
+ }
115
+
116
+ // `icon: @name` in a d2 block resolves to the bundled monochrome Lucide icon at
117
+ // templates/distribution/icons/name.svg, keeping source markdown portable while
118
+ // d2 (which runs in a temp dir) still receives an absolute path it can read.
119
+
120
+ export function resolveIconTokens(code) {
121
+ return String(code || '').replace(
122
+ /icon:\s*@([a-z0-9-]+)/gi,
123
+ (match, name) => {
124
+ const file = path.join(ICON_DIR, `${name}.svg`);
125
+ return fs.existsSync(file) ? `icon: ${file}` : match;
126
+ },
127
+ );
128
+ }
129
+
130
+ export function injectD2DistributionDefaults(code) {
131
+ const body = resolveIconTokens(String(code || '').trim());
132
+ if (!body) return body;
133
+ if (/CONSTRUCT_D2_DEFAULTS/.test(body)) return body;
134
+ return `**: {
135
+ style.font-size: ${DISTRIBUTION_D2_FONT_SIZE}
136
+ style.font-color: "${MONO.ink}"
137
+ style.stroke: "${MONO.ink}"
138
+ style.fill: "${MONO.surfaceAlt}"
139
+ }
140
+ (** -> **)[*]: {
141
+ style.stroke: "${MONO.muted}"
142
+ style.font-color: "${MONO.inkBody}"
143
+ }
144
+
145
+ ${body}
146
+ `;
147
+ }
148
+
149
+ export function preprocessMarkdownDiagrams(content) {
150
+ let out = String(content);
151
+ out = out.replace(/```mermaid\n([\s\S]*?)```/g, (_match, inner) => `\`\`\`mermaid\n${injectMermaidBrandTheme(inner)}\`\`\``);
152
+ out = out.replace(/```d2\n([\s\S]*?)```/g, (_match, inner) => `\`\`\`d2\n${injectD2DistributionDefaults(inner)}\`\`\``);
153
+ return out;
154
+ }
155
+
156
+ export function countDiagramFences(content) {
157
+ const matches = String(content || '').match(/```(?:d2|mermaid)\n/g);
158
+ return matches ? matches.length : 0;
159
+ }
160
+
161
+ export function buildDistributionDiagramEnv(baseEnv = process.env) {
162
+ const env = {
163
+ ...baseEnv,
164
+ CONSTRUCT_D2_THEME: DISTRIBUTION_D2_THEME,
165
+ CONSTRUCT_D2_PAD: DISTRIBUTION_D2_PAD,
166
+ CONSTRUCT_D2_SCALE: DISTRIBUTION_D2_SCALE,
167
+ CONSTRUCT_D2_SKETCH: DISTRIBUTION_D2_SKETCH,
168
+ CONSTRUCT_MERMAID_THEME: 'construct',
169
+ CONSTRUCT_MERMAID_MIME: DISTRIBUTION_MERMAID_MIME,
170
+ CONSTRUCT_MERMAID_WIDTH: DISTRIBUTION_MERMAID_WIDTH,
171
+ CONSTRUCT_MERMAID_SCALE: DISTRIBUTION_MERMAID_SCALE,
172
+ };
173
+ const chrome = resolvePuppeteerExecutable(baseEnv);
174
+ if (chrome) env.PUPPETEER_EXECUTABLE_PATH = chrome;
175
+ return env;
176
+ }
177
+
178
+ export function distributionDiagramDefaults() {
179
+ return {
180
+ d2Theme: 'neutral',
181
+ d2ThemeId: DISTRIBUTION_D2_THEME,
182
+ d2Sketch: true,
183
+ d2Scale: Number(DISTRIBUTION_D2_SCALE),
184
+ d2FontSize: Number(DISTRIBUTION_D2_FONT_SIZE),
185
+ figureMaxWidth: DISTRIBUTION_FIGURE_MAX_WIDTH,
186
+ mermaidLook: 'handDrawn',
187
+ mermaidTheme: 'construct',
188
+ mermaidWidth: Number(DISTRIBUTION_MERMAID_WIDTH),
189
+ mermaidScale: Number(DISTRIBUTION_MERMAID_SCALE),
190
+ accent: MONO.ink,
191
+ };
192
+ }
@@ -0,0 +1,302 @@
1
+ /**
2
+ * lib/diagram.mjs — `construct diagram` command, generates code-driven
3
+ * architecture/flow diagrams from a natural-language description and renders
4
+ * them to SVG/PNG via an external diagram binary, degrading to source-only
5
+ * output when no renderer is installed.
6
+ *
7
+ * Tool evaluation (2026-06), against: visual distinctiveness, reproducible
8
+ * from code, headless Node/CI SVG+PNG render, theming, license:
9
+ * - D2 (terrastruct, MPL-2.0): single self-contained Go binary, clean neutral
10
+ * theme plus `--sketch` hand-drawn mode (publish distribution path), headless
11
+ * `d2 in.d2 out.svg|png`, deterministic from a text file. Chosen as PRIMARY.
12
+ * - Graphviz `dot` (EPL-1.0): ubiquitous, almost always already present on
13
+ * CI images and dev machines, headless `dot -Tsvg|-Tpng`. Chosen as
14
+ * FALLBACK for reach when D2 is absent.
15
+ * - Mermaid: renders natively in many viewers but headless image export
16
+ * needs mermaid-cli, which pulls a headless-Chromium npm tree — disallowed
17
+ * by ADR-0001 (zero-npm-core). Used only as the SOURCE we emit when no
18
+ * binary is present, reusing lib/wireframe.mjs's Mermaid generators so the
19
+ * description still yields a viewable diagram in GitHub/Notion/OpenCode.
20
+ *
21
+ * Degradation contract: when neither D2 nor dot is on PATH the command still
22
+ * succeeds (exit 0) by writing the diagram SOURCE (.d2 by default, or .mmd
23
+ * Mermaid for diagram kinds wireframe.mjs already models) plus an install
24
+ * hint. No bundled binaries, no npm dependencies; renderers are detected at
25
+ * runtime, mirroring lib/runtime/whisper-bootstrap.mjs.
26
+ *
27
+ * Output: .cx/diagrams/<slug>-<ts>.<ext>.
28
+ */
29
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
30
+ import { spawnSync } from 'node:child_process';
31
+ import { join, resolve, dirname } from 'node:path';
32
+
33
+ import { generateWireframe } from './wireframe.mjs';
34
+
35
+ const TYPES = ['architecture', 'flow', 'sequence', 'state', 'er', 'class'];
36
+ const FORMATS = ['svg', 'png'];
37
+
38
+ function slugify(text) {
39
+ return String(text || '')
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9]+/g, '-')
42
+ .replace(/^-+|-+$/g, '')
43
+ .slice(0, 60) || 'diagram';
44
+ }
45
+
46
+ function timestamp() {
47
+ return new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
48
+ }
49
+
50
+ function which(bin) {
51
+ const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', [bin], { encoding: 'utf8' });
52
+ if (result.status !== 0) return null;
53
+ return result.stdout.trim().split('\n')[0] || null;
54
+ }
55
+
56
+ // Renderer detection follows the optional-external-tool pattern: probe PATH,
57
+ // return a structured descriptor, never throw. D2 is preferred for its
58
+ // distinctive look; dot is the ubiquitous fallback.
59
+
60
+ export function locateRenderer() {
61
+ const d2 = which('d2');
62
+ if (d2) return { engine: 'd2', binary: d2, sourceExt: 'd2' };
63
+ const dot = which('dot');
64
+ if (dot) return { engine: 'dot', binary: dot, sourceExt: 'dot' };
65
+ return null;
66
+ }
67
+
68
+ export function installHint() {
69
+ if (process.platform === 'darwin') return 'brew install d2 (or: brew install graphviz)';
70
+ if (process.platform === 'linux') return 'curl -fsSL https://d2lang.com/install.sh | sh (or install graphviz via your distro)';
71
+ return 'See https://d2lang.com/tour/install or https://graphviz.org/download/';
72
+ }
73
+
74
+ function inferType(description, explicit) {
75
+ if (explicit) return explicit;
76
+ const text = String(description || '').toLowerCase();
77
+ if (/\b(sequence|handshake|request|response|api call)\b/.test(text)) return 'sequence';
78
+ if (/\b(state|status|transition|machine|lifecycle)\b/.test(text)) return 'state';
79
+ if (/\b(entity|schema|database|table|relationship|foreign key)\b/.test(text)) return 'er';
80
+ if (/\b(class|inheritance|interface)\b/.test(text)) return 'class';
81
+ if (/\b(flow|funnel|step|decision|process)\b/.test(text)) return 'flow';
82
+ return 'architecture';
83
+ }
84
+
85
+ // Parse `a -> b -> c` (or `a → b → c`) chains out of the description so a
86
+ // terse architecture prompt produces a real edge graph rather than a single
87
+ // node. Falls back to treating the whole description as one node.
88
+
89
+ function parseNodes(description) {
90
+ const text = String(description || '');
91
+ const arrowSplit = text.split(/\s*(?:->|→|=>)\s*/).map((s) => s.trim()).filter(Boolean);
92
+ if (arrowSplit.length >= 2) {
93
+ const stripped = arrowSplit.map((s) => s.replace(/^[a-z0-9\s]*:\s*/i, '').trim() || s);
94
+ return stripped.map((label) => label.split(/[,/]/)[0].trim()).filter(Boolean);
95
+ }
96
+ return [];
97
+ }
98
+
99
+ function nodeId(label, index) {
100
+ const id = String(label).toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
101
+ return id || `n${index}`;
102
+ }
103
+
104
+ function d2Source(description, nodes) {
105
+ const header = `# ${description}\n# D2 source — render: d2 this.d2 out.svg (https://d2lang.com)\n`;
106
+ if (nodes.length >= 2) {
107
+ const lines = nodes.map((label, i) => `${nodeId(label, i)}: ${label}`);
108
+ for (let i = 0; i < nodes.length - 1; i += 1) {
109
+ lines.push(`${nodeId(nodes[i], i)} -> ${nodeId(nodes[i + 1], i + 1)}`);
110
+ }
111
+ return `${header}${lines.join('\n')}\n`;
112
+ }
113
+ return `${header}node: ${description || 'Component'}\n`;
114
+ }
115
+
116
+ function dotSource(description, nodes) {
117
+ const header = `// ${description}\n// Graphviz DOT source — render: dot -Tsvg this.dot -o out.svg\n`;
118
+ const body = [];
119
+ if (nodes.length >= 2) {
120
+ nodes.forEach((label, i) => body.push(` ${nodeId(label, i)} [label="${label}"];`));
121
+ for (let i = 0; i < nodes.length - 1; i += 1) {
122
+ body.push(` ${nodeId(nodes[i], i)} -> ${nodeId(nodes[i + 1], i + 1)};`);
123
+ }
124
+ } else {
125
+ body.push(` node [label="${description || 'Component'}"];`);
126
+ }
127
+ return `${header}digraph G {\n rankdir=LR;\n${body.join('\n')}\n}\n`;
128
+ }
129
+
130
+ // For diagram kinds wireframe.mjs already models (flow/sequence/state/er) we
131
+ // reuse its Mermaid generators when emitting source-only output, so the
132
+ // description maps to a richer diagram than a bare D2 chain.
133
+
134
+ const WIREFRAME_KINDS = { flow: 'flow', sequence: 'sequence', state: 'state', er: 'er' };
135
+
136
+ export function generateDiagram({ description = '', type = null, theme = null } = {}) {
137
+ const kind = inferType(description, type);
138
+ const nodes = parseNodes(description);
139
+
140
+ let source;
141
+ let engineHint;
142
+ if (nodes.length >= 2 || kind === 'architecture' || kind === 'class') {
143
+ source = d2Source(description, nodes);
144
+ engineHint = 'd2';
145
+ } else {
146
+ const wf = generateWireframe({ description, type: WIREFRAME_KINDS[kind] || 'flow' });
147
+ source = wf.content;
148
+ engineHint = 'mermaid';
149
+ }
150
+ return { kind, nodes, source, engineHint, theme };
151
+ }
152
+
153
+ // Render via the detected binary. D2 themes map to numeric ids; an invalid
154
+ // theme name is ignored rather than failing the render. Returns the render
155
+ // result so the caller can fall back to source-only on any non-zero exit.
156
+
157
+ function renderWithD2(binary, sourcePath, outPath, { format, theme }) {
158
+ const args = [];
159
+ const themeName = theme?.toLowerCase();
160
+ if (themeName === 'sketch') {
161
+ args.push('--sketch');
162
+ } else if (themeName) {
163
+ const themeId = D2_THEMES[themeName];
164
+ if (themeId != null) args.push('--theme', String(themeId));
165
+ }
166
+ args.push(sourcePath, outPath);
167
+ return spawnSync(binary, args, { encoding: 'utf8', timeout: 60_000 });
168
+ }
169
+
170
+ const D2_THEMES = {
171
+ neutral: 0,
172
+ 'neutral-grey': 1,
173
+ 'flagship-terrastruct': 3,
174
+ 'cool-classics': 4,
175
+ 'mixed-berry-blue': 5,
176
+ 'grape-soda': 6,
177
+ aubergine: 7,
178
+ 'colorblind-clear': 8,
179
+ 'vanilla-nitro-cola': 100,
180
+ 'orange-creamsicle': 101,
181
+ };
182
+
183
+ function renderWithDot(binary, sourcePath, outPath, { format }) {
184
+ return spawnSync(binary, [`-T${format}`, sourcePath, '-o', outPath], { encoding: 'utf8', timeout: 60_000 });
185
+ }
186
+
187
+ export function printHelp() {
188
+ console.log(`Usage: construct diagram <description> [options]
189
+
190
+ Generates a code-driven diagram from a description and renders it to SVG/PNG
191
+ via D2 (primary) or Graphviz dot (fallback). When neither renderer is
192
+ installed, writes the diagram SOURCE plus an install hint and exits 0.
193
+
194
+ Options:
195
+ --type <t> architecture (default) | flow | sequence | state | er | class
196
+ --format <f> svg (default) | png
197
+ --theme <name> D2 theme name (default: neutral; sketch enables --sketch hand-drawn)
198
+ --out <path> Output path (default: .cx/diagrams/<slug>-<ts>.<ext>)
199
+ --source-only Always write the source file; skip rendering
200
+ -h, --help Show this message
201
+
202
+ Renderers (detected at runtime, never bundled):
203
+ D2 ${process.platform === 'darwin' ? 'brew install d2' : 'https://d2lang.com/tour/install'}
204
+ dot ${process.platform === 'darwin' ? 'brew install graphviz' : 'https://graphviz.org/download/'}
205
+
206
+ Examples:
207
+ construct diagram "web app: client -> api -> db"
208
+ construct diagram "auth flow" --type flow
209
+ construct diagram "client -> api -> db" --format png --theme neutral
210
+ `);
211
+ }
212
+
213
+ function parseArgs(argv) {
214
+ const options = { type: null, format: 'svg', theme: null, out: null, sourceOnly: false, description: [] };
215
+ for (let i = 0; i < argv.length; i += 1) {
216
+ const arg = argv[i];
217
+ if (arg === '--help' || arg === '-h') { options.help = true; continue; }
218
+ if (arg === '--source-only') { options.sourceOnly = true; continue; }
219
+ if (arg === '--type') { options.type = argv[++i]; continue; }
220
+ if (arg.startsWith('--type=')) { options.type = arg.slice(7); continue; }
221
+ if (arg === '--format') { options.format = argv[++i]; continue; }
222
+ if (arg.startsWith('--format=')) { options.format = arg.slice(9); continue; }
223
+ if (arg === '--theme') { options.theme = argv[++i]; continue; }
224
+ if (arg.startsWith('--theme=')) { options.theme = arg.slice(8); continue; }
225
+ if (arg === '--out') { options.out = argv[++i]; continue; }
226
+ if (arg.startsWith('--out=')) { options.out = arg.slice(6); continue; }
227
+ options.description.push(arg);
228
+ }
229
+ options.description = options.description.join(' ').trim();
230
+ return options;
231
+ }
232
+
233
+ export async function runDiagramCli(argv = [], { cwd = process.cwd() } = {}) {
234
+ const options = parseArgs(argv);
235
+ if (options.help || !options.description) {
236
+ printHelp();
237
+ if (!options.help && !options.description) process.exit(1);
238
+ return;
239
+ }
240
+ if (options.type && !TYPES.includes(options.type)) {
241
+ console.error(`Unknown type: ${options.type}. Valid: ${TYPES.join(', ')}`);
242
+ process.exit(1);
243
+ }
244
+ if (!FORMATS.includes(options.format)) {
245
+ console.error(`Unknown format: ${options.format}. Valid: ${FORMATS.join(', ')}`);
246
+ process.exit(1);
247
+ }
248
+
249
+ const effectiveTheme = options.theme || 'neutral';
250
+ const result = generateDiagram({ description: options.description, type: options.type, theme: effectiveTheme });
251
+ const renderer = options.sourceOnly ? null : locateRenderer();
252
+
253
+ const slug = slugify(options.description);
254
+ const dir = options.out ? dirname(resolve(cwd, options.out)) : join(cwd, '.cx', 'diagrams');
255
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
256
+
257
+ // Source is always written. When the source is Mermaid (reused wireframe
258
+ // output) the .d2/.dot renderers can't consume it, so we keep the source
259
+ // extension honest and skip the binary render in that case.
260
+
261
+ const renderableSource = result.engineHint !== 'mermaid';
262
+ const sourceExt = result.engineHint === 'mermaid' ? 'md' : (renderer ? renderer.sourceExt : 'd2');
263
+ const baseName = options.out
264
+ ? resolve(cwd, options.out).replace(/\.[^./]+$/, '')
265
+ : join(dir, `${slug}-${timestamp()}`);
266
+ const sourcePath = `${baseName}.${sourceExt}`;
267
+
268
+ let source = result.source;
269
+ if (renderer && renderableSource) {
270
+ source = renderer.engine === 'dot' ? dotSource(options.description, result.nodes) : d2Source(options.description, result.nodes);
271
+ }
272
+ writeFileSync(sourcePath, source, 'utf8');
273
+
274
+ if (!renderer || !renderableSource) {
275
+ console.log(`Diagram source (${result.kind}, ${result.engineHint}) written to:`);
276
+ console.log(` ${sourcePath}`);
277
+ if (!renderer) {
278
+ console.log(`\nNo diagram renderer found. To render SVG/PNG, install one:`);
279
+ console.log(` ${installHint()}`);
280
+ } else {
281
+ console.log(`\nThis diagram kind renders as Mermaid; paste into any Mermaid-aware viewer (GitHub, Notion, OpenCode).`);
282
+ }
283
+ return;
284
+ }
285
+
286
+ const outPath = `${baseName}.${options.format}`;
287
+ const render = renderer.engine === 'd2'
288
+ ? renderWithD2(renderer.binary, sourcePath, outPath, { format: options.format, theme: effectiveTheme })
289
+ : renderWithDot(renderer.binary, sourcePath, outPath, { format: options.format });
290
+
291
+ if (render.status === 0 && existsSync(outPath)) {
292
+ console.log(`Diagram (${result.kind}) rendered via ${renderer.engine} to:`);
293
+ console.log(` ${outPath}`);
294
+ console.log(`Source: ${sourcePath}`);
295
+ return;
296
+ }
297
+
298
+ console.log(`Diagram source written to:`);
299
+ console.log(` ${sourcePath}`);
300
+ console.log(`\nRenderer ${renderer.engine} failed (exit ${render.status}); source preserved.`);
301
+ if (render.stderr) console.log(` ${render.stderr.trim().split('\n').slice(0, 3).join('\n ')}`);
302
+ }
@@ -20,6 +20,7 @@
20
20
  import fs from "node:fs";
21
21
  import path from "node:path";
22
22
  import { statSync, readFileSync, existsSync } from "node:fs";
23
+ import { spawnSync } from "node:child_process";
23
24
  import { hasIntakeReference } from "./intake/traceability.mjs";
24
25
  import { ensureCxDir } from "./project-init-shared.mjs";
25
26
 
@@ -27,6 +28,7 @@ const args = process.argv.slice(2);
27
28
  const quickMode = args.includes("--quick");
28
29
  const fixMode = args.includes("--fix");
29
30
  const stagedMode = args.includes("--staged");
31
+ const strictMode = args.includes("--strict") || process.env.CI === 'true';
30
32
  const cwdArg = args.find(arg => arg.startsWith("--cwd="));
31
33
  const cwd = cwdArg ? path.resolve(cwdArg.split("=")[1]) : process.cwd();
32
34
 
@@ -195,6 +197,17 @@ function checkContextHasProgress(contextPath) {
195
197
  // not every artifact necessarily comes from an intake packet — but the absence
196
198
  // is a flag for the operator to either stamp the provenance or declare it
197
199
  // intake-independent.
200
+ function gitTrackedMdFiles(cwd, dir) {
201
+ if (!fs.existsSync(dir)) return [];
202
+ const rel = path.relative(cwd, dir).split(path.sep).join('/');
203
+ const result = spawnSync('git', ['ls-files', '--', `${rel}/`], { cwd, encoding: 'utf8' });
204
+ if (result.status !== 0 || !result.stdout) return [];
205
+ return result.stdout
206
+ .split('\n')
207
+ .filter((line) => line.endsWith('.md') && !line.endsWith('/README.md'))
208
+ .map((line) => path.join(cwd, line));
209
+ }
210
+
198
211
  function checkIntakeTraceability(cwd) {
199
212
  const intakeFedDirs = [
200
213
  path.join(cwd, 'docs', 'prd'),
@@ -203,13 +216,10 @@ function checkIntakeTraceability(cwd) {
203
216
  ];
204
217
  let inspected = 0;
205
218
  for (const dir of intakeFedDirs) {
206
- if (!fs.existsSync(dir)) continue;
207
- let entries;
208
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
209
- for (const entry of entries) {
210
- if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
211
- if (entry.name.startsWith('_') || entry.name.toLowerCase() === 'readme.md') continue;
212
- const filePath = path.join(dir, entry.name);
219
+ const files = gitTrackedMdFiles(cwd, dir);
220
+ for (const filePath of files) {
221
+ const base = path.basename(filePath);
222
+ if (base.startsWith('_')) continue;
213
223
  inspected += 1;
214
224
  if (!hasIntakeReference(filePath)) {
215
225
  warnings.push(
@@ -438,6 +448,14 @@ function main() {
438
448
  console.log("💡 Run `construct init:update` to update project to current standards");
439
449
  process.exit(1);
440
450
  }
451
+
452
+ if (strictMode && warnings.length > 0) {
453
+ console.log("❌ Strict mode: warnings are blocking:");
454
+ for (const warning of warnings) {
455
+ console.log(` • ${warning}`);
456
+ }
457
+ process.exit(1);
458
+ }
441
459
 
442
460
  console.log("✅ Documentation validation passed with warnings");
443
461
  process.exit(0);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lib/doctor/index.mjs — `construct-doctor` daemon main loop.
3
3
  *
4
- * Long-running Node process spawned by `construct up`. Holds the L0 watchers
4
+ * Long-running Node process spawned by `construct dev`. Holds the L0 watchers
5
5
  * for resource pressure, service health, disk + log rotation, and cost. Each
6
6
  * watcher ticks on its own interval; the loop is bounded so a slow watcher
7
7
  * never starves the others. Termination via SIGTERM/SIGINT triggers a clean
@@ -24,9 +24,11 @@ import * as bdWatch from './watchers/bd-watch.mjs';
24
24
  import * as handoffs from './watchers/handoffs.mjs';
25
25
  import * as consistency from './watchers/consistency.mjs';
26
26
  import * as mcpProtocol from './watchers/mcp-protocol.mjs';
27
+ import * as cxBudget from './watchers/cx-budget.mjs';
28
+ import * as graphStaleness from './watchers/graph-staleness.mjs';
27
29
 
28
30
  const STATE_PATH = join(homedir(), '.construct', 'doctor.json');
29
- const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol];
31
+ const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol, cxBudget, graphStaleness];
30
32
 
31
33
  let running = false;
32
34
  let timers = [];
@@ -0,0 +1,44 @@
1
+ /**
2
+ * lib/doctor/project-adapters.mjs — doctor checks for project-scoped host adapters.
3
+ *
4
+ * When inside a Construct-managed project, verifies gitignored adapter artifacts
5
+ * exist for installed hosts (.cursor/mcp.json, .vscode/mcp.json, construct.mdc).
6
+ */
7
+
8
+ import { checkProjectParity } from '../parity.mjs';
9
+ import { detectHostCapabilities } from '../host-capabilities.mjs';
10
+
11
+ const HOST_REQUIRES = {
12
+ cursor: ['cursor'],
13
+ vscode: ['vscode'],
14
+ };
15
+
16
+ export function checkProjectAdaptersForDoctor({ rootDir, projectDir = rootDir }) {
17
+ const parity = checkProjectParity({ rootDir, projectDir });
18
+ if (parity.skipped) return { ok: true, label: 'Project adapters (not a Construct project)', warning: false };
19
+
20
+ const installed = new Set(
21
+ detectHostCapabilities()
22
+ .filter((h) => h.availability === 'installed')
23
+ .map((h) => h.host.toLowerCase().replace(/\s+/g, '')),
24
+ );
25
+ const installedIds = new Set();
26
+ if (installed.has('claudecode') || installed.has('claude')) installedIds.add('claude');
27
+ if (installed.has('vscode')) installedIds.add('vscode');
28
+ if (installed.has('cursor')) installedIds.add('cursor');
29
+
30
+ const failures = [];
31
+ for (const surface of parity.surfaces) {
32
+ if (surface.status === 'absent' && (surface.surface === 'cursor' || surface.surface === 'vscode')) {
33
+ if (installedIds.has(surface.surface)) failures.push(surface);
34
+ }
35
+ if (surface.status === 'drift') failures.push(surface);
36
+ }
37
+
38
+ const ok = failures.length === 0;
39
+ const label = ok
40
+ ? `Project adapters (${parity.summary.filter((s) => s.includes(': ok')).length}/${parity.surfaces.length} ok)`
41
+ : `Project adapters missing or stale — run \`npm run adapters\` (${failures.map((f) => f.surface).join(', ')})`;
42
+
43
+ return { ok, label, warning: !ok, parity };
44
+ }