@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
@@ -1,66 +1,47 @@
1
1
  /**
2
- * lib/templates/visual-requirements.mjs — required visuals per document type.
2
+ * lib/templates/visual-requirements.mjs — required visuals and structure per document type.
3
3
  *
4
- * The machine-readable form of docs/concepts/doc-visual-matrix.md: which doc
5
- * types must carry which visual (a diagram of a given kind, or a table with given
6
- * columns), expressed as postcondition checks so the existing validate.mjs engine
7
- * enforces them. The shipped template for each listed type must satisfy its own
8
- * requirements — pinned by tests/template-visuals.test.mjs — so the templates can
9
- * never drift out of step with the matrix (beads wvbf.10 / wvbf.11).
4
+ * Loads structure and visual requirements from specialists/artifact-manifest.json
5
+ * (single source of truth) and merges legacy types not yet in the manifest.
6
+ * Paired with docs/concepts/doc-visual-matrix.md; pinned by template tests.
10
7
  */
11
8
 
12
9
  import { validateArtifactPostconditions } from '../contracts/validate.mjs';
10
+ import {
11
+ structureRequirementsFromManifest,
12
+ visualRequirementsFromManifest,
13
+ getArtifactEntry,
14
+ } from '../artifact-manifest.mjs';
15
+
16
+ const LEGACY_STRUCTURE = {
17
+ 'persona-artifact': ['Goals', 'Frustrations', 'Decision rights', 'Output contract', 'Failure modes', 'Evidence'],
18
+ 'skill-artifact': ['What this skill produces', 'When to invoke it', 'Competency rubric', 'Failure modes', 'Worked example'],
19
+ 'research-finding': ['SOURCES', 'FINDINGS', 'INFERENCES', 'CONFIDENCE', 'GAPS', 'RECOMMENDATION'],
20
+ };
21
+
22
+ export const STRUCTURE_REQUIREMENTS = {
23
+ ...structureRequirementsFromManifest(),
24
+ ...LEGACY_STRUCTURE,
25
+ };
13
26
 
14
27
  export const VISUAL_REQUIREMENTS = {
15
- runbook: [
16
- { id: 'runbook-diagnostic-flowchart', check: 'artifact-has-mermaid', diagram: 'flowchart' },
17
- ],
18
- 'incident-report': [
19
- { id: 'incident-timeline-table', check: 'artifact-table-has-columns', columns: ['Time (UTC)', 'Event'] },
20
- ],
21
- rfc: [
22
- { id: 'rfc-sequence-diagram', check: 'artifact-has-mermaid', diagram: 'sequenceDiagram' },
23
- ],
28
+ ...visualRequirementsFromManifest(),
24
29
  };
25
30
 
26
31
  export function visualRequirementTypes() {
27
32
  return Object.keys(VISUAL_REQUIREMENTS);
28
33
  }
29
34
 
30
- // The structural floor of the quality rubric (ADR-0018): the sections a domain
31
- // expert expects for each doc type. Each shipped template must satisfy its own
32
- // entry — pinned by tests/structure-requirements.test.mjs — so a template can
33
- // never quietly drop a required section. Section requirements compose with the
34
- // VISUAL_REQUIREMENTS above; lintDocStructure runs both.
35
-
36
- export const STRUCTURE_REQUIREMENTS = {
37
- adr: ['Problem', 'Decision', 'Rejected alternatives', 'Consequences', 'Reversibility'],
38
- rfc: ['Summary', 'Motivation', 'Proposed design', 'Risks', 'Verification'],
39
- prd: ['Problem', 'Goals', 'Success metrics', 'Risks and mitigations'],
40
- 'research-brief': ['Sources', 'Findings', 'Confidence summary', 'Recommendation'],
41
- 'incident-report': ['Summary', 'Severity rationale', 'Impact', 'Timeline', 'Trigger', 'Root cause', 'Contributing factors', 'Action items'],
42
- runbook: ['Alert trigger', 'Symptoms', 'Impact', 'Severity and response', 'Diagnostic steps', 'Remediation', 'Rollback', 'Escalation'],
43
- strategy: ['Vision', 'Bets', 'Non-bets', 'North Star Metric', 'Metrics', 'Milestones', 'Risks'],
44
- 'signal-brief': ['Signal', 'Evidence', 'Counter-signal', 'What would make this actionable'],
45
- prfaq: ['Problem statement', 'Press release', 'External FAQ', 'Internal FAQ', 'Evidence appendix'],
46
- 'customer-profile': ['Snapshot', 'Active pain points', 'Open asks', 'Evidence links'],
47
- 'one-pager': ['Problem', 'Proposal', 'Why now', 'Success measure', 'Cost', 'Asks'],
48
- 'product-intelligence-report': ['Executive readout', 'Evidence base', 'Themes', 'Customer asks', 'Product implications', 'Recommended actions', 'Gaps and risks'],
49
- 'backlog-proposal': ['Source evidence', 'Proposed changes', 'Duplicate and conflict check', 'Approval request'],
50
- 'persona-artifact': ['Goals', 'Frustrations', 'Decision rights', 'Output contract', 'Failure modes', 'Evidence'],
51
- 'skill-artifact': ['What this skill produces', 'When to invoke it', 'Competency rubric', 'Failure modes', 'Worked example'],
52
- 'research-finding': ['SOURCES', 'FINDINGS', 'INFERENCES', 'CONFIDENCE', 'GAPS', 'RECOMMENDATION'],
53
- };
54
-
55
35
  export function structureRequirementTypes() {
56
36
  return [...new Set([...Object.keys(STRUCTURE_REQUIREMENTS), ...Object.keys(VISUAL_REQUIREMENTS)])];
57
37
  }
