@geraldmaron/construct 1.1.1 → 1.2.1

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 (426) hide show
  1. package/README.md +30 -19
  2. package/bin/construct +487 -87
  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 +258 -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/artifact-workflow.mjs +252 -0
  20. package/lib/audit-skills.mjs +4 -0
  21. package/lib/audit-specialists.mjs +285 -0
  22. package/lib/audit-trail.mjs +1 -1
  23. package/lib/auto-docs.mjs +90 -19
  24. package/lib/beads/drift.mjs +27 -6
  25. package/lib/beads-client.mjs +49 -121
  26. package/lib/beads-optimistic.mjs +5 -12
  27. package/lib/brand-fonts.mjs +93 -0
  28. package/lib/brand-prose.mjs +214 -0
  29. package/lib/brand-tokens.mjs +92 -0
  30. package/lib/bridges/copilot-proxy.mjs +13 -26
  31. package/lib/capability-ledger.mjs +156 -0
  32. package/lib/certification/artifact-fixtures.mjs +132 -0
  33. package/lib/certification/artifact-gates.mjs +63 -0
  34. package/lib/certification/artifact-provenance.mjs +97 -0
  35. package/lib/certification/canonical-scenarios.mjs +78 -0
  36. package/lib/certification/cli.mjs +191 -0
  37. package/lib/certification/dashboard-api.mjs +71 -0
  38. package/lib/certification/demo-parity.mjs +116 -0
  39. package/lib/certification/document-io-fixtures.mjs +246 -0
  40. package/lib/certification/document-workflow.mjs +97 -0
  41. package/lib/certification/eval-bridge.mjs +77 -0
  42. package/lib/certification/model-routing.mjs +149 -0
  43. package/lib/certification/prompt-budget.mjs +119 -0
  44. package/lib/certification/rc-gate.mjs +206 -0
  45. package/lib/certification/real-llm-scenarios.mjs +303 -0
  46. package/lib/certification/role-cards.mjs +113 -0
  47. package/lib/certification/role-overlays.mjs +117 -0
  48. package/lib/certification/run.mjs +122 -0
  49. package/lib/certification/runner.mjs +323 -0
  50. package/lib/certification/scenarios.mjs +51 -0
  51. package/lib/certification/skill-inventory.mjs +289 -0
  52. package/lib/certification/skill-scenarios.mjs +147 -0
  53. package/lib/certification/specialist-contracts.mjs +85 -0
  54. package/lib/certification/specialist-scenarios.mjs +175 -0
  55. package/lib/certification/stale-impact.mjs +146 -0
  56. package/lib/certification/status.mjs +252 -0
  57. package/lib/certification/store.mjs +77 -0
  58. package/lib/chat/cli.mjs +333 -0
  59. package/lib/chat/command-suggest.mjs +161 -0
  60. package/lib/chat/commands.mjs +215 -0
  61. package/lib/chat/config.mjs +142 -0
  62. package/lib/chat/context-compactor.mjs +250 -0
  63. package/lib/chat/context-continuation.mjs +253 -0
  64. package/lib/chat/continuation-source.mjs +58 -0
  65. package/lib/chat/demo-guide.mjs +61 -0
  66. package/lib/chat/design-tokens.mjs +91 -0
  67. package/lib/chat/desktop-binary.mjs +79 -0
  68. package/lib/chat/desktop-build.mjs +130 -0
  69. package/lib/chat/desktop-launcher.mjs +133 -0
  70. package/lib/chat/evidence.mjs +145 -0
  71. package/lib/chat/export.mjs +74 -0
  72. package/lib/chat/harness/driver.mjs +91 -0
  73. package/lib/chat/list-picker.mjs +112 -0
  74. package/lib/chat/model-picker.mjs +356 -0
  75. package/lib/chat/openrouter-fallback.mjs +151 -0
  76. package/lib/chat/permission-prompt.mjs +33 -0
  77. package/lib/chat/picker-catalog.mjs +45 -0
  78. package/lib/chat/policy-telemetry.mjs +34 -0
  79. package/lib/chat/present.mjs +246 -0
  80. package/lib/chat/session-context.mjs +39 -0
  81. package/lib/chat/session-persist.mjs +73 -0
  82. package/lib/chat/session-restore.mjs +71 -0
  83. package/lib/chat/session-settings.mjs +53 -0
  84. package/lib/chat/system-prompt.mjs +52 -0
  85. package/lib/chat/transparency.mjs +93 -0
  86. package/lib/chat/tui/color-scheme.mjs +42 -0
  87. package/lib/chat/tui/markdown.mjs +123 -0
  88. package/lib/chat/tui/presentation.mjs +100 -0
  89. package/lib/chat/tui/render.mjs +500 -0
  90. package/lib/chat/tui/turn-block.mjs +284 -0
  91. package/lib/chat/tui/turn-present.mjs +18 -0
  92. package/lib/chat/tui/turn-state.mjs +88 -0
  93. package/lib/chat/tui/usage.mjs +122 -0
  94. package/lib/chat/web-commands.mjs +146 -0
  95. package/lib/chat/web-launcher.mjs +63 -0
  96. package/lib/chat/web-picker-keys.mjs +46 -0
  97. package/lib/chat/web-session.mjs +159 -0
  98. package/lib/cli-commands.mjs +146 -15
  99. package/lib/cli-service-inventory.mjs +42 -0
  100. package/lib/comment-lint.mjs +6 -4
  101. package/lib/config/intake-policy.mjs +209 -0
  102. package/lib/config/project-config.mjs +11 -2
  103. package/lib/config/schema.mjs +86 -6
  104. package/lib/config/source-targets.mjs +311 -0
  105. package/lib/contract-schemas/decision.json +50 -0
  106. package/lib/contract-schemas/implementation.json +51 -0
  107. package/lib/contract-schemas/review-report.json +32 -0
  108. package/lib/contract-schemas/test-report.json +43 -0
  109. package/lib/contracts/construct-handoff.mjs +60 -0
  110. package/lib/contracts/validate.mjs +32 -3
  111. package/lib/contracts/violation-log.mjs +58 -5
  112. package/lib/dashboard-demo.mjs +71 -0
  113. package/lib/dashboard-static.mjs +19 -5
  114. package/lib/decisions/registry.mjs +5 -3
  115. package/lib/deck-export-pptx.mjs +1152 -0
  116. package/lib/demo-recording.mjs +142 -0
  117. package/lib/demo-script.mjs +114 -0
  118. package/lib/demo-surface.mjs +249 -0
  119. package/lib/demo.mjs +598 -140
  120. package/lib/diagram-export.mjs +192 -0
  121. package/lib/diagram.mjs +13 -11
  122. package/lib/docs-verify.mjs +25 -7
  123. package/lib/doctor/index.mjs +3 -1
  124. package/lib/doctor/project-adapters.mjs +44 -0
  125. package/lib/doctor/source-checkout.mjs +16 -0
  126. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  127. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  128. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  129. package/lib/document-export.mjs +384 -38
  130. package/lib/document-extract.mjs +44 -4
  131. package/lib/document-ingest.mjs +29 -20
  132. package/lib/embed/auto-sources.mjs +44 -0
  133. package/lib/embed/cli.mjs +4 -3
  134. package/lib/embed/daemon.mjs +18 -46
  135. package/lib/embed/demand-fetch.mjs +50 -19
  136. package/lib/embed/inbox.mjs +6 -10
  137. package/lib/embed/worker.mjs +4 -3
  138. package/lib/embedded-contract/index.mjs +11 -0
  139. package/lib/embedded-contract/model-resolve.mjs +10 -9
  140. package/lib/env-config.mjs +26 -0
  141. package/lib/evals/dataset.mjs +137 -0
  142. package/lib/evals/gates.mjs +175 -0
  143. package/lib/export-branding.mjs +32 -0
  144. package/lib/graph/build-co-change.mjs +54 -0
  145. package/lib/graph/build-from-registry.mjs +136 -0
  146. package/lib/graph/build-import-graph.mjs +153 -0
  147. package/lib/graph/cli.mjs +110 -0
  148. package/lib/graph/impact-cli.mjs +89 -0
  149. package/lib/graph/impact.mjs +108 -0
  150. package/lib/graph/staleness.mjs +44 -0
  151. package/lib/graph/store.mjs +172 -0
  152. package/lib/health-check.mjs +90 -76
  153. package/lib/hooks/artifact-release-gate.mjs +43 -0
  154. package/lib/hooks/brand-prose-lint.mjs +38 -0
  155. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  156. package/lib/hooks/session-optimize.mjs +84 -207
  157. package/lib/hooks/session-start.mjs +18 -16
  158. package/lib/host-disposition.mjs +7 -0
  159. package/lib/improvement/cli.mjs +189 -0
  160. package/lib/improvement/controller.mjs +137 -0
  161. package/lib/improvement/proposal.mjs +120 -0
  162. package/lib/improvement/specialist-loop.mjs +192 -0
  163. package/lib/improvement/store.mjs +89 -0
  164. package/lib/improvement/surface.mjs +219 -0
  165. package/lib/ingest-tooling.mjs +97 -0
  166. package/lib/init/doc-lanes.mjs +165 -0
  167. package/lib/init-docs.mjs +31 -178
  168. package/lib/init-unified.mjs +50 -119
  169. package/lib/init-update-guide.mjs +102 -0
  170. package/lib/init-update.mjs +32 -1
  171. package/lib/init.mjs +8 -0
  172. package/lib/install/desktop-binary-download.mjs +85 -0
  173. package/lib/install/legacy-global-cleanup.mjs +189 -0
  174. package/lib/intake/constants.mjs +29 -0
  175. package/lib/intake/daemon.mjs +2 -0
  176. package/lib/intake/git-queue.mjs +9 -8
  177. package/lib/intake/intake-config.mjs +52 -105
  178. package/lib/intake/legacy-paths.mjs +5 -0
  179. package/lib/intake/queue.mjs +2 -1
  180. package/lib/intake/session-prelude.mjs +90 -1
  181. package/lib/libreoffice-export.mjs +97 -0
  182. package/lib/logging/rotate.mjs +9 -0
  183. package/lib/maintenance/docker-reclaim.mjs +206 -0
  184. package/lib/mcp/external-schema-cost.mjs +74 -0
  185. package/lib/mcp/server.mjs +103 -10
  186. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  187. package/lib/mcp/tool-budget.mjs +55 -4
  188. package/lib/mcp/tools/document.mjs +18 -4
  189. package/lib/mcp/tools/embedded-contract.mjs +14 -0
  190. package/lib/mcp/tools/skills.mjs +32 -3
  191. package/lib/mcp/tools/workflow.mjs +13 -1
  192. package/lib/model-free-selector.mjs +18 -0
  193. package/lib/model-registry.mjs +13 -229
  194. package/lib/model-router.mjs +373 -92
  195. package/lib/models/behavior-matrix.mjs +289 -0
  196. package/lib/models/catalog.mjs +209 -0
  197. package/lib/models/execution-capability-profile.mjs +196 -0
  198. package/lib/models/execution-policy.mjs +307 -0
  199. package/lib/models/provider-poll.mjs +383 -0
  200. package/lib/npm-spawn-env.mjs +17 -0
  201. package/lib/ollama/installed-models.mjs +129 -0
  202. package/lib/oracle/actions.mjs +187 -0
  203. package/lib/oracle/artifact-gate.mjs +99 -0
  204. package/lib/oracle/cli.mjs +204 -0
  205. package/lib/oracle/daemon-entry.mjs +14 -0
  206. package/lib/oracle/dispatch.mjs +81 -0
  207. package/lib/oracle/execute.mjs +143 -0
  208. package/lib/oracle/gaps.mjs +76 -0
  209. package/lib/oracle/index.mjs +84 -0
  210. package/lib/oracle/issues.mjs +164 -0
  211. package/lib/oracle/org-graph.mjs +170 -0
  212. package/lib/oracle/policy.mjs +80 -0
  213. package/lib/oracle/read-model.mjs +398 -0
  214. package/lib/oracle/reconcile.mjs +191 -0
  215. package/lib/oracle/routing.mjs +89 -0
  216. package/lib/oracle/synthesize.mjs +384 -0
  217. package/lib/oracle/verdicts.mjs +51 -0
  218. package/lib/orchestration/worker.mjs +88 -27
  219. package/lib/orchestration-policy.mjs +50 -0
  220. package/lib/parity.mjs +96 -2
  221. package/lib/playwright-demo.mjs +254 -0
  222. package/lib/project-init-shared.mjs +4 -1
  223. package/lib/prompt-composer.js +7 -3
  224. package/lib/prompt-validation-contract.mjs +24 -0
  225. package/lib/provider-capabilities.js +57 -11
  226. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  227. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  228. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  229. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  230. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  231. package/lib/providers/contract/contract-tests.mjs +57 -0
  232. package/lib/providers/contract/errors.mjs +48 -0
  233. package/lib/providers/contract/interface.mjs +50 -0
  234. package/lib/providers/contract/registry.mjs +102 -0
  235. package/lib/providers/copilot-auth.mjs +297 -0
  236. package/lib/providers/credential-bootstrap.mjs +178 -0
  237. package/lib/providers/credential-catalog.mjs +46 -0
  238. package/lib/providers/credential-sources.mjs +63 -0
  239. package/lib/providers/creds.mjs +5 -2
  240. package/lib/providers/op-run.mjs +59 -0
  241. package/lib/providers/secret-resolver.mjs +159 -0
  242. package/lib/publish-template.mjs +163 -0
  243. package/lib/publish-tooling.mjs +119 -0
  244. package/lib/publish.mjs +305 -0
  245. package/lib/registry/cli.mjs +82 -0
  246. package/lib/registry/consolidation.mjs +147 -0
  247. package/lib/registry/generate-docs.mjs +75 -0
  248. package/lib/registry/skill-verification.mjs +57 -0
  249. package/lib/registry/surface-map.mjs +76 -0
  250. package/lib/registry/validate.mjs +135 -0
  251. package/lib/resources/budget.mjs +82 -0
  252. package/lib/resources/process-budget.mjs +45 -0
  253. package/lib/rules-delivery.mjs +9 -2
  254. package/lib/rules-read.mjs +26 -0
  255. package/lib/runtime-env.mjs +23 -0
  256. package/lib/runtime-pressure.mjs +51 -7
  257. package/lib/schema-infer.mjs +13 -2
  258. package/lib/server/chat-loop.mjs +622 -0
  259. package/lib/server/demo-preview.mjs +63 -0
  260. package/lib/server/index.mjs +259 -9
  261. package/lib/server/rate-limit.mjs +5 -3
  262. package/lib/server/webhook.mjs +3 -2
  263. package/lib/service-manager.mjs +60 -8
  264. package/lib/setup.mjs +70 -7
  265. package/lib/specialists/prompt-schema.mjs +19 -10
  266. package/lib/specialists/roster.mjs +43 -0
  267. package/lib/specialists/scaffold.mjs +56 -0
  268. package/lib/storage/backend.mjs +6 -6
  269. package/lib/storage/hybrid-query.mjs +7 -4
  270. package/lib/storage/state-source.mjs +5 -5
  271. package/lib/storage/sync.mjs +2 -3
  272. package/lib/telemetry/rule-calls.mjs +52 -0
  273. package/lib/template-registry.mjs +2 -2
  274. package/lib/templates/visual-requirements.mjs +27 -51
  275. package/lib/test-corpus-inventory.mjs +313 -0
  276. package/lib/uninstall/uninstall.mjs +19 -7
  277. package/lib/update.mjs +12 -0
  278. package/lib/upgrade.mjs +14 -0
  279. package/lib/wireframe.mjs +20 -14
  280. package/lib/worker/run.mjs +17 -4
  281. package/lib/worker/trace.mjs +11 -3
  282. package/package.json +28 -14
  283. package/personas/construct.md +2 -2
  284. package/platforms/claude/settings.template.json +36 -0
  285. package/rules/common/patterns.md +1 -1
  286. package/rules/common/release-gates.md +4 -3
  287. package/scripts/sync-specialists.mjs +41 -28
  288. package/skills/devops/data-engineering.md +1 -1
  289. package/skills/docs/adr-workflow.md +1 -0
  290. package/skills/docs/backlog-proposal-workflow.md +1 -0
  291. package/skills/docs/codebase-research-workflow.md +40 -0
  292. package/skills/docs/customer-profile-workflow.md +1 -0
  293. package/skills/docs/document-ingest-workflow.md +1 -0
  294. package/skills/docs/evidence-ingest-workflow.md +1 -0
  295. package/skills/docs/init-docs.md +2 -2
  296. package/skills/docs/init-project.md +7 -2
  297. package/skills/docs/prd-workflow.md +24 -1
  298. package/skills/docs/prfaq-workflow.md +1 -0
  299. package/skills/docs/product-intelligence-workflow.md +1 -0
  300. package/skills/docs/product-signal-workflow.md +1 -0
  301. package/skills/docs/research-workflow.md +54 -37
  302. package/skills/docs/runbook-workflow.md +1 -0
  303. package/skills/docs/strategy-workflow.md +1 -0
  304. package/skills/docs/user-research-workflow.md +40 -0
  305. package/skills/operating/orchestration-reference.md +1 -1
  306. package/skills/roles/architect.md +5 -0
  307. package/skills/roles/operator.docs.md +4 -0
  308. package/skills/routing.md +4 -2
  309. package/specialists/artifact-manifest.json +489 -0
  310. package/specialists/artifact-manifest.schema.json +83 -0
  311. package/specialists/audit-enrichments.json +454 -0
  312. package/specialists/contracts.json +37 -25
  313. package/specialists/prompts/_shared/validation-contract.md +26 -0
  314. package/specialists/prompts/cx-accessibility.md +21 -2
  315. package/specialists/prompts/cx-ai-engineer.md +24 -1
  316. package/specialists/prompts/cx-architect.md +4 -1
  317. package/specialists/prompts/cx-business-strategist.md +23 -2
  318. package/specialists/prompts/cx-data-analyst.md +22 -1
  319. package/specialists/prompts/cx-data-engineer.md +23 -2
  320. package/specialists/prompts/cx-debugger.md +21 -2
  321. package/specialists/prompts/cx-designer.md +26 -3
  322. package/specialists/prompts/cx-devil-advocate.md +19 -2
  323. package/specialists/prompts/cx-docs-keeper.md +28 -1
  324. package/specialists/prompts/cx-engineer.md +22 -1
  325. package/specialists/prompts/cx-evaluator.md +18 -1
  326. package/specialists/prompts/cx-explorer.md +21 -2
  327. package/specialists/prompts/cx-legal-compliance.md +21 -2
  328. package/specialists/prompts/cx-operations.md +21 -2
  329. package/specialists/prompts/cx-oracle.md +94 -0
  330. package/specialists/prompts/cx-orchestrator.md +20 -1
  331. package/specialists/prompts/cx-platform-engineer.md +25 -2
  332. package/specialists/prompts/cx-product-manager.md +32 -1
  333. package/specialists/prompts/cx-qa.md +24 -2
  334. package/specialists/prompts/cx-rd-lead.md +23 -2
  335. package/specialists/prompts/cx-release-manager.md +23 -2
  336. package/specialists/prompts/cx-researcher.md +22 -2
  337. package/specialists/prompts/cx-reviewer.md +18 -1
  338. package/specialists/prompts/cx-security.md +22 -2
  339. package/specialists/prompts/cx-sre.md +30 -3
  340. package/specialists/prompts/cx-test-automation.md +7 -1
  341. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  342. package/specialists/prompts/cx-ux-researcher.md +21 -2
  343. package/specialists/registry.json +52 -173
  344. package/specialists/tone-profiles.json +42 -0
  345. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  346. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  347. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  348. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  349. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  350. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  351. package/templates/demos/vhs/construct-cockpit.json +24 -0
  352. package/templates/distribution/construct-brand.typ +446 -0
  353. package/templates/distribution/construct-decision.typ +38 -0
  354. package/templates/distribution/construct-deck.html +95 -0
  355. package/templates/distribution/construct-pdf.typ +38 -0
  356. package/templates/distribution/construct-prd.typ +38 -0
  357. package/templates/distribution/construct-reference.docx +0 -0
  358. package/templates/distribution/construct-research.typ +38 -0
  359. package/templates/distribution/construct-web.html +92 -0
  360. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  361. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  362. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  363. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  364. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  365. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  366. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  367. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  368. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  369. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  370. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  371. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  372. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  373. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  374. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  375. package/templates/distribution/fonts/README.md +36 -0
  376. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  377. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  378. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  379. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  380. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  381. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  382. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  383. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  384. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  385. package/templates/distribution/icons/activity.svg +15 -0
  386. package/templates/distribution/icons/alert-triangle.svg +17 -0
  387. package/templates/distribution/icons/book-open.svg +16 -0
  388. package/templates/distribution/icons/bot.svg +20 -0
  389. package/templates/distribution/icons/brain.svg +22 -0
  390. package/templates/distribution/icons/circle-check.svg +16 -0
  391. package/templates/distribution/icons/clipboard-check.svg +17 -0
  392. package/templates/distribution/icons/cpu.svg +28 -0
  393. package/templates/distribution/icons/database.svg +17 -0
  394. package/templates/distribution/icons/eye.svg +16 -0
  395. package/templates/distribution/icons/file-text.svg +19 -0
  396. package/templates/distribution/icons/gauge.svg +16 -0
  397. package/templates/distribution/icons/git-branch.svg +17 -0
  398. package/templates/distribution/icons/key.svg +17 -0
  399. package/templates/distribution/icons/layers.svg +17 -0
  400. package/templates/distribution/icons/list-checks.svg +19 -0
  401. package/templates/distribution/icons/lock.svg +16 -0
  402. package/templates/distribution/icons/message-square.svg +15 -0
  403. package/templates/distribution/icons/network.svg +19 -0
  404. package/templates/distribution/icons/route.svg +17 -0
  405. package/templates/distribution/icons/scale.svg +19 -0
  406. package/templates/distribution/icons/search.svg +16 -0
  407. package/templates/distribution/icons/send.svg +16 -0
  408. package/templates/distribution/icons/server.svg +18 -0
  409. package/templates/distribution/icons/shield-check.svg +16 -0
  410. package/templates/distribution/icons/users.svg +18 -0
  411. package/templates/distribution/icons/webhook.svg +17 -0
  412. package/templates/distribution/icons/workflow.svg +17 -0
  413. package/templates/distribution/icons/wrench.svg +15 -0
  414. package/templates/distribution/run.mjs +18 -1
  415. package/templates/docs/adr.md +7 -0
  416. package/templates/docs/construct_guide.md +8 -8
  417. package/templates/docs/customer-profile.md +4 -0
  418. package/templates/docs/one-pager.md +4 -0
  419. package/templates/docs/postmortem.md +31 -0
  420. package/templates/docs/prd-platform.md +19 -0
  421. package/templates/docs/prd.md +15 -0
  422. package/templates/docs/prfaq.md +7 -0
  423. package/templates/docs/qa-strategy.md +103 -0
  424. package/templates/docs/research-brief.md +8 -0
  425. package/templates/docs/rfc-platform.md +12 -0
  426. package/templates/docs/test-plan.md +7 -0
