@geraldmaron/construct 1.1.1 → 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 (408) hide show
  1. package/README.md +29 -19
  2. package/bin/construct +311 -81
  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 +1 -1
  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 +129 -15
  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 +598 -140
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +13 -11
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +3 -1
  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/document-export.mjs +360 -36
  124. package/lib/document-extract.mjs +44 -4
  125. package/lib/document-ingest.mjs +29 -20
  126. package/lib/embed/cli.mjs +2 -2
  127. package/lib/embed/daemon.mjs +18 -46
  128. package/lib/embed/worker.mjs +2 -2
  129. package/lib/embedded-contract/model-resolve.mjs +10 -9
  130. package/lib/env-config.mjs +26 -0
  131. package/lib/evals/dataset.mjs +137 -0
  132. package/lib/evals/gates.mjs +175 -0
  133. package/lib/graph/build-co-change.mjs +54 -0
  134. package/lib/graph/build-from-registry.mjs +136 -0
  135. package/lib/graph/build-import-graph.mjs +153 -0
  136. package/lib/graph/cli.mjs +110 -0
  137. package/lib/graph/impact-cli.mjs +89 -0
  138. package/lib/graph/impact.mjs +108 -0
  139. package/lib/graph/staleness.mjs +44 -0
  140. package/lib/graph/store.mjs +172 -0
  141. package/lib/health-check.mjs +90 -76
  142. package/lib/hooks/artifact-release-gate.mjs +43 -0
  143. package/lib/hooks/brand-prose-lint.mjs +38 -0
  144. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  145. package/lib/hooks/session-optimize.mjs +84 -207
  146. package/lib/hooks/session-start.mjs +4 -5
  147. package/lib/host-disposition.mjs +7 -0
  148. package/lib/improvement/cli.mjs +189 -0
  149. package/lib/improvement/controller.mjs +137 -0
  150. package/lib/improvement/proposal.mjs +120 -0
  151. package/lib/improvement/specialist-loop.mjs +192 -0
  152. package/lib/improvement/store.mjs +89 -0
  153. package/lib/improvement/surface.mjs +219 -0
  154. package/lib/ingest-tooling.mjs +97 -0
  155. package/lib/init/doc-lanes.mjs +165 -0
  156. package/lib/init-docs.mjs +31 -178
  157. package/lib/init-unified.mjs +27 -97
  158. package/lib/init-update-guide.mjs +102 -0
  159. package/lib/init-update.mjs +32 -1
  160. package/lib/init.mjs +8 -0
  161. package/lib/install/desktop-binary-download.mjs +85 -0
  162. package/lib/install/legacy-global-cleanup.mjs +189 -0
  163. package/lib/intake/daemon.mjs +2 -0
  164. package/lib/intake/git-queue.mjs +9 -8
  165. package/lib/intake/queue.mjs +2 -1
  166. package/lib/intake/session-prelude.mjs +90 -1
  167. package/lib/libreoffice-export.mjs +97 -0
  168. package/lib/logging/rotate.mjs +9 -0
  169. package/lib/maintenance/docker-reclaim.mjs +206 -0
  170. package/lib/mcp/external-schema-cost.mjs +74 -0
  171. package/lib/mcp/server.mjs +84 -9
  172. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  173. package/lib/mcp/tool-budget.mjs +55 -4
  174. package/lib/mcp/tools/document.mjs +18 -4
  175. package/lib/mcp/tools/skills.mjs +32 -3
  176. package/lib/mcp/tools/workflow.mjs +13 -1
  177. package/lib/model-free-selector.mjs +18 -0
  178. package/lib/model-registry.mjs +13 -229
  179. package/lib/model-router.mjs +373 -92
  180. package/lib/models/behavior-matrix.mjs +289 -0
  181. package/lib/models/catalog.mjs +209 -0
  182. package/lib/models/execution-capability-profile.mjs +196 -0
  183. package/lib/models/execution-policy.mjs +307 -0
  184. package/lib/models/provider-poll.mjs +383 -0
  185. package/lib/npm-spawn-env.mjs +17 -0
  186. package/lib/ollama/installed-models.mjs +129 -0
  187. package/lib/oracle/actions.mjs +187 -0
  188. package/lib/oracle/artifact-gate.mjs +99 -0
  189. package/lib/oracle/cli.mjs +204 -0
  190. package/lib/oracle/daemon-entry.mjs +14 -0
  191. package/lib/oracle/dispatch.mjs +81 -0
  192. package/lib/oracle/execute.mjs +143 -0
  193. package/lib/oracle/gaps.mjs +76 -0
  194. package/lib/oracle/index.mjs +84 -0
  195. package/lib/oracle/issues.mjs +164 -0
  196. package/lib/oracle/org-graph.mjs +170 -0
  197. package/lib/oracle/policy.mjs +80 -0
  198. package/lib/oracle/read-model.mjs +398 -0
  199. package/lib/oracle/reconcile.mjs +191 -0
  200. package/lib/oracle/routing.mjs +89 -0
  201. package/lib/oracle/synthesize.mjs +384 -0
  202. package/lib/oracle/verdicts.mjs +51 -0
  203. package/lib/orchestration/worker.mjs +88 -27
  204. package/lib/orchestration-policy.mjs +50 -0
  205. package/lib/parity.mjs +96 -2
  206. package/lib/playwright-demo.mjs +252 -0
  207. package/lib/project-init-shared.mjs +4 -1
  208. package/lib/prompt-composer.js +7 -3
  209. package/lib/prompt-validation-contract.mjs +24 -0
  210. package/lib/provider-capabilities.js +57 -11
  211. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  212. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  213. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  214. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  215. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  216. package/lib/providers/contract/contract-tests.mjs +57 -0
  217. package/lib/providers/contract/errors.mjs +48 -0
  218. package/lib/providers/contract/interface.mjs +50 -0
  219. package/lib/providers/contract/registry.mjs +102 -0
  220. package/lib/providers/copilot-auth.mjs +297 -0
  221. package/lib/providers/credential-bootstrap.mjs +178 -0
  222. package/lib/providers/credential-catalog.mjs +46 -0
  223. package/lib/providers/credential-sources.mjs +63 -0
  224. package/lib/providers/creds.mjs +5 -2
  225. package/lib/providers/op-run.mjs +59 -0
  226. package/lib/providers/secret-resolver.mjs +159 -0
  227. package/lib/publish-template.mjs +163 -0
  228. package/lib/publish-tooling.mjs +119 -0
  229. package/lib/publish.mjs +305 -0
  230. package/lib/registry/cli.mjs +82 -0
  231. package/lib/registry/consolidation.mjs +147 -0
  232. package/lib/registry/generate-docs.mjs +75 -0
  233. package/lib/registry/skill-verification.mjs +57 -0
  234. package/lib/registry/surface-map.mjs +76 -0
  235. package/lib/registry/validate.mjs +135 -0
  236. package/lib/resources/budget.mjs +82 -0
  237. package/lib/resources/process-budget.mjs +45 -0
  238. package/lib/rules-delivery.mjs +9 -2
  239. package/lib/rules-read.mjs +26 -0
  240. package/lib/runtime-env.mjs +23 -0
  241. package/lib/runtime-pressure.mjs +51 -7
  242. package/lib/schema-infer.mjs +13 -2
  243. package/lib/server/chat-loop.mjs +622 -0
  244. package/lib/server/demo-preview.mjs +63 -0
  245. package/lib/server/index.mjs +228 -7
  246. package/lib/service-manager.mjs +60 -8
  247. package/lib/setup.mjs +70 -7
  248. package/lib/specialists/prompt-schema.mjs +19 -10
  249. package/lib/specialists/roster.mjs +43 -0
  250. package/lib/specialists/scaffold.mjs +56 -0
  251. package/lib/storage/backend.mjs +6 -6
  252. package/lib/storage/hybrid-query.mjs +7 -4
  253. package/lib/storage/state-source.mjs +5 -5
  254. package/lib/storage/sync.mjs +2 -3
  255. package/lib/telemetry/rule-calls.mjs +52 -0
  256. package/lib/template-registry.mjs +2 -2
  257. package/lib/templates/visual-requirements.mjs +27 -51
  258. package/lib/test-corpus-inventory.mjs +313 -0
  259. package/lib/uninstall/uninstall.mjs +19 -7
  260. package/lib/update.mjs +12 -0
  261. package/lib/upgrade.mjs +14 -0
  262. package/lib/wireframe.mjs +20 -14
  263. package/lib/worker/run.mjs +17 -4
  264. package/lib/worker/trace.mjs +11 -3
  265. package/package.json +28 -14
  266. package/personas/construct.md +2 -2
  267. package/platforms/claude/settings.template.json +36 -0
  268. package/rules/common/patterns.md +1 -1
  269. package/rules/common/release-gates.md +4 -3
  270. package/scripts/sync-specialists.mjs +41 -28
  271. package/skills/devops/data-engineering.md +1 -1
  272. package/skills/docs/adr-workflow.md +1 -0
  273. package/skills/docs/backlog-proposal-workflow.md +1 -0
  274. package/skills/docs/codebase-research-workflow.md +40 -0
  275. package/skills/docs/customer-profile-workflow.md +1 -0
  276. package/skills/docs/document-ingest-workflow.md +1 -0
  277. package/skills/docs/evidence-ingest-workflow.md +1 -0
  278. package/skills/docs/init-docs.md +2 -2
  279. package/skills/docs/init-project.md +7 -2
  280. package/skills/docs/prd-workflow.md +24 -1
  281. package/skills/docs/prfaq-workflow.md +1 -0
  282. package/skills/docs/product-intelligence-workflow.md +1 -0
  283. package/skills/docs/product-signal-workflow.md +1 -0
  284. package/skills/docs/research-workflow.md +54 -37
  285. package/skills/docs/runbook-workflow.md +1 -0
  286. package/skills/docs/strategy-workflow.md +1 -0
  287. package/skills/docs/user-research-workflow.md +40 -0
  288. package/skills/operating/orchestration-reference.md +1 -1
  289. package/skills/roles/architect.md +5 -0
  290. package/skills/roles/operator.docs.md +4 -0
  291. package/skills/routing.md +4 -2
  292. package/specialists/artifact-manifest.json +480 -0
  293. package/specialists/artifact-manifest.schema.json +53 -0
  294. package/specialists/audit-enrichments.json +454 -0
  295. package/specialists/contracts.json +37 -25
  296. package/specialists/prompts/_shared/validation-contract.md +26 -0
  297. package/specialists/prompts/cx-accessibility.md +21 -2
  298. package/specialists/prompts/cx-ai-engineer.md +24 -1
  299. package/specialists/prompts/cx-architect.md +4 -1
  300. package/specialists/prompts/cx-business-strategist.md +23 -2
  301. package/specialists/prompts/cx-data-analyst.md +22 -1
  302. package/specialists/prompts/cx-data-engineer.md +23 -2
  303. package/specialists/prompts/cx-debugger.md +21 -2
  304. package/specialists/prompts/cx-designer.md +26 -3
  305. package/specialists/prompts/cx-devil-advocate.md +19 -2
  306. package/specialists/prompts/cx-docs-keeper.md +28 -1
  307. package/specialists/prompts/cx-engineer.md +22 -1
  308. package/specialists/prompts/cx-evaluator.md +18 -1
  309. package/specialists/prompts/cx-explorer.md +21 -2
  310. package/specialists/prompts/cx-legal-compliance.md +21 -2
  311. package/specialists/prompts/cx-operations.md +21 -2
  312. package/specialists/prompts/cx-oracle.md +94 -0
  313. package/specialists/prompts/cx-orchestrator.md +20 -1
  314. package/specialists/prompts/cx-platform-engineer.md +25 -2
  315. package/specialists/prompts/cx-product-manager.md +32 -1
  316. package/specialists/prompts/cx-qa.md +24 -2
  317. package/specialists/prompts/cx-rd-lead.md +23 -2
  318. package/specialists/prompts/cx-release-manager.md +23 -2
  319. package/specialists/prompts/cx-researcher.md +22 -2
  320. package/specialists/prompts/cx-reviewer.md +18 -1
  321. package/specialists/prompts/cx-security.md +22 -2
  322. package/specialists/prompts/cx-sre.md +30 -3
  323. package/specialists/prompts/cx-test-automation.md +7 -1
  324. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  325. package/specialists/prompts/cx-ux-researcher.md +21 -2
  326. package/specialists/registry.json +52 -173
  327. package/specialists/tone-profiles.json +42 -0
  328. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  329. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  330. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  331. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  332. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  333. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  334. package/templates/demos/vhs/construct-cockpit.json +24 -0
  335. package/templates/distribution/construct-brand.typ +446 -0
  336. package/templates/distribution/construct-decision.typ +38 -0
  337. package/templates/distribution/construct-deck.html +95 -0
  338. package/templates/distribution/construct-pdf.typ +38 -0
  339. package/templates/distribution/construct-prd.typ +38 -0
  340. package/templates/distribution/construct-research.typ +38 -0
  341. package/templates/distribution/construct-web.html +92 -0
  342. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  343. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  344. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  345. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  346. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  347. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  348. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  349. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  350. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  351. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  352. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  353. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  354. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  355. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  356. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  357. package/templates/distribution/fonts/README.md +36 -0
  358. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  359. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  360. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  361. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  362. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  363. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  364. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  365. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  366. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  367. package/templates/distribution/icons/activity.svg +15 -0
  368. package/templates/distribution/icons/alert-triangle.svg +17 -0
  369. package/templates/distribution/icons/book-open.svg +16 -0
  370. package/templates/distribution/icons/bot.svg +20 -0
  371. package/templates/distribution/icons/brain.svg +22 -0
  372. package/templates/distribution/icons/circle-check.svg +16 -0
  373. package/templates/distribution/icons/clipboard-check.svg +17 -0
  374. package/templates/distribution/icons/cpu.svg +28 -0
  375. package/templates/distribution/icons/database.svg +17 -0
  376. package/templates/distribution/icons/eye.svg +16 -0
  377. package/templates/distribution/icons/file-text.svg +19 -0
  378. package/templates/distribution/icons/gauge.svg +16 -0
  379. package/templates/distribution/icons/git-branch.svg +17 -0
  380. package/templates/distribution/icons/key.svg +17 -0
  381. package/templates/distribution/icons/layers.svg +17 -0
  382. package/templates/distribution/icons/list-checks.svg +19 -0
  383. package/templates/distribution/icons/lock.svg +16 -0
  384. package/templates/distribution/icons/message-square.svg +15 -0
  385. package/templates/distribution/icons/network.svg +19 -0
  386. package/templates/distribution/icons/route.svg +17 -0
  387. package/templates/distribution/icons/scale.svg +19 -0
  388. package/templates/distribution/icons/search.svg +16 -0
  389. package/templates/distribution/icons/send.svg +16 -0
  390. package/templates/distribution/icons/server.svg +18 -0
  391. package/templates/distribution/icons/shield-check.svg +16 -0
  392. package/templates/distribution/icons/users.svg +18 -0
  393. package/templates/distribution/icons/webhook.svg +17 -0
  394. package/templates/distribution/icons/workflow.svg +17 -0
  395. package/templates/distribution/icons/wrench.svg +15 -0
  396. package/templates/distribution/run.mjs +18 -1
  397. package/templates/docs/adr.md +7 -0
  398. package/templates/docs/construct_guide.md +8 -8
  399. package/templates/docs/customer-profile.md +4 -0
  400. package/templates/docs/one-pager.md +4 -0
  401. package/templates/docs/postmortem.md +31 -0
  402. package/templates/docs/prd-platform.md +19 -0
  403. package/templates/docs/prd.md +15 -0
  404. package/templates/docs/prfaq.md +7 -0
  405. package/templates/docs/qa-strategy.md +103 -0
  406. package/templates/docs/research-brief.md +8 -0
  407. package/templates/docs/rfc-platform.md +12 -0
  408. package/templates/docs/test-plan.md +7 -0