58
38
 
59
- /**
60
- * Lint one document against both the required sections and required visuals for
61
- * its type. Returns an array of violation strings (empty when satisfied or when
62
- * the type declares no requirements).
63
- */
39
+ export function resolveTemplatePath(type, rootDir) {
40
+ const entry = getArtifactEntry(type, { rootDir });
41
+ if (entry?.template) return entry.template;
42
+ return `templates/docs/${type}.md`;
43
+ }
44
+
64
45
  export function lintDocStructure(filePath, type) {
65
46
  const sectionChecks = (STRUCTURE_REQUIREMENTS[type] || []).map((section) => ({
66
47
  id: `${type}-section-${section}`,
@@ -72,11 +53,6 @@ export function lintDocStructure(filePath, type) {
72
53
  return validateArtifactPostconditions({ contract: { postconditions }, artifactPath: filePath });
73
54
  }
74
55
 
75
- /**
76
- * Lint one document against the visual requirements for its type. Returns an
77
- * array of violation strings (empty when satisfied, or when the type has no
78
- * declared visual requirements).
79
- */
80
56
  export function lintDocVisuals(filePath, type) {
81
57
  const postconditions = VISUAL_REQUIREMENTS[type];
82
58
  if (!postconditions) return [];
@@ -0,0 +1,313 @@
1
+ /**
2
+ * lib/test-corpus-inventory.mjs — classify the Construct test corpus for certification traceability.
3
+ *
4
+ * Walks all test files under tests/, assigns layer and signal category per file, extracts
5
+ * @capability markers, and compares shipped capability-matrix rows against the behavior
6
+ * ledger to surface release-critical gaps. Output feeds tests/capabilities/corpus-inventory.json.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { loadCapabilityLedger } from './capability-ledger.mjs';
12
+
13
+ export const CORPUS_CATEGORIES = Object.freeze([
14
+ 'structural-guard',
15
+ 'unit',
16
+ 'integration',
17
+ 'hook',
18
+ 'functional',
19
+ 'live-provider',
20
+ 'visual',
21
+ 'contract-subsystem',
22
+ ]);
23
+
24
+ const STRUCTURAL_GUARD_NAMES = [
25
+ 'agent-prompts',
26
+ 'release-workflow',
27
+ 'parity',
28
+ 'comment-lint',
29
+ 'agents-registry',
30
+ 'cli-catalog-accuracy',
31
+ 'auto-docs',
32
+ 'docs-verify',
33
+ 'sync-contract',
34
+ 'release-gates',
35
+ 'template-policy',
36
+ ];
37
+
38
+ const SUBSYSTEM_DIRS = [
39
+ 'profile',
40
+ 'profiles',
41
+ 'outcomes',
42
+ 'flavors',
43
+ 'knowledge',
44
+ 'intake',
45
+ 'roles',
46
+ 'embed',
47
+ 'doctor',
48
+ 'integrations',
49
+ 'graph',
50
+ 'evals',
51
+ 'reflect',
52
+ 'hooks',
53
+ 'functional',
54
+ 'capabilities',
55
+ 'fixtures',
56
+ 'e2e',
57
+ ];
58
+
59
+ const SHIPPED_MATRIX_CAPABILITIES = Object.freeze([
60
+ { id: 'document', ledgerHints: ['artifact', 'docs'] },
61
+ { id: 'research', ledgerHints: ['research', 'knowledge', 'ask'] },
62
+ { id: 'ingest', ledgerHints: ['document.ingest', 'ingest'] },
63
+ { id: 'diagram', ledgerHints: ['diagram', 'wireframe'] },
64
+ { id: 'demo', ledgerHints: ['demo'] },
65
+ { id: 'publish', ledgerHints: ['publish', 'artifact', 'export'] },
66
+ ]);
67
+
68
+ function findConstructRoot(startPath = process.cwd()) {
69
+ let current = path.resolve(startPath);
70
+ while (true) {
71
+ if (fs.existsSync(path.join(current, 'package.json')) && fs.existsSync(path.join(current, 'tests'))) return current;
72
+ const parent = path.dirname(current);
73
+ if (parent === current) return path.resolve(startPath);
74
+ current = parent;
75
+ }
76
+ }
77
+
78
+ export function defaultCorpusInventoryPath(rootDir = process.cwd()) {
79
+ return path.join(findConstructRoot(rootDir), 'tests', 'capabilities', 'corpus-inventory.json');
80
+ }
81
+
82
+ function listTestFiles(testsDir) {
83
+ const files = [];
84
+ const walk = (dir) => {
85
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
86
+ const absolute = path.join(dir, entry.name);
87
+ if (entry.isDirectory()) {
88
+ if (entry.name === 'node_modules' || entry.name === '.tmp') continue;
89
+ walk(absolute);
90
+ continue;
91
+ }
92
+ if (/\.test\.(mjs|js)$/.test(entry.name)) files.push(absolute);
93
+ }
94
+ };
95
+ walk(testsDir);
96
+ return files.sort();
97
+ }
98
+
99
+ function readHeaderNote(content) {
100
+ const block = content.match(/^\/\*\*([\s\S]*?)\*\//)?.[1] ?? content.match(/^\/\/\/([\s\S]*?)(?:\n\n|\nimport )/)?.[1];
101
+ if (!block) return '';
102
+ const line = block
103
+ .split('\n')
104
+ .map((row) => row.replace(/^\s*\*\s?/, '').trim())
105
+ .find((row) => row.length > 0 && !row.startsWith('@'));
106
+ return line?.slice(0, 160) ?? '';
107
+ }
108
+
109
+ function classifyLayer(relPath, content) {
110
+ const base = path.basename(relPath);
111
+ if (relPath.includes('tests/functional/') || base.includes('.functional.test.')) return 'functional';
112
+ if (base.includes('real-llm') || content.includes('CONSTRUCT_E2E_REAL_LLM')) return 'live-provider';
113
+ if (/visual/i.test(base) || /playwright/i.test(content)) return 'visual';
114
+ if (
115
+ /store/i.test(base)
116
+ || /postgres/i.test(base)
117
+ || /hybrid/i.test(base)
118
+ || /vector-client/i.test(base)
119
+ || relPath.includes('/hooks/')
120
+ || /hook/i.test(base)
121
+ ) return 'integration';
122
+ return 'unit';
123
+ }
124
+
125
+ function classifyCategory(relPath, layer, content) {
126
+ const base = path.basename(relPath);
127
+ const dir = path.dirname(relPath);
128
+ if (layer === 'functional') return 'functional';
129
+ if (layer === 'live-provider') return 'live-provider';
130
+ if (layer === 'visual') return 'visual';
131
+ if (dir.includes('/hooks/') || /\bhook/i.test(base)) return 'hook';
132
+ if (STRUCTURAL_GUARD_NAMES.some((name) => base.includes(name))) return 'structural-guard';
133
+ if (layer === 'integration') return 'integration';
134
+ if (SUBSYSTEM_DIRS.some((segment) => dir.split(path.sep).includes(segment))) return 'contract-subsystem';
135
+ if (/@capability\s+/.test(content) && layer === 'unit') return 'unit';
136
+ return 'unit';
137
+ }
138
+
139
+ function extractCapabilities(content) {
140
+ return [...content.matchAll(/@capability\s+([a-z][a-z0-9]*(?:[.-][a-z0-9]+)*)/g)].map((match) => match[1]);
141
+ }
142
+
143
+ function detectSkipped(content) {
144
+ return /\btest\.skip\s*\(|\bit\.skip\s*\(|\bdescribe\.skip\s*\(/.test(content);
145
+ }
146
+
147
+ export function classifyTestFile(relPath, content) {
148
+ const layer = classifyLayer(relPath, content);
149
+ return {
150
+ path: relPath.replace(/\\/g, '/'),
151
+ layer,
152
+ category: classifyCategory(relPath, layer, content),
153
+ capabilities: extractCapabilities(content),
154
+ skipped: detectSkipped(content),
155
+ maintainerNotes: readHeaderNote(content),
156
+ };
157
+ }
158
+
159
+ function countBy(items, key) {
160
+ const totals = {};
161
+ for (const item of items) totals[item[key]] = (totals[item[key]] ?? 0) + 1;
162
+ return totals;
163
+ }
164
+
165
+ function ledgerCoversHint(ledgerIds, hints) {
166
+ return hints.some((hint) => ledgerIds.some((id) => id.includes(hint)));
167
+ }
168
+
169
+ export function computeReleaseCriticalGaps(ledger) {
170
+ const allLedgerIds = (ledger?.capabilities ?? []).map((entry) => entry.id);
171
+ const gaps = [];
172
+
173
+ for (const row of SHIPPED_MATRIX_CAPABILITIES) {
174
+ if (ledgerCoversHint(allLedgerIds, row.ledgerHints)) continue;
175
+ gaps.push({
176
+ source: 'docs/audit/capability-matrix.md',
177
+ capability: row.id,
178
+ status: 'shipped',
179
+ reason: 'no ledger entry matches shipped capability hints',
180
+ suggestedLedgerHints: row.ledgerHints,
181
+ });
182
+ }
183
+
184
+ return gaps;
185
+ }
186
+
187
+ export function buildTestCorpusInventory({ rootDir } = {}) {
188
+ const root = findConstructRoot(rootDir);
189
+ const testsDir = path.join(root, 'tests');
190
+ const files = listTestFiles(testsDir);
191
+ const entries = files.map((absolute) => {
192
+ const rel = path.relative(root, absolute);
193
+ const content = fs.readFileSync(absolute, 'utf8');
194
+ return classifyTestFile(rel, content);
195
+ });
196
+
197
+ const { ledger } = loadCapabilityLedger({ rootDir: root });
198
+ const topLevel = entries.filter((entry) => entry.path.split('/').length === 2).length;
199
+ const nested = entries.length - topLevel;
200
+
201
+ return {
202
+ version: 1,
203
+ generatedAt: new Date().toISOString(),
204
+ generator: 'node scripts/generate-test-corpus-inventory.mjs',
205
+ summary: {
206
+ testFiles: entries.length,
207
+ topLevelTestFiles: topLevel,
208
+ nestedTestFiles: nested,
209
+ skippedFiles: entries.filter((entry) => entry.skipped).length,
210
+ capabilityMarkedFiles: entries.filter((entry) => entry.capabilities.length > 0).length,
211
+ byLayer: countBy(entries, 'layer'),
212
+ byCategory: countBy(entries, 'category'),
213
+ },
214
+ releaseCriticalGaps: computeReleaseCriticalGaps(ledger),
215
+ files: entries,
216
+ };
217
+ }
218
+
219
+ export function formatAuditAtAGlance(inventory) {
220
+ const { summary } = inventory;
221
+ const functional = summary.byCategory.functional ?? 0;
222
+ const subsystem = summary.byCategory['contract-subsystem'] ?? 0;
223
+ const hook = summary.byCategory.hook ?? 0;
224
+ return [
225
+ `- **${summary.testFiles} test files** total: ${summary.topLevelTestFiles} at \`tests/\` top level + ${summary.nestedTestFiles} in subdirectories.`,
226
+ `- **Layers:** ${Object.entries(summary.byLayer).map(([k, v]) => `${k} ${v}`).join(', ')}.`,
227
+ `- **Functional layer:** ${functional} file(s) under \`tests/functional/\` or \`*.functional.test.mjs\`. End-to-end checks spawn the real binary or import real modules in an isolated tmpdir.`,
228
+ `- **Contract subsystems:** ${subsystem} file(s) under profile/outcomes/hooks/knowledge/intake/graph/evals and related subdirs.`,
229
+ `- **Hook tests:** ${hook} file(s) (including \`tests/hooks/\`).`,
230
+ `- **Capability-marked:** ${summary.capabilityMarkedFiles} file(s) declare \`@capability\` markers.`,
231
+ `- **Skipped markers:** ${summary.skippedFiles} file(s) contain \`test.skip\` / \`describe.skip\` (see inventory for paths).`,
232
+ `- **Regenerate inventory:** \`node scripts/generate-test-corpus-inventory.mjs\`.`,
233
+ ].join('\n');
234
+ }
235
+
236
+ export function refreshAuditMarkdown(inventory, { rootDir } = {}) {
237
+ const root = findConstructRoot(rootDir);
238
+ const auditPath = path.join(root, 'tests', 'AUDIT.md');
239
+ const markdown = fs.readFileSync(auditPath, 'utf8');
240
+ const start = '## At a glance';
241
+ const end = '## Categories';
242
+ const startIdx = markdown.indexOf(start);
243
+ const endIdx = markdown.indexOf(end);
244
+ if (startIdx === -1 || endIdx === -1) throw new Error('tests/AUDIT.md missing At a glance section');
245
+ const replacement = `${start}\n\n${formatAuditAtAGlance(inventory)}\n\n`;
246
+ return `${markdown.slice(0, startIdx)}${replacement}${markdown.slice(endIdx)}`;
247
+ }
248
+
249
+ export function validateCorpusInventory({ rootDir, inventory: supplied } = {}) {
250
+ const root = findConstructRoot(rootDir);
251
+ const inventory = supplied ?? JSON.parse(fs.readFileSync(defaultCorpusInventoryPath(root), 'utf8'));
252
+ const errors = [];
253
+ const warnings = [];
254
+ const onDisk = listTestFiles(path.join(root, 'tests')).map((absolute) => path.relative(root, absolute).replace(/\\/g, '/'));
255
+ const indexed = new Set((inventory.files ?? []).map((entry) => entry.path));
256
+
257
+ if (inventory.version !== 1) errors.push('corpus-inventory.version must equal 1');
258
+ if (!Array.isArray(inventory.files) || inventory.files.length === 0) errors.push('corpus-inventory.files must be non-empty');
259
+
260
+ for (const rel of onDisk) {
261
+ if (!indexed.has(rel)) errors.push(`missing inventory entry: ${rel}`);
262
+ }
263
+ for (const entry of inventory.files ?? []) {
264
+ if (!onDisk.includes(entry.path)) errors.push(`stale inventory entry: ${entry.path}`);
265
+ if (!CORPUS_CATEGORIES.includes(entry.category)) errors.push(`${entry.path}: invalid category ${entry.category}`);
266
+ if (!entry.path.endsWith('.test.mjs') && !entry.path.endsWith('.test.js')) errors.push(`${entry.path}: not a test file path`);
267
+ }
268
+
269
+ if ((inventory.summary?.testFiles ?? 0) !== (inventory.files?.length ?? 0)) {
270
+ errors.push('summary.testFiles must equal files.length');
271
+ }
272
+
273
+ if (!Array.isArray(inventory.releaseCriticalGaps) || inventory.releaseCriticalGaps.length === 0) {
274
+ warnings.push('releaseCriticalGaps is empty');
275
+ }
276
+
277
+ return {
278
+ filePath: defaultCorpusInventoryPath(root),
279
+ fileCount: inventory.files?.length ?? 0,
280
+ errors,
281
+ warnings,
282
+ pass: errors.length === 0,
283
+ };
284
+ }
285
+
286
+ export function writeCorpusInventoryArtifacts({ rootDir } = {}) {
287
+ const root = findConstructRoot(rootDir);
288
+ const inventory = buildTestCorpusInventory({ rootDir: root });
289
+ const inventoryPath = defaultCorpusInventoryPath(root);
290
+ fs.mkdirSync(path.dirname(inventoryPath), { recursive: true });
291
+ fs.writeFileSync(inventoryPath, `${JSON.stringify(inventory, null, 2)}\n`);
292
+ fs.writeFileSync(path.join(root, 'tests', 'AUDIT.md'), refreshAuditMarkdown(inventory, { rootDir: root }));
293
+ return { inventoryPath, inventory };
294
+ }
295
+
296
+ export function runCorpusInventoryAuditCli(args = [], { rootDir } = {}) {
297
+ if (args.includes('--write')) {
298
+ const { inventoryPath, inventory } = writeCorpusInventoryArtifacts({ rootDir });
299
+ process.stdout.write(`Wrote ${inventoryPath} (${inventory.files.length} files)\n`);
300
+ process.stdout.write('Updated tests/AUDIT.md At a glance section\n');
301
+ return validateCorpusInventory({ rootDir, inventory });
302
+ }
303
+ const result = validateCorpusInventory({ rootDir });
304
+ if (args.includes('--json')) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
305
+ else {
306
+ process.stdout.write(`Corpus inventory: ${result.fileCount} files\n`);
307
+ if (result.errors.length) result.errors.forEach((error) => process.stderr.write(` ✗ ${error}\n`));
308
+ if (result.warnings.length) result.warnings.forEach((warning) => process.stderr.write(` ⚠ ${warning}\n`));
309
+ process.stdout.write(result.pass ? ' Result: PASS\n' : ' Result: FAIL\n');
310
+ }
311
+ if (!result.pass) process.exitCode = 1;
312
+ return result;
313
+ }
@@ -38,11 +38,10 @@ import { fileURLToPath } from 'node:url';
38
38
  import { multiSelect } from '../tty-prompts.mjs';
39
39
  import { getUserConfigDir, getUserEnvPath } from '../env-config.mjs';
40
40
  import { removeTomlTables, tomlString } from '../codex-config.mjs';
41
+ import { postgresContainerName, LEGACY_PG_CONTAINER } from '../home-namespace.mjs';
41
42
 
42
43
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
43
44
  const PKG_ROOT = path.resolve(__dirname, '..', '..');
44
-
45
- const POSTGRES_CONTAINER = 'construct-postgres';
46
45
  const PGVECTOR_IMAGE = 'pgvector/pgvector:pg16';
47
46
  const PRESSURE_GUARD_LABEL = 'dev.construct.pressure-release';
48
47
  const MEMORY_MCP_KEYS = ['memory', 'cass'];
@@ -75,6 +74,7 @@ export function parseArgs(argv) {
75
74
  }
76
75
 
77
76
  function buildCategories({ cwd, home, withImages = false }) {
77
+ const pgContainerName = postgresContainerName(process.env, home);
78
78
  const dotConstruct = path.join(cwd, '.construct');
79
79
  const dotClaudeAgents = path.join(cwd, '.claude', 'agents');
80
80
  const dotClaudeCommands = path.join(cwd, '.claude', 'commands');
@@ -213,10 +213,19 @@ function buildCategories({ cwd, home, withImages = false }) {
213
213
  id: 'machine-postgres-container',
214
214
  scope: 'machine',
215
215
  risk: 'ask',
216
- label: `Docker container "${POSTGRES_CONTAINER}" (stop + remove, including data)`,
217
- detect: () => dockerContainerExists(POSTGRES_CONTAINER),
218
- describe: () => 'Stops and removes the construct-postgres Docker container AND its data volume. The pgvector image stays cached. This destroys indexed observations — skip if you might restore.',
219
- execute: () => removeDockerContainer(POSTGRES_CONTAINER),
216
+ label: `Docker container "${pgContainerName}" (stop + remove, including data)`,
217
+ detect: () => dockerContainerExists(pgContainerName) || dockerContainerExists(LEGACY_PG_CONTAINER),
218
+ describe: () => 'Stops and removes the Postgres container and its named data volume. The pgvector image stays cached. This destroys indexed observations — skip if you might restore.',
219
+ execute: () => {
220
+ const results = [];
221
+ if (dockerContainerExists(pgContainerName)) {
222
+ results.push(removeDockerContainer(pgContainerName, `postgres_${pgContainerName}-data`));
223
+ }
224
+ if (dockerContainerExists(LEGACY_PG_CONTAINER)) {
225
+ results.push(removeDockerContainer(LEGACY_PG_CONTAINER));
226
+ }
227
+ return results.join(', ') || 'nothing to remove';
228
+ },
220
229
  },
221
230
  {
222
231
  id: 'machine-postgres-compose',
@@ -468,12 +477,15 @@ function dockerContainerExists(name) {
468
477
  return probe.stdout.trim() === name;
469
478
  }
470
479
 
471
- function removeDockerContainer(name) {
480
+ function removeDockerContainer(name, namedVolume = null) {
472
481
  const stop = spawnSync('docker', ['stop', name], { encoding: 'utf8' });
473
482
  const rm = spawnSync('docker', ['rm', '-v', name], { encoding: 'utf8' });
474
483
  if (rm.status !== 0) {
475
484
  throw new Error((rm.stderr || '').trim() || 'docker rm failed');
476
485
  }
486
+ if (namedVolume) {
487
+ spawnSync('docker', ['volume', 'rm', namedVolume], { encoding: 'utf8' });
488
+ }
477
489
  return stop.status === 0 ? 'stopped and removed (with volume)' : 'removed (with volume)';
478
490
  }
479
491
 
package/lib/update.mjs CHANGED
@@ -11,6 +11,7 @@ import path from 'node:path';
11
11
  import { spawnSync } from 'node:child_process';
12
12
 
13
13
  import { writeVersionStamp } from './maintenance/cleanup.mjs';
14
+ import { reclaimOrphanedDockerResources, formatReclaim } from './maintenance/docker-reclaim.mjs';
14
15
 
15
16
  const PACKAGE_NAME = '@geraldmaron/construct';
16
17
 
@@ -131,6 +132,17 @@ export function runUpdate({ cwd, env = process.env, spawn = spawnSync, stdout =
131
132
  runStep(step, { spawn, env, stdout });
132
133
  }
133
134
 
135
+ // Reclaim Postgres containers and volumes left behind by deleted home
136
+ // namespaces. An upgrade is the once-per-version moment to sweep machine
137
+ // state without taxing ordinary commands. The disable switch opts a machine
138
+ // out of every auto-maintenance pass, not this sweep alone.
139
+ if (env.CONSTRUCT_DISABLE_AUTO_CLEANUP !== '1') {
140
+ try {
141
+ const line = formatReclaim(reclaimOrphanedDockerResources({ env }));
142
+ if (line) stdout.write(`${line}\n`);
143
+ } catch { /* reclaim is best-effort */ }
144
+ }
145
+
134
146
  // Advance the cleanup stamp to the checkout version so the next CLI
135
147
  // invocation doesn't run a full cleanup pass on the assumption that
136
148
  // an upgrade just happened.
package/lib/upgrade.mjs CHANGED
@@ -8,9 +8,11 @@
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { spawnSync } from 'node:child_process';
11
+ import { sanitizeNpmSpawnEnv } from './npm-spawn-env.mjs';
11
12
  import { fileURLToPath } from 'node:url';
12
13
 
13
14
  import { writeVersionStamp } from './maintenance/cleanup.mjs';
15
+ import { reclaimOrphanedDockerResources, formatReclaim } from './maintenance/docker-reclaim.mjs';
14
16
 
15
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
18
  const ROOT_DIR = path.resolve(__dirname, '..');
@@ -31,6 +33,7 @@ function getLatestVersion() {
31
33
  const result = spawnSync('npm', ['view', PACKAGE_NAME, 'version'], {
32
34
  encoding: 'utf8',
33
35
  stdio: ['ignore', 'pipe', 'ignore'],
36
+ env: sanitizeNpmSpawnEnv(process.env),
34
37
  });
35
38
  if (result.status !== 0) return null;
36
39
  return result.stdout.trim();
@@ -154,6 +157,17 @@ export function runUpgrade({ cwd = process.cwd(), env = process.env, stdout = pr
154
157
  }
155
158
  }
156
159
 
160
+ // Reclaim Postgres containers and volumes left behind by deleted home
161
+ // namespaces. An upgrade is the once-per-version moment to sweep machine
162
+ // state without taxing ordinary commands. The disable switch opts a machine
163
+ // out of every auto-maintenance pass, not this sweep alone.
164
+ if (env.CONSTRUCT_DISABLE_AUTO_CLEANUP !== '1') {
165
+ try {
166
+ const line = formatReclaim(reclaimOrphanedDockerResources({ env }));
167
+ if (line) stdout.write(`${line}\n`);
168
+ } catch { /* reclaim is best-effort */ }
169
+ }
170
+
157
171
  // Advance the cleanup stamp so the next CLI invocation doesn't re-run a
158
172
  // full cleanup pass under the assumption that the user just upgraded.
159
173
  // Without this, every command pays the cleanup cost until something
package/lib/wireframe.mjs CHANGED
@@ -19,6 +19,7 @@
19
19
  */
20
20
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
21
21
  import { join, resolve } from 'node:path';
22
+ import { INK, FONTS } from './brand-tokens.mjs';
22
23
 
23
24
  const TYPES = ['flow', 'state', 'sequence', 'er', 'layout', 'user-journey'];
24
25
 
@@ -134,38 +135,43 @@ function mermaidUserJourney(description, keywords) {
134
135
 
135
136
  const SKETCH_CSS = `
136
137
  :root {
137
- --ink: #222;
138
- --paper: #fefdf8;
139
- --rough: #6b6b6b;
140
- --hint: #a9a9a9;
141
- --accent: #2c5282;
138
+ --ink: ${INK.inkBody};
139
+ --paper: ${INK.paper};
140
+ --rough: ${INK.muted};
141
+ --hint: ${INK.faint};
142
+ --accent: ${INK.ink};
143
+ --hairline: ${INK.hairline};
144
+ --surface: ${INK.surface};
142
145
  }
143
146
  * { box-sizing: border-box; }
144
147
  body {
145
148
  margin: 0; padding: 2rem;
146
- font-family: "Caveat", "Bradley Hand", "Comic Sans MS", cursive;
149
+ font-family: ${FONTS.sans};
147
150
  background: var(--paper);
148
151
  color: var(--ink);
149
- line-height: 1.4;
152
+ line-height: 1.45;
153
+ font-size: 15px;
150
154
  }
151
155
  .wireframe { max-width: 1200px; margin: 0 auto; }
152
156
  .wireframe header, .wireframe nav, .wireframe main,
153
157
  .wireframe aside, .wireframe section, .wireframe footer,
154
158
  .block, .card {
155
- border: 2px solid var(--ink);
156
- border-radius: 6px;
159
+ border: 1px solid var(--hairline);
160
+ border-radius: 8px;
157
161
  padding: 1rem;
158
162
  margin: 0.5rem 0;
159
163
  position: relative;
160
- background: var(--paper);
161
- box-shadow: 3px 3px 0 var(--hint);
164
+ background: var(--surface);
165
+ box-shadow: none;
162
166
  }
163
- .wireframe header { background: #f3f0e7; }
167
+ .wireframe header { background: var(--surface); }
164
168
  .label {
165
169
  position: absolute; top: -10px; left: 10px;
166
170
  background: var(--paper); padding: 0 6px;
167
- font-size: 0.85em; color: var(--rough);
168
- font-family: ui-monospace, Menlo, monospace;
171
+ font-size: 0.75rem; color: var(--rough);
172
+ font-family: ${FONTS.mono};
173
+ letter-spacing: 0.04em;
174
+ text-transform: uppercase;
169
175
  }
170
176
  .row { display: grid; grid-template-columns: repeat(var(--cols, 3), 1fr); gap: 1rem; }
171
177
  .row-2 { --cols: 2; }
@@ -27,6 +27,7 @@ import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
27
27
  import path from 'node:path';
28
28
 
29
29
  import { emitTraceEvent, newTraceId, newSpanId } from './trace.mjs';
30
+ import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
30
31
 
31
32
  const DEFAULT_TIMEOUT_SECONDS = 300;
32
33
  const ARTIFACTS_SUBDIR = '.cx/runtime/worker';
@@ -120,8 +121,17 @@ export async function runJob({
120
121
  });
121
122
  clearTimeout(timer);
122
123
 
123
- writeFileSync(stdoutPath, stdout, 'utf8');
124
- writeFileSync(stderrPath, stderr, 'utf8');
124
+ const outBytes = Buffer.byteLength(stdout, 'utf8');
125
+ const errBytes = Buffer.byteLength(stderr, 'utf8');
126
+ const gate = ensureDiskWrite(rootDir, 'worker-logs', outBytes + errBytes, process.env);
127
+ let budgetSkipped = false;
128
+ if (gate.ok) {
129
+ writeFileSync(stdoutPath, stdout, 'utf8');
130
+ writeFileSync(stderrPath, stderr, 'utf8');
131
+ } else {
132
+ budgetSkipped = true;
133
+ warnBudgetOnce(gate.message || 'worker log write blocked by .cx/ disk budget', process.env);
134
+ }
125
135
 
126
136
  const durationMs = Date.now() - started;
127
137
  const status = timedOut ? 'timeout' : exitCode === 0 ? 'passed' : 'failed';
@@ -135,7 +145,10 @@ export async function runJob({
135
145
  stdoutPath,
136
146
  stderrPath,
137
147
  durationMs,
138
- artifacts: [{ path: stdoutPath, kind: 'stdout' }, { path: stderrPath, kind: 'stderr' }],
148
+ artifacts: budgetSkipped
149
+ ? []
150
+ : [{ path: stdoutPath, kind: 'stdout' }, { path: stderrPath, kind: 'stderr' }],
151
+ budgetSkipped,
139
152
  traceId,
140
153
  spanId,
141
154
  };
@@ -147,7 +160,7 @@ export async function runJob({
147
160
  spanId,
148
161
  project: job.project,
149
162
  taskId: job.taskId,
150
- metadata: { jobId: job.jobId, status, exitCode, durationMs, stdoutPath, stderrPath, timedOut },
163
+ metadata: { jobId: job.jobId, status, exitCode, durationMs, stdoutPath, stderrPath, timedOut, budgetSkipped },
151
164
  });
152
165
 
153
166
  return result;