@@ -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;
@@ -33,6 +33,7 @@ import path from 'node:path';
33
33
  import { createTelemetryClient } from '../telemetry/client.mjs';
34
34
  import { ensureCxDir } from '../project-init-shared.mjs';
35
35
  import { appendWithRotationSync } from '../logging/rotate.mjs';
36
+ import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
36
37
 
37
38
  const TRACE_SUBDIR = '.cx/traces';
38
39
 
@@ -183,9 +184,16 @@ export function emitTraceEvent({
183
184
  createdAt: new Date().toISOString(),
184
185
  };
185
186
  const shardPath = path.join(dir, `${todayShard()}.jsonl`);
186
- appendWithRotationSync(shardPath, `${JSON.stringify(event)}\n`, {
187
- maxBytes: traceMaxBytes(env),
188
- });
187
+ const line = `${JSON.stringify(event)}\n`;
188
+ const gate = ensureDiskWrite(rootDir, 'traces', Buffer.byteLength(line), env);
189
+ if (gate.ok) {
190
+ appendWithRotationSync(shardPath, line, {
191
+ maxBytes: traceMaxBytes(env),
192
+ });
193
+ } else {
194
+ warnBudgetOnce(gate.message || 'trace write blocked by .cx/ disk budget', env);
195
+ return { ...event, budgetSkipped: true, budgetReason: gate.reason || 'budget-exceeded' };
196
+ }
189
197
 
190
198
  // Fire-and-forget export to the configured remote adapter. Silent no-op when not configured.
191
199
  exportToRemote(event, env, rootDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geraldmaron/construct",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "packageManager": "npm@11.5.1",
6
6
  "description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
@@ -26,7 +26,6 @@
26
26
  "platforms/**",
27
27
  "templates/**",
28
28
  "templates/distribution/**",
29
- "db/**",
30
29
  "scripts/sync-specialists.mjs",
31
30
  "README.md",
32
31
  ".env.example"
@@ -57,20 +56,28 @@
57
56
  "test:unit": "node scripts/run-tests.mjs --exclude=tests/functional",
58
57
  "lint:js": "eslint bin/construct \"lib/**/*.mjs\" \"scripts/**/*.mjs\" \"tests/**/*.mjs\"",
59
58
  "coverage": "c8 --reporter=text-summary --reporter=lcov --src=lib --src=bin node scripts/run-tests.mjs --exclude=tests/functional",
60
- "docs:init": "node lib/init-docs.mjs --yes",
59
+ "docs:init": "node -e \"console.error('docs:init is deprecated — use: construct init --docs-preset=lean|product|full'); process.exit(1)\"",
61
60
  "docs:update": "node ./bin/construct docs:update",
62
61
  "docs:site": "node ./bin/construct docs:site",
63
- "docs:dev": "npm --prefix apps/docs run dev",
64
- "docs:build": "npm --prefix apps/docs run build",
62
+ "docs:dev": "node scripts/npm-run.mjs npm --prefix apps/docs run dev",
63
+ "docs:build": "node scripts/npm-run.mjs npm --prefix apps/docs run build",
65
64
  "lint:comments": "node ./bin/construct lint:comments",
66
65
  "lint:contracts": "node ./bin/construct lint:contracts",
67
66
  "lint:agents": "node ./bin/construct lint:agents",
68
67
  "build:sea": "esbuild bin/construct --bundle --platform=node --target=node20 --packages=external --outfile=dist/construct-bundle.cjs 2>&1 || (echo 'esbuild not found — install with: npm install -g esbuild' && exit 1)",
68
+ "build:chat": "esbuild apps/chat/tui/index.jsx --bundle --platform=node --format=esm --packages=external --jsx=automatic --jsx-import-source=react --outfile=apps/chat/dist/tui.mjs",
69
+ "build:chat-desktop": "node scripts/build-chat-desktop.mjs",
70
+ "build:chat-desktop:bundle": "node scripts/npm-run.mjs npm install --prefix apps/chat/desktop --include=dev && node scripts/npm-run.mjs npm --prefix apps/chat/desktop run build",
71
+ "build:dashboard": "node scripts/npm-run.mjs npm install --prefix apps/dashboard && node scripts/npm-run.mjs npm --prefix apps/dashboard run build",
72
+ "examples:deck": "node scripts/generate-deck-examples.mjs",
73
+ "examples:distribution": "node scripts/generate-distribution-examples.mjs",
74
+ "clean:artifacts": "node scripts/clean-artifacts.mjs",
69
75
  "lint:profiles": "node scripts/lint-profiles.mjs",
70
76
  "learning:status": "node scripts/learning-status.mjs",
71
77
  "lint:templates": "node scripts/lint-commits-pr.mjs",
72
78
  "eval:routing": "node scripts/eval/score-intent-classifier.mjs",
73
- "release:check": "node ./bin/construct doctor && npm test && node ./bin/construct docs:update --check && node ./bin/construct dashboard:sync --check && node ./bin/construct lint:comments && node scripts/lint-commits-pr.mjs",
79
+ "adapters": "node lib/adapters-sync.mjs",
80
+ "release:check": "node ./bin/construct doctor && npm test && node ./bin/construct docs:update --check && node ./bin/construct docs:site --check && node ./bin/construct registry:generate-docs --check && node ./bin/construct docs:verify --strict && node scripts/alignment/census.mjs --ratchet && node ./bin/construct dashboard:sync --check && node ./bin/construct lint:comments && node ./bin/construct certify gate && node scripts/lint-commits-pr.mjs",
74
81
  "release:gate": "node --test tests/functional/release-gate.functional.test.mjs",
75
82
  "audit:published": "node scripts/audit-published-artifact.mjs",
76
83
  "version": "node scripts/sync-construct-version.mjs && git add .construct/version",
@@ -86,23 +93,30 @@
86
93
  "js-yaml": "^4.2.0"
87
94
  },
88
95
  "optionalDependencies": {
96
+ "@ai-sdk/anthropic": "^3.0.85",
97
+ "@ai-sdk/openai": "^3.0.73",
98
+ "@ai-sdk/openai-compatible": "^2.0.51",
89
99
  "@huggingface/transformers": "^4.2.0",
90
100
  "@opentelemetry/api": "^1.9.0",
91
- "@opentelemetry/core": "^2.7.1",
92
- "@opentelemetry/exporter-trace-otlp-http": "^0.218.0",
93
- "@opentelemetry/resources": "^2.7.1",
94
- "@opentelemetry/sdk-trace-node": "^1.25.0",
95
- "@opentelemetry/semantic-conventions": "^1.25.0",
101
+ "ai": "^6.0.208",
102
+ "ink": "^7.1.0",
96
103
  "mammoth": "^1.12.0",
97
- "unpdf": "^1.6.2"
104
+ "pptx-embed-fonts": "^0.0.6",
105
+ "pptxgenjs": "^4.0.1",
106
+ "react": "^19.2.7",
107
+ "unpdf": "^1.6.2",
108
+ "zod": "^4.4.3"
98
109
  },
99
110
  "overrides": {
100
- "express-rate-limit": "8.5.1"
111
+ "express-rate-limit": "8.5.1",
112
+ "undici": "6.27.0"
101
113
  },
102
114
  "devDependencies": {
103
115
  "@eslint/js": "^9.39.4",
104
116
  "c8": "^11.0.0",
117
+ "esbuild": "^0.28.1",
105
118
  "eslint": "^10.4.1",
106
- "globals": "^17.6.0"
119
+ "globals": "^17.6.0",
120
+ "ink-testing-library": "^4.0.0"
107
121
  }
108
122
  }