@@ -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
+ }
package/lib/diagram.mjs CHANGED
@@ -6,10 +6,9 @@
6
6
  *
7
7
  * Tool evaluation (2026-06), against: visual distinctiveness, reproducible
8
8
  * from code, headless Node/CI SVG+PNG render, theming, license:
9
- * - D2 (terrastruct, MPL-2.0): single self-contained Go binary, distinctive
10
- * sketch look plus first-class themes, headless `d2 in.d2 out.svg|png`,
11
- * deterministic from a text file. Chosen as PRIMARY for visual
12
- * distinctiveness and one-binary install.
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.
13
12
  * - Graphviz `dot` (EPL-1.0): ubiquitous, almost always already present on
14
13
  * CI images and dev machines, headless `dot -Tsvg|-Tpng`. Chosen as
15
14
  * FALLBACK for reach when D2 is absent.
@@ -157,8 +156,11 @@ export function generateDiagram({ description = '', type = null, theme = null }
157
156
 
158
157
  function renderWithD2(binary, sourcePath, outPath, { format, theme }) {
159
158
  const args = [];
160
- if (theme) {
161
- const themeId = D2_THEMES[theme.toLowerCase()];
159
+ const themeName = theme?.toLowerCase();
160
+ if (themeName === 'sketch') {
161
+ args.push('--sketch');
162
+ } else if (themeName) {
163
+ const themeId = D2_THEMES[themeName];
162
164
  if (themeId != null) args.push('--theme', String(themeId));
163
165
  }
164
166
  args.push(sourcePath, outPath);
@@ -176,7 +178,6 @@ const D2_THEMES = {
176
178
  'colorblind-clear': 8,
177
179
  'vanilla-nitro-cola': 100,
178
180
  'orange-creamsicle': 101,
179
- sketch: 200,
180
181
  };
181
182
 
182
183
  function renderWithDot(binary, sourcePath, outPath, { format }) {
@@ -193,7 +194,7 @@ installed, writes the diagram SOURCE plus an install hint and exits 0.
193
194
  Options:
194
195
  --type <t> architecture (default) | flow | sequence | state | er | class
195
196
  --format <f> svg (default) | png
196
- --theme <name> D2 theme name (e.g. neutral, sketch, cool-classics)
197
+ --theme <name> D2 theme name (default: neutral; sketch enables --sketch hand-drawn)
197
198
  --out <path> Output path (default: .cx/diagrams/<slug>-<ts>.<ext>)
198
199
  --source-only Always write the source file; skip rendering
199
200
  -h, --help Show this message
@@ -205,7 +206,7 @@ Renderers (detected at runtime, never bundled):
205
206
  Examples:
206
207
  construct diagram "web app: client -> api -> db"
207
208
  construct diagram "auth flow" --type flow
208
- construct diagram "client -> api -> db" --format png --theme sketch
209
+ construct diagram "client -> api -> db" --format png --theme neutral
209
210
  `);
210
211
  }
211
212
 
@@ -245,7 +246,8 @@ export async function runDiagramCli(argv = [], { cwd = process.cwd() } = {}) {
245
246
  process.exit(1);
246
247
  }
247
248
 
248
- const result = generateDiagram({ description: options.description, type: options.type, theme: options.theme });
249
+ const effectiveTheme = options.theme || 'neutral';
250
+ const result = generateDiagram({ description: options.description, type: options.type, theme: effectiveTheme });
249
251
  const renderer = options.sourceOnly ? null : locateRenderer();
250
252
 
251
253
  const slug = slugify(options.description);
@@ -283,7 +285,7 @@ export async function runDiagramCli(argv = [], { cwd = process.cwd() } = {}) {
283
285
 
284
286
  const outPath = `${baseName}.${options.format}`;
285
287
  const render = renderer.engine === 'd2'
286
- ? renderWithD2(renderer.binary, sourcePath, outPath, { format: options.format, theme: options.theme })
288
+ ? renderWithD2(renderer.binary, sourcePath, outPath, { format: options.format, theme: effectiveTheme })
287
289
  : renderWithDot(renderer.binary, sourcePath, outPath, { format: options.format });
288
290
 
289
291
  if (render.status === 0 && existsSync(outPath)) {
@@ -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);
@@ -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
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * lib/doctor/watchers/cx-budget.mjs — .cx/ disk budget maintenance.
3
+ *
4
+ * When project .cx/ usage crosses the configured cap, runs age/count prune.
5
+ * When still over cap after retention prune, emergency-reclaims oldest
6
+ * traces and worker logs (hard-reject categories only).
7
+ *
8
+ * Tick: 15 min.
9
+ */
10
+
11
+ import { existsSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+
14
+ import { record } from '../audit.mjs';
15
+ import { escalate } from '../escalate.mjs';
16
+ import {
17
+ measureUsage,
18
+ planPrune,
19
+ executePrune,
20
+ planEmergencyReclaim,
21
+ } from '../../resources/budget.mjs';
22
+
23
+ export const name = 'cx-budget';
24
+ export const intervalMs = 15 * 60 * 1000;
25
+
26
+ function projectRoot() {
27
+ return process.env.CONSTRUCT_PROJECT_ROOT || process.cwd();
28
+ }
29
+
30
+ export async function tick() {
31
+ const actions = [];
32
+ const escalations = [];
33
+ const root = projectRoot();
34
+ const cxDir = join(root, '.cx');
35
+ if (!existsSync(cxDir)) {
36
+ return { actions, escalations, notes: [{ skipped: 'no .cx directory' }] };
37
+ }
38
+
39
+ const before = measureUsage(root, process.env);
40
+ if (before.totalCxUsageRatio <= 0.8) {
41
+ return { actions, escalations, notes: [{ usageRatio: before.totalCxUsageRatio }] };
42
+ }
43
+
44
+ const planned = planPrune(root, process.env);
45
+ let reclaimed = { removed: [], bytesFreed: 0 };
46
+ if (planned.length) {
47
+ reclaimed = executePrune(planned);
48
+ record({
49
+ kind: 'action',
50
+ watcher: name,
51
+ action: 'prune',
52
+ target: '.cx/',
53
+ summary: `retention prune removed ${reclaimed.removed.length} file(s), freed ${reclaimed.bytesFreed} bytes`,
54
+ context: { planned: planned.length, removed: reclaimed.removed.length },
55
+ });
56
+ actions.push({ type: 'prune', target: '.cx/', removed: reclaimed.removed.length });
57
+ }
58
+
59
+ const afterPrune = measureUsage(root, process.env);
60
+ if (afterPrune.totalCxUsageRatio > 1) {
61
+ const emergency = planEmergencyReclaim(root, 0, process.env);
62
+ if (emergency.length) {
63
+ const emergencyResult = executePrune(emergency);
64
+ record({
65
+ kind: 'action',
66
+ watcher: name,
67
+ action: 'emergency-reclaim',
68
+ target: '.cx/traces|.cx/runtime/worker',
69
+ summary: `emergency reclaim removed ${emergencyResult.removed.length} file(s), freed ${emergencyResult.bytesFreed} bytes`,
70
+ context: emergencyResult,
71
+ });
72
+ actions.push({ type: 'emergency-reclaim', removed: emergencyResult.removed.length });
73
+ }
74
+ }
75
+
76
+ const after = measureUsage(root, process.env);
77
+ if (after.totalCxUsageRatio > 1) {
78
+ const usedMb = Math.round(after.totalCxBytes / 1024 / 1024);
79
+ const capMb = Math.round(after.totalCxCap / 1024 / 1024);
80
+ const r = await escalate({
81
+ watcher: name,
82
+ eventType: 'service.down',
83
+ summary: `.cx/ still over cap after prune (${usedMb}MB / ${capMb}MB) — raise resources.disk.totalCxMaxMb or delete load-bearing state manually`,
84
+ context: { usageRatio: after.totalCxUsageRatio, usedMb, capMb },
85
+ });
86
+ escalations.push({ eventType: 'service.down', result: r });
87
+ }
88
+
89
+ return {
90
+ actions,
91
+ escalations,
92
+ notes: [{
93
+ usageBefore: before.totalCxUsageRatio,
94
+ usageAfter: after.totalCxUsageRatio,
95
+ bytesFreed: reclaimed.bytesFreed,
96
+ }],
97
+ };
98
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * lib/doctor/watchers/graph-staleness.mjs — dependency graph freshness watcher.
3
+ *
4
+ * Ticks every 30 minutes. When registry/contracts/workflow seeds changed since
5
+ * the last `construct graph build`, records an audit entry so doctor surfaces
6
+ * stale-graph drift without waiting for the next Oracle tick.
7
+ */
8
+
9
+ import { existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+
12
+ import { record } from '../audit.mjs';
13
+ import { checkGraphStaleness } from '../../graph/staleness.mjs';
14
+ import { isConstructPackageRepo } from '../../host-disposition.mjs';
15
+
16
+ export const name = 'graph-staleness';
17
+ export const intervalMs = 30 * 60 * 1000;
18
+
19
+ function projectRoot() {
20
+ return process.env.CONSTRUCT_PROJECT_ROOT || process.cwd();
21
+ }
22
+
23
+ export async function tick() {
24
+ const actions = [];
25
+ const escalations = [];
26
+ const root = projectRoot();
27
+ const graphDir = join(root, '.cx', 'graph');
28
+
29
+ if (!existsSync(graphDir) && !isConstructPackageRepo(root)) {
30
+ return { actions, escalations, notes: [{ skipped: 'no graph and not construct package repo' }] };
31
+ }
32
+
33
+ const state = checkGraphStaleness(root);
34
+ if (!state.present) {
35
+ return { actions, escalations, notes: [{ present: false }] };
36
+ }
37
+ if (!state.stale) {
38
+ return { actions, escalations, notes: [{ present: true, stale: false }] };
39
+ }
40
+
41
+ record({
42
+ kind: 'action',
43
+ watcher: name,
44
+ action: 'graph-stale',
45
+ target: '.cx/graph/',
46
+ summary: state.staleReason,
47
+ context: { storedHash: state.storedHash, currentHash: state.currentHash },
48
+ });
49
+ actions.push({ type: 'graph-stale', target: '.cx/graph/' });
50
+
51
+ return { actions, escalations, notes: [state] };
52
+ }
@@ -2,9 +2,10 @@
2
2
  * lib/doctor/watchers/process-pressure.mjs — continuous process pressure guard.
3
3
  *
4
4
  * Runs the runtime-pressure cleanup on a recurring tick so stale Construct
5
- * helpers don't accumulate between `construct dev` invocations. Any killed
6
- * process is recorded; if the same service-class has been killed >=2 times
7
- * in the last hour, escalates so cx-sre can investigate the underlying churn.
5
+ * helpers and leaked VHS demo-recorder processes don't accumulate between
6
+ * `construct dev` invocations. Any killed process is recorded; if the same
7
+ * service-class has been killed >=2 times in the last hour, escalation lets
8
+ * cx-sre investigate the underlying churn.
8
9
  *
9
10
  * Tick: 60s.
10
11
  */