@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
@@ -3,7 +3,7 @@
3
3
  * lib/hooks/session-optimize.mjs — Session end optimization hook — triggers agent optimization for low-performers.
4
4
  *
5
5
  * Runs as a Stop hook at session end. Checks recent performance reviews for agents with avgScore < 0.7
6
- * and invokes `construct optimize <agent>` to generate and apply improvement patches.
6
+ * and schedules `construct optimize <agent> --dry-run` detached so the Stop hook returns within budget.
7
7
  *
8
8
  * @p95ms 300
9
9
  * @maxBlockingScope Stop (non-blocking, async)
@@ -12,110 +12,102 @@
12
12
  * @matcher *
13
13
  * @exits 0 = pass
14
14
  */
15
- import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
15
+ import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
16
16
  import { join, dirname } from 'path';
17
17
  import { homedir } from 'os';
18
- import { spawnSync } from 'child_process';
18
+ import { spawn } from 'child_process';
19
19
  import { fileURLToPath } from 'url';
20
20
 
21
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
22
  const CONSTRUCT_BIN = join(__dirname, '..', '..', 'bin', 'construct');
23
23
 
24
- const OPTIMIZATION_THRESHOLD = 0.7; // Score below this triggers optimization
25
- const MIN_TRACES_PER_AGENT = 3; // Minimum traces needed for optimization
26
- const OPTIMIZATION_DAYS = 7; // Look back 7 days for performance data
24
+ const OPTIMIZATION_THRESHOLD = 0.7;
25
+ const MIN_TRACES_PER_AGENT = 3;
26
+ const OPTIMIZATION_DAYS = 7;
27
27
  const OPTIMIZATION_LOG_DIR = join(homedir(), '.cx', 'optimization-logs');
28
+ const REVIEWS_DIR = join(homedir(), '.cx', 'performance-reviews');
28
29
 
29
- // Ensure optimization log directory exists
30
30
  mkdirSync(OPTIMIZATION_LOG_DIR, { recursive: true });
31
31
 
32
32
  let input = {};
33
33
  try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
34
34
 
35
- // Only run for Construct sessions
36
35
  const cwd = input?.cwd || process.cwd();
37
36
  const projectName = cwd.split('/').pop() || 'project';
38
37
 
39
- // Skip if this is not a Construct project or in global mode
40
38
  if (projectName !== 'construct' && !existsSync(join(cwd, '.cx'))) {
41
39
  process.exit(0);
42
40
  }
43
41
 
44
- // Reviews older than this trigger an auto-refresh on the next session-stop.
45
- // Six hours keeps optimization signal current without re-running review on
46
- // every short session. Operators can still run `construct review` manually.
47
42
  const REVIEW_REFRESH_MS = 6 * 60 * 60 * 1000;
48
43
 
44
+ function spawnDetached(args, { cwd: workDir = cwd } = {}) {
45
+ try {
46
+ const child = spawn(process.execPath, args, {
47
+ cwd: workDir,
48
+ stdio: 'ignore',
49
+ detached: true,
50
+ env: process.env,
51
+ });
52
+ child.unref();
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function listReviewJsonFiles(dir) {
60
+ if (!existsSync(dir)) return [];
61
+ return readdirSync(dir)
62
+ .filter((name) => name.endsWith('.json'))
63
+ .map((name) => join(dir, name));
64
+ }
65
+
49
66
  function maybeRefreshReviews() {
50
- const reviewsDir = join(homedir(), '.cx', 'performance-reviews');
51
67
  let needsRefresh = false;
52
68
  try {
53
- if (!existsSync(reviewsDir)) {
69
+ const files = listReviewJsonFiles(REVIEWS_DIR);
70
+ if (files.length === 0) {
54
71
  needsRefresh = true;
55
72
  } else {
56
- const files = spawnSync('find', [reviewsDir, '-name', '*.json', '-type', 'f'], {
57
- encoding: 'utf8', timeout: 5000,
58
- }).stdout.trim().split('\n').filter(Boolean);
59
- if (files.length === 0) {
60
- needsRefresh = true;
61
- } else {
62
- const newestMs = files.reduce((acc, f) => {
63
- try {
64
- const m = Number(spawnSync('stat', ['-f', '%m', f], { encoding: 'utf8' }).stdout.trim());
65
- return Number.isFinite(m) ? Math.max(acc, m * 1000) : acc;
66
- } catch { return acc; }
67
- }, 0);
68
- if (Date.now() - newestMs > REVIEW_REFRESH_MS) needsRefresh = true;
69
- }
73
+ const newestMs = files.reduce((acc, filePath) => {
74
+ try {
75
+ return Math.max(acc, statSync(filePath).mtimeMs);
76
+ } catch {
77
+ return acc;
78
+ }
79
+ }, 0);
80
+ if (Date.now() - newestMs > REVIEW_REFRESH_MS) needsRefresh = true;
70
81
  }
71
- } catch { /* tolerate fs errors silently — refresh is best-effort */ }
82
+ } catch {
83
+ /* refresh is best-effort */
84
+ }
72
85
 
73
86
  if (!needsRefresh) return;
74
87
 
75
- // Refresh detached so the Stop hook does not block on telemetry latency.
76
- try {
77
- spawnSync(process.execPath, [CONSTRUCT_BIN, 'review', '--quiet'], {
78
- stdio: 'ignore',
79
- timeout: 60_000,
80
- detached: true,
81
- });
82
- } catch { /* refresh is best-effort; manual `construct review` is always available */ }
88
+ spawnDetached([CONSTRUCT_BIN, 'review', '--quiet']);
83
89
  }
84
90
 
85
- // Read the most recent performance review
86
91
  function getLatestPerformanceReview() {
87
- const reviewsDir = join(homedir(), '.cx', 'performance-reviews');
88
- if (!existsSync(reviewsDir)) return null;
89
-
90
92
  try {
91
- const files = spawnSync('find', [reviewsDir, '-name', '*.json', '-type', 'f'], {
92
- encoding: 'utf8',
93
- timeout: 5000
94
- }).stdout.trim().split('\n').filter(Boolean);
95
-
93
+ const files = listReviewJsonFiles(REVIEWS_DIR);
96
94
  if (files.length === 0) return null;
97
-
98
- // Sort by modification time, newest first
95
+
99
96
  files.sort((a, b) => {
100
97
  try {
101
- const statA = spawnSync('stat', ['-f', '%m', a], { encoding: 'utf8' }).stdout.trim();
102
- const statB = spawnSync('stat', ['-f', '%m', b], { encoding: 'utf8' }).stdout.trim();
103
- return parseInt(statB) - parseInt(statA);
98
+ return statSync(b).mtimeMs - statSync(a).mtimeMs;
104
99
  } catch {
105
100
  return 0;
106
101
  }
107
102
  });
108
-
109
- const latestFile = files[0];
110
- const content = readFileSync(latestFile, 'utf8');
111
- return JSON.parse(content);
103
+
104
+ return JSON.parse(readFileSync(files[0], 'utf8'));
112
105
  } catch (error) {
113
106
  console.error(`Error reading performance reviews: ${error.message}`);
114
107
  return null;
115
108
  }
116
109
  }
117
110
 
118
- // Check if agent needs optimization
119
111
  function needsOptimization(agentStats) {
120
112
  return (
121
113
  agentStats.scoredInvocations >= MIN_TRACES_PER_AGENT &&
@@ -124,101 +116,25 @@ function needsOptimization(agentStats) {
124
116
  );
125
117
  }
126
118
 
127
- // Run optimization for a specific agent
128
- function optimizeAgent(agentName) {
129
- console.log(`🔧 Optimizing ${agentName} (score < ${OPTIMIZATION_THRESHOLD})`);
130
-
131
- const logFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-${agentName}.log`);
132
- const startTime = Date.now();
133
-
134
- try {
135
- // Preview only. The new contract requires an explicit --apply from the
136
- // operator; hook-driven runs must never modify prompt files.
137
- const result = spawnSync(CONSTRUCT_BIN, ['optimize', agentName, `--days=${OPTIMIZATION_DAYS}`, '--dry-run'], {
138
- cwd,
139
- encoding: 'utf8',
140
- timeout: 300000, // 5 minutes timeout for optimization
141
- stdio: ['pipe', 'pipe', 'pipe']
142
- });
143
-
144
- const duration = Date.now() - startTime;
145
-
146
- // Log the optimization attempt
147
- const logEntry = {
148
- timestamp: new Date().toISOString(),
149
- agent: agentName,
150
- durationMs: duration,
151
- success: result.status === 0,
152
- stdout: result.stdout,
153
- stderr: result.stderr,
154
- exitCode: result.status,
155
- signal: result.signal
156
- };
157
-
158
- writeFileSync(logFile, JSON.stringify(logEntry, null, 2) + '\n');
159
-
160
- if (result.status === 0) {
161
- console.log(`✅ Optimization for ${agentName} completed in ${duration}ms`);
162
-
163
- // Record observation about the optimization
164
- recordOptimizationObservation(agentName, true, duration);
165
- } else {
166
- console.log(`❌ Optimization for ${agentName} failed with code ${result.status}`);
167
- console.log(`Stderr: ${result.stderr.slice(0, 500)}...`);
168
-
169
- // Record observation about the failure
170
- recordOptimizationObservation(agentName, false, duration, result.stderr);
171
- }
172
-
173
- return result.status === 0;
174
- } catch (error) {
175
- console.error(`❌ Optimization for ${agentName} threw error: ${error.message}`);
176
-
177
- const logEntry = {
178
- timestamp: new Date().toISOString(),
179
- agent: agentName,
180
- durationMs: Date.now() - startTime,
181
- success: false,
182
- error: error.message,
183
- stack: error.stack
184
- };
185
-
186
- writeFileSync(logFile, JSON.stringify(logEntry, null, 2) + '\n');
187
- return false;
188
- }
189
- }
190
-
191
- // Record optimization observation for learning
192
- function recordOptimizationObservation(agentName, success, durationMs, error = null) {
193
- const observation = {
194
- role: 'construct',
195
- category: success ? 'pattern' : 'anti-pattern',
196
- summary: success
197
- ? `Auto-optimized ${agentName} in ${durationMs}ms`
198
- : `Auto-optimization failed for ${agentName}: ${error?.slice(0, 200) || 'unknown error'}`,
199
- content: success
200
- ? `Automatic optimization triggered for ${agentName} after performance review showed avgScore < ${OPTIMIZATION_THRESHOLD}. Optimization completed successfully in ${durationMs}ms.`
201
- : `Automatic optimization failed for ${agentName}. Error: ${error || 'Unknown'}. System should notify maintainer.`,
202
- tags: ['auto-optimization', 'performance-review', agentName],
203
- confidence: 0.9
204
- };
205
-
206
- try {
207
- // Use existing observation recording if available
208
- const observationFile = join(homedir(), '.cx', 'observations', `${Date.now()}-${agentName}-optimization.json`);
209
- mkdirSync(dirname(observationFile), { recursive: true });
210
- writeFileSync(observationFile, JSON.stringify(observation, null, 2) + '\n');
211
- } catch (error) {
212
- // Non-critical - observation recording failed
213
- console.error(`Failed to record optimization observation: ${error.message}`);
214
- }
119
+ function scheduleAgentOptimization(agentName) {
120
+ const logFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-${agentName}-scheduled.json`);
121
+ const scheduled = spawnDetached([
122
+ CONSTRUCT_BIN,
123
+ 'optimize',
124
+ agentName,
125
+ `--days=${OPTIMIZATION_DAYS}`,
126
+ '--dry-run',
127
+ ]);
128
+ writeFileSync(logFile, `${JSON.stringify({
129
+ timestamp: new Date().toISOString(),
130
+ agent: agentName,
131
+ scheduled,
132
+ mode: 'detached-dry-run',
133
+ })}\n`);
134
+ return scheduled;
215
135
  }
216
136
 
217
- // Main optimization logic
218
137
  function main() {
219
- // If the most recent review is missing or stale (>6h), kick off a refresh
220
- // detached so the Stop hook returns promptly. The refresh runs on the next
221
- // session's Stop if it's still in flight; nothing here blocks user work.
222
138
  maybeRefreshReviews();
223
139
 
224
140
  const review = getLatestPerformanceReview();
@@ -226,92 +142,53 @@ function main() {
226
142
  console.log('📊 No performance review data found. Run `construct review` first.');
227
143
  process.exit(0);
228
144
  }
229
-
230
- console.log(`📊 Checking ${review.agentStats.length} agents from performance review ${review.generated}`);
231
-
145
+
232
146
  const agentsNeedingOptimization = review.agentStats.filter(needsOptimization);
233
-
147
+
234
148
  if (agentsNeedingOptimization.length === 0) {
235
149
  console.log(`✅ All ${review.agentStats.length} agents have scores >= ${OPTIMIZATION_THRESHOLD} or insufficient data`);
236
150
  process.exit(0);
237
151
  }
238
-
239
- console.log(`🔧 Found ${agentsNeedingOptimization.length} agents needing optimization:`);
240
- agentsNeedingOptimization.forEach(agent => {
241
- console.log(` • ${agent.name}: avgScore=${agent.avgScore}, invocations=${agent.invocations}`);
242
- });
243
-
244
- // Sequential optimization keeps prompt-tuning deterministic and avoids
245
- // colliding writes to per-agent prompt files.
246
- let optimizedCount = 0;
247
- let failedCount = 0;
248
-
152
+
153
+ console.log(`🔧 Scheduling ${agentsNeedingOptimization.length} detached optimization preview(s):`);
154
+ let scheduledCount = 0;
249
155
  for (const agent of agentsNeedingOptimization) {
250
- const success = optimizeAgent(agent.name);
251
- if (success) {
252
- optimizedCount++;
253
- } else {
254
- failedCount++;
255
- }
256
-
257
- // Small delay between optimizations to avoid rate limits
258
- if (agentsNeedingOptimization.length > 1) {
259
- spawnSync('sleep', ['10'], { encoding: 'utf8' });
260
- }
261
- }
262
-
263
- // Summary
264
- console.log(`\n📈 Optimization Summary:`);
265
- console.log(` • Agents checked: ${review.agentStats.length}`);
266
- console.log(` • Needing optimization: ${agentsNeedingOptimization.length}`);
267
- console.log(` • Successfully optimized: ${optimizedCount}`);
268
- console.log(` • Failed: ${failedCount}`);
269
-
270
- if (optimizedCount > 0) {
271
- console.log(`\n💡 Optimized agents will be evaluated in the next performance review.`);
272
- console.log(` Run \`construct review\` after next session to see improvements.`);
156
+ console.log(` • ${agent.name}: avgScore=${agent.avgScore}, invocations=${agent.invocations}`);
157
+ if (scheduleAgentOptimization(agent.name)) scheduledCount += 1;
273
158
  }
274
-
275
- // Log summary to file
276
- const summary = {
159
+
160
+ const summaryFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-summary.json`);
161
+ writeFileSync(summaryFile, `${JSON.stringify({
277
162
  timestamp: new Date().toISOString(),
278
163
  project: projectName,
279
164
  reviewDate: review.generated,
280
165
  agentsChecked: review.agentStats.length,
281
166
  agentsNeedingOptimization: agentsNeedingOptimization.length,
282
- optimizedCount,
283
- failedCount,
284
- agents: agentsNeedingOptimization.map(a => ({
167
+ scheduledCount,
168
+ agents: agentsNeedingOptimization.map((a) => ({
285
169
  name: a.name,
286
170
  avgScore: a.avgScore,
287
171
  invocations: a.invocations,
288
- optimized: optimizeAgent.includes ? 'yes' : 'no'
289
- }))
290
- };
291
-
292
- const summaryFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-summary.json`);
293
- writeFileSync(summaryFile, JSON.stringify(summary, null, 2) + '\n');
172
+ })),
173
+ }, null, 2)}\n`);
174
+
175
+ console.log(`\n📈 Optimization previews scheduled: ${scheduledCount}/${agentsNeedingOptimization.length}`);
294
176
  }
295
177
 
296
- // Run main logic
297
178
  try {
298
179
  main();
299
180
  } catch (error) {
300
181
  console.error(`❌ Session optimization hook crashed: ${error.message}`);
301
- console.error(error.stack);
302
-
303
- // Log the crash
304
182
  const crashLog = join(OPTIMIZATION_LOG_DIR, 'crashes', `${Date.now()}-crash.json`);
305
183
  mkdirSync(dirname(crashLog), { recursive: true });
306
- writeFileSync(crashLog, JSON.stringify({
184
+ writeFileSync(crashLog, `${JSON.stringify({
307
185
  timestamp: new Date().toISOString(),
308
186
  error: error.message,
309
187
  stack: error.stack,
310
188
  cwd,
311
- projectName
312
- }, null, 2) + '\n');
313
-
189
+ projectName,
190
+ }, null, 2)}\n`);
314
191
  process.exit(1);
315
192
  }
316
193
 
317
- process.exit(0);
194
+ process.exit(0);
@@ -265,28 +265,31 @@ const selfKnowledgeNote = '\nSelf-knowledge: call `knowledge_search` for any Con
265
265
  let sourcesNote = '';
266
266
  let embedStatusNote = '';
267
267
  try {
268
- const repos = (process.env.GITHUB_REPOS ?? '').split(',').map(r => r.trim()).filter(Boolean);
269
- const jiraConfigured = !!(process.env.JIRA_BASE_URL);
270
- const linearConfigured = !!(process.env.LINEAR_API_KEY);
271
- const hasSources = repos.length > 0 || jiraConfigured || linearConfigured;
268
+ const { loadProjectConfig } = await import('../config/project-config.mjs');
269
+ const { resolveEffectiveSourceTargetsFromConfig } = await import('../config/source-targets.mjs');
270
+ const { config } = loadProjectConfig(process.cwd(), process.env);
271
+ const targets = resolveEffectiveSourceTargetsFromConfig(config, process.env);
272
+ const repos = targets.filter((t) => t.provider === 'github').map((t) => t.selector.repo);
273
+ const jiraTargets = targets.filter((t) => t.provider === 'jira');
274
+ const linearTargets = targets.filter((t) => t.provider === 'linear');
275
+ const slackTargets = targets.filter((t) => t.provider === 'slack');
276
+ const hasSources = targets.length > 0;
272
277
  if (hasSources) {
273
278
  const parts = [];
274
279
  if (repos.length > 0) parts.push(`GitHub repos: ${repos.join(', ')}`);
275
- if (jiraConfigured) parts.push('Jira: configured');
276
- if (linearConfigured) parts.push('Linear: configured');
277
- // Surface which sources are wired so `provider_fetch` is the obvious
278
- // tool for questions about them. The "fetch first, don't answer from
279
- // memory" policy lives in the persona.
280
+ if (jiraTargets.length > 0) parts.push(`Jira projects: ${jiraTargets.map((t) => t.selector.project).join(', ')}`);
281
+ if (linearTargets.length > 0) parts.push(`Linear teams: ${linearTargets.map((t) => t.selector.team).join(', ')}`);
282
+ if (slackTargets.length > 0) parts.push(`Slack channels: ${slackTargets.map((t) => t.selector.channel).join(', ')}`);
280
283
  sourcesNote = '\nProvider sources wired: ' + parts.join(' · ') +
281
284
  '. Use `provider_fetch` for any question about them.\n';
282
285
  }
286
+ } catch { /* best effort */ }
283
287
 
284
- // Embed daemon status — surface as a one-liner so operator always knows state
288
+ try {
285
289
  const { resolveEmbedStatus, autoStartEmbedIfNeeded } = await import('../embed/cli.mjs');
286
290
  const embedStatus = resolveEmbedStatus(process.env);
287
291
  if (embedStatus.level !== 'none') {
288
292
  embedStatusNote = `\n## Embed daemon\n${embedStatus.label} · ${embedStatus.detail}\n`;
289
- // Auto-start if CX_AUTO_EMBED=1 and daemon is stopped
290
293
  if (embedStatus.level === 'stopped' && process.env.CX_AUTO_EMBED === '1') {
291
294
  const result = await autoStartEmbedIfNeeded(process.env);
292
295
  if (result.started) {
@@ -319,11 +322,10 @@ try {
319
322
  // plugin so both platforms surface the same prelude on session start.
320
323
 
321
324
  let intakeReviewNote = '';
322
- let brokerStatusNote = '';
323
325
  try {
324
- const { buildIntakePrelude, buildBrokerStatusLine } = await import('../intake/session-prelude.mjs');
325
- intakeReviewNote = buildIntakePrelude({ cwd, env: process.env });
326
- brokerStatusNote = `\n${buildBrokerStatusLine({ env: process.env })}\n`;
326
+ const { buildSessionPrelude } = await import('../intake/session-prelude.mjs');
327
+ const prelude = buildSessionPrelude({ cwd, env: process.env });
328
+ intakeReviewNote = prelude ? `\n${prelude}\n` : '';
327
329
  } catch { /* best effort */ }
328
330
 
329
331
  // Missing-env-vars notice. Reads .env.example from the project root,
@@ -383,7 +385,7 @@ try {
383
385
  // that pollutes the caller's output contract, so the resolved output mode routes
384
386
  // the payload to stdout (interactive), stderr, or a debug log (silent).
385
387
 
386
- const payload = `${header}\n${body}${stateNote}${efficiencyNote}${observationsNote}${concurrencyNote}${skillScopeNote}${dropNote}${embedStatusNote}${sourcesNote}${selfKnowledgeNote}${rolesNote}${intakeReviewNote}${brokerStatusNote}${permissionPostureNote}${envCheckNote}\n${footer}${pendingNote}\n`;
388
+ const payload = `${header}\n${body}${stateNote}${efficiencyNote}${observationsNote}${concurrencyNote}${skillScopeNote}${dropNote}${embedStatusNote}${sourcesNote}${selfKnowledgeNote}${rolesNote}${intakeReviewNote}${permissionPostureNote}${envCheckNote}\n${footer}${pendingNote}\n`;
387
389
  try {
388
390
  const { mode } = resolveHookOutputMode({ cwd, env: process.env });
389
391
  writeHookContext({ payload, mode });
@@ -2,6 +2,13 @@
2
2
  * lib/host-disposition.mjs — single source of truth for the disposition of
3
3
  * every artifact Construct creates in a host project (ADR-0027 §1).
4
4
  *
5
+ * Scope model (see docs/concepts/project-scopes.md):
6
+ * templates/, docs/ — package/repo durable sources (committed)
7
+ * .construct/ — per-project launcher (ignored)
8
+ * .cx/ — per-project runtime: intake, oracle, observations, demos (ignored)
9
+ * ~/.construct/ — user credentials and doctor state
10
+ * ~/.cx/ — user-scope telemetry fallback
11
+ *
5
12
  * `IGNORED_PATTERNS` enumerates machine-specific, sync-regenerated artifacts
6
13
  * that must never be committed; the init `.gitignore` writer and the
7
14
  * `gitignore-coverage` reconciliation both read from here, and uninstall /
@@ -0,0 +1,189 @@
1
+ /**
2
+ * lib/improvement/cli.mjs — CLI handlers for `construct improvement <subcommand>`.
3
+ *
4
+ * Subcommands: submit, review, pending, show, approve, apply, rollback, list.
5
+ * Each command reads or writes durable state under `.cx/improvement/` and routes
6
+ * proposals through the governed controller; apply and rollback record human acts
7
+ * but do not mutate live artifacts.
8
+ */
9
+ import fs from 'node:fs';
10
+
11
+ import {
12
+ applyRecord,
13
+ approveRecord,
14
+ formatReviewSummary,
15
+ listPending,
16
+ resolveKnownApprovers,
17
+ reviewRecord,
18
+ rollbackRecord,
19
+ submitBundle,
20
+ } from './surface.mjs';
21
+ import { listRecords, loadRecord } from './store.mjs';
22
+
23
+ function readJsonArg(args, prefix) {
24
+ const hit = args.find((a) => a.startsWith(prefix));
25
+ if (!hit) return null;
26
+ const file = hit.slice(prefix.length);
27
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
28
+ }
29
+
30
+ function flagValue(args, prefix) {
31
+ const hit = args.find((a) => a.startsWith(prefix));
32
+ return hit ? hit.slice(prefix.length) : null;
33
+ }
34
+
35
+ function usage() {
36
+ process.stderr.write(
37
+ 'Usage: construct improvement <submit|review|pending|show|approve|apply|rollback|list> [--json]\n'
38
+ + ' submit --bundle=FILE | (--trace=FILE --dataset=FILE --report=FILE [--trigger=KIND])\n'
39
+ + ' review <id>\n'
40
+ + ' pending\n'
41
+ + ' show <id>\n'
42
+ + ' approve <id> [--identity=USER]\n'
43
+ + ' apply <id> [--monitor=ID]\n'
44
+ + ' rollback <id> [--reason=TEXT]\n'
45
+ + ' list [--state=STATE]\n',
46
+ );
47
+ }
48
+
49
+ export async function runImprovementCli(args, { projectDir = process.cwd() } = {}) {
50
+ const json = args.includes('--json');
51
+ const sub = args.find((a) => !a.startsWith('-'));
52
+ const positional = args.filter((a) => !a.startsWith('-'));
53
+
54
+ if (!sub || sub === '--help' || sub === '-h') {
55
+ usage();
56
+ return sub === '--help' || sub === '-h' ? 0 : 1;
57
+ }
58
+
59
+ const write = (payload) => {
60
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
61
+ };
62
+
63
+ switch (sub) {
64
+ case 'submit': {
65
+ const bundle = readJsonArg(args, '--bundle=');
66
+ const trace = bundle?.trace ?? readJsonArg(args, '--trace=');
67
+ const dataset = bundle?.dataset ?? readJsonArg(args, '--dataset=');
68
+ const evaluationReport = bundle?.evaluationReport ?? readJsonArg(args, '--report=');
69
+ const triggerKind = flagValue(args, '--trigger=') || bundle?.trigger?.kind || 'operator-request';
70
+ const trigger = bundle?.trigger || { kind: triggerKind, optIn: true };
71
+ const result = submitBundle({
72
+ projectDir,
73
+ bundle: bundle || {
74
+ trace,
75
+ trigger,
76
+ dataset,
77
+ evaluationReport,
78
+ approver: flagValue(args, '--approver=') || resolveKnownApprovers(projectDir)[0] || null,
79
+ },
80
+ });
81
+ if (json) write(result);
82
+ else if (!result.ok) {
83
+ process.stderr.write(`submit refused: ${result.stage || result.error}\n`);
84
+ if (result.governance?.refusals?.length) {
85
+ process.stderr.write(` refusals: ${result.governance.refusals.join(', ')}\n`);
86
+ }
87
+ return 1;
88
+ } else {
89
+ process.stdout.write(`submitted ${result.record.id} → ${result.record.proposal.state}\n`);
90
+ }
91
+ return result.ok ? 0 : 1;
92
+ }
93
+
94
+ case 'review': {
95
+ const id = positional[1];
96
+ if (!id) { usage(); return 1; }
97
+ const result = reviewRecord(projectDir, id);
98
+ if (!result.ok) {
99
+ process.stderr.write(`review failed: ${result.error}\n`);
100
+ return 1;
101
+ }
102
+ if (json) write(result);
103
+ else process.stdout.write(formatReviewSummary(result.record, result.governance) + '\n');
104
+ return 0;
105
+ }
106
+
107
+ case 'pending': {
108
+ const pending = listPending(projectDir);
109
+ if (json) write({ pending });
110
+ else {
111
+ if (!pending.length) process.stdout.write('No proposals awaiting approval.\n');
112
+ for (const row of pending) {
113
+ process.stdout.write(`${row.id} · ${row.proposal?.type} · ${row.proposal?.state}\n`);
114
+ }
115
+ }
116
+ return 0;
117
+ }
118
+
119
+ case 'show': {
120
+ const id = positional[1];
121
+ if (!id) { usage(); return 1; }
122
+ const record = loadRecord(projectDir, id);
123
+ if (!record) {
124
+ process.stderr.write(`not found: ${id}\n`);
125
+ return 1;
126
+ }
127
+ if (json) write(record);
128
+ else process.stdout.write(formatReviewSummary(record) + '\n');
129
+ return 0;
130
+ }
131
+
132
+ case 'approve': {
133
+ const id = positional[1];
134
+ if (!id) { usage(); return 1; }
135
+ const result = approveRecord(projectDir, id, { identity: flagValue(args, '--identity=') });
136
+ if (!result.ok) {
137
+ process.stderr.write(`approve failed: ${result.error}\n`);
138
+ return 1;
139
+ }
140
+ if (json) write(result);
141
+ else process.stdout.write(`approved ${id} → ${result.record.proposal.state}\n`);
142
+ return 0;
143
+ }
144
+
145
+ case 'apply': {
146
+ const id = positional[1];
147
+ if (!id) { usage(); return 1; }
148
+ const result = applyRecord(projectDir, id, { monitor: flagValue(args, '--monitor=') });
149
+ if (!result.ok) {
150
+ process.stderr.write(`apply failed: ${result.error}\n`);
151
+ return 1;
152
+ }
153
+ if (json) write(result);
154
+ else process.stdout.write(`applied ${id} → ${result.record.proposal.state}\n`);
155
+ return 0;
156
+ }
157
+
158
+ case 'rollback': {
159
+ const id = positional[1];
160
+ if (!id) { usage(); return 1; }
161
+ const result = rollbackRecord(projectDir, id, { reason: flagValue(args, '--reason=') || 'operator-rollback' });
162
+ if (!result.ok) {
163
+ process.stderr.write(`rollback failed: ${result.error}\n`);
164
+ return 1;
165
+ }
166
+ if (json) write(result);
167
+ else process.stdout.write(`rolled back ${id} → ${result.record.proposal.state}\n`);
168
+ return 0;
169
+ }
170
+
171
+ case 'list': {
172
+ const state = flagValue(args, '--state=');
173
+ const rows = listRecords(projectDir, { state });
174
+ if (json) write({ proposals: rows });
175
+ else {
176
+ if (!rows.length) process.stdout.write('No improvement proposals.\n');
177
+ for (const row of rows) {
178
+ process.stdout.write(`${row.id} · ${row.proposal?.type} · ${row.proposal?.state}\n`);
179
+ }
180
+ }
181
+ return 0;
182
+ }
183
+
184
+ default:
185
+ process.stderr.write(`Unknown subcommand: ${sub}\n`);
186
+ usage();
187
+ return 1;
188
+ }
189
+ }