@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,137 @@
1
+ /**
2
+ * lib/improvement/controller.mjs — the approval-gated improvement-loop controller
3
+ * (construct-6zga.1.5).
4
+ *
5
+ * The controller is the mutation boundary for the governed loop. It admits only
6
+ * versioned artifacts — proposal objects, dataset items, and evaluation reports,
7
+ * never raw chat history — and refuses any candidate that lacks provenance,
8
+ * lacks held-out evaluation evidence, failed a deterministic gate, names an unknown
9
+ * approver, or has an unresolved dependency. Promotability is re-derived from the
10
+ * report's own gate results rather than a precomputed flag, so a tampered decision
11
+ * cannot pass. The final mutation boundary requires an explicit human approval act,
12
+ * recorded with the approver identity and timestamp; the controller never applies a
13
+ * change itself — it plans the rollout and records the human-performed apply and
14
+ * rollback, keeping the loop a chain of proposals, not autonomous mutations.
15
+ */
16
+ import { transitionProposal, validateProposal } from './proposal.mjs';
17
+ import { decidePromotion } from '../evals/gates.mjs';
18
+
19
+ function isVersioned(artifact) {
20
+ return Boolean(artifact && typeof artifact === 'object' && Number.isInteger(artifact.schemaVersion));
21
+ }
22
+
23
+ /**
24
+ * Admit the input artifacts. Only versioned proposal/dataset/evaluation objects are
25
+ * accepted; an unversioned blob (such as raw chat history) is refused
26
+ * (construct-6zga.1.5 AC1).
27
+ */
28
+ export function admitArtifacts({ proposal = null, dataset = null, evaluationReport = null } = {}) {
29
+ const refusals = [];
30
+ if (!validateProposal(proposal).valid) refusals.push('proposal-invalid');
31
+ if (!isVersioned(dataset)) refusals.push('dataset-unversioned');
32
+ if (!isVersioned(evaluationReport)) refusals.push('evaluation-report-unversioned');
33
+ return { admissible: refusals.length === 0, refusals };
34
+ }
35
+
36
+ /**
37
+ * Evaluate the refusal guards. Missing provenance, missing held-out results, a
38
+ * failed deterministic gate, an unknown approver, or an unresolved dependency each
39
+ * blocks the proposal from reaching the approval boundary (construct-6zga.1.5 AC2).
40
+ */
41
+ export function evaluateGuards({ proposal = null, evaluationReport = null, knownApprovers = [], resolvedDependencies = [] } = {}) {
42
+ const refusals = [];
43
+
44
+ const traces = proposal?.lineage?.inputTraceIds;
45
+ if (!Array.isArray(traces) || traces.length < 1) refusals.push('insufficient-provenance');
46
+
47
+ if (!evaluationReport || evaluationReport.deltas == null) refusals.push('missing-held-out-results');
48
+ if (evaluationReport?.blocked === true) refusals.push('failed-deterministic-checks');
49
+
50
+ const approverId = proposal?.approver?.identity ?? null;
51
+ if (!approverId || !knownApprovers.includes(approverId)) refusals.push('unknown-approver');
52
+
53
+ const deps = Array.isArray(proposal?.dependencies) ? proposal.dependencies : [];
54
+ const resolved = new Set(resolvedDependencies);
55
+ if (deps.some((d) => !resolved.has(d))) refusals.push('unresolved-dependency');
56
+
57
+ return { admissible: refusals.length === 0, refusals };
58
+ }
59
+
60
+ /**
61
+ * Run the full admission + guard pipeline and re-derive promotability from the
62
+ * report's gate results (judges never stand alone). Returns the stage that refused,
63
+ * or admissible: true once the proposal is ready for the human approval boundary.
64
+ */
65
+ export function governProposal({ proposal = null, dataset = null, evaluationReport = null, knownApprovers = [], resolvedDependencies = [] } = {}) {
66
+ const admission = admitArtifacts({ proposal, dataset, evaluationReport });
67
+ if (!admission.admissible) return { admissible: false, stage: 'admission', refusals: admission.refusals };
68
+
69
+ const guards = evaluateGuards({ proposal, evaluationReport, knownApprovers, resolvedDependencies });
70
+ if (!guards.admissible) return { admissible: false, stage: 'guards', refusals: guards.refusals };
71
+
72
+ const gates = Array.isArray(evaluationReport.gates) ? evaluationReport.gates : [];
73
+ const deterministic = {
74
+ blocked: evaluationReport.blocked === true,
75
+ regressions: gates.filter((g) => !g.pass).map((g) => g.gate),
76
+ gates,
77
+ };
78
+ const promotion = decidePromotion({ deterministic, judges: evaluationReport.evaluators || [] });
79
+ if (!promotion.promotable) return { admissible: false, stage: 'promotion', refusals: ['not-promotable'], promotion };
80
+
81
+ return { admissible: true, stage: 'awaiting-approval', refusals: [], promotion };
82
+ }
83
+
84
+ /**
85
+ * The final mutation boundary: an explicit human approval act is mandatory and is
86
+ * recorded with the approver identity and timestamp. Transitions
87
+ * awaiting_approval -> approved, or refuses (construct-6zga.1.5 AC3).
88
+ */
89
+ export function requireApproval({ proposal = null, approval = null, knownApprovers = [], nowIso = null } = {}) {
90
+ if (proposal?.state !== 'awaiting_approval') {
91
+ return { ok: false, error: `approval requires state awaiting_approval, got ${proposal?.state}` };
92
+ }
93
+ const identity = approval?.identity ?? null;
94
+ if (!identity || !knownApprovers.includes(identity)) {
95
+ return { ok: false, error: 'approval refused: unknown approver' };
96
+ }
97
+ const approver = { identity, approvedAt: approval?.approvedAt ?? nowIso, decision: 'approved' };
98
+ return transitionProposal(proposal, 'approved', { approver });
99
+ }
100
+
101
+ /**
102
+ * Plan a traceable rollout for an approved proposal: sandbox, staged apply, and
103
+ * post-apply monitoring, plus the rollback target and blast radius. The controller
104
+ * plans but never applies — application is a human-performed action
105
+ * (construct-6zga.1.5 AC4).
106
+ */
107
+ export function planRollout({ proposal = null } = {}) {
108
+ if (proposal?.state !== 'approved') return { ok: false, error: `rollout requires state approved, got ${proposal?.state}` };
109
+ return {
110
+ ok: true,
111
+ plan: {
112
+ mode: proposal.rolloutMode,
113
+ steps: ['sandbox', 'staged-apply', 'post-apply-monitor'],
114
+ rollbackTarget: proposal.rollbackTarget,
115
+ affectedProfiles: proposal.affectedProfiles,
116
+ blastRadius: proposal.blastRadius,
117
+ },
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Record a human-performed application (approved -> applied) with its monitor
123
+ * handle, keeping the apply traceable (construct-6zga.1.5 AC4).
124
+ */
125
+ export function recordApplication({ proposal = null, monitor = null, nowIso = null } = {}) {
126
+ return transitionProposal(proposal, 'applied', { rollout: { appliedAt: nowIso, monitor } });
127
+ }
128
+
129
+ /**
130
+ * Record a deterministic rollback (applied -> rolled_back) to the proposal's
131
+ * declared rollback target (construct-6zga.1.5 AC4).
132
+ */
133
+ export function recordRollback({ proposal = null, reason = null, nowIso = null } = {}) {
134
+ return transitionProposal(proposal, 'rolled_back', {
135
+ rollout: { rolledBackTo: proposal?.rollbackTarget ?? null, reason, rolledBackAt: nowIso },
136
+ });
137
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * lib/improvement/proposal.mjs — the improvement-loop proposal record and its
3
+ * operator state machine (construct-6zga.1.5).
4
+ *
5
+ * Every candidate change in the governed loop is a proposal, never an applied
6
+ * mutation. A proposal declares its type, the affected profiles, the blast radius,
7
+ * a rollback target, the gates to pass, an approver, and a rollout mode, plus
8
+ * immutable lineage — input-trace references, baseline/candidate versions, the
9
+ * capability snapshot, evaluator versions, and budgets — so a reader can re-verify
10
+ * its provenance. The operator state machine encodes the only legal progressions
11
+ * (observed through applied/rolled_back), so a proposal can never jump the held-out
12
+ * evaluation or the approval boundary by skipping a state.
13
+ * Reference shape: schemas/improvement-proposal.schema.json.
14
+ */
15
+
16
+ export const IMPROVEMENT_PROPOSAL_SCHEMA_VERSION = 1;
17
+
18
+ export const PROPOSAL_TYPES = Object.freeze([
19
+ 'prompt',
20
+ 'role-flavor',
21
+ 'skill',
22
+ 'routing-rule',
23
+ 'contract',
24
+ 'provider-adapter',
25
+ 'policy-config',
26
+ ]);
27
+
28
+ export const PROPOSAL_STATES = Object.freeze([
29
+ 'observed',
30
+ 'rejected',
31
+ 'reproduce_failed',
32
+ 'baseline_failed',
33
+ 'proposal_ready',
34
+ 'evaluation_failed',
35
+ 'awaiting_approval',
36
+ 'approved',
37
+ 'applied',
38
+ 'rolled_back',
39
+ 'superseded',
40
+ ]);
41
+
42
+ export const BLAST_RADIUS = Object.freeze(['single-surface', 'multi-surface', 'global']);
43
+ export const ROLLOUT_MODES = Object.freeze(['sandbox', 'staged', 'monitored']);
44
+
45
+ // The only legal state progressions. superseded is reachable from any non-terminal
46
+ // state (a newer proposal replaces this one); the terminal states have no
47
+ // successor. Nothing reaches applied without passing through approved, and nothing
48
+ // reaches approved without awaiting_approval — the held-out evaluation and the
49
+ // human approval boundary cannot be skipped.
50
+
51
+ const LEGAL_TRANSITIONS = Object.freeze({
52
+ observed: Object.freeze(['reproduce_failed', 'proposal_ready', 'rejected', 'superseded']),
53
+ proposal_ready: Object.freeze(['baseline_failed', 'evaluation_failed', 'awaiting_approval', 'superseded']),
54
+ awaiting_approval: Object.freeze(['approved', 'rejected', 'superseded']),
55
+ approved: Object.freeze(['applied', 'superseded']),
56
+ applied: Object.freeze(['rolled_back', 'superseded']),
57
+ rejected: Object.freeze([]),
58
+ reproduce_failed: Object.freeze([]),
59
+ baseline_failed: Object.freeze([]),
60
+ evaluation_failed: Object.freeze([]),
61
+ rolled_back: Object.freeze([]),
62
+ superseded: Object.freeze([]),
63
+ });
64
+
65
+ export function isTerminalState(state) {
66
+ return Array.isArray(LEGAL_TRANSITIONS[state]) && LEGAL_TRANSITIONS[state].length === 0;
67
+ }
68
+
69
+ export function canTransition(from, to) {
70
+ return Array.isArray(LEGAL_TRANSITIONS[from]) && LEGAL_TRANSITIONS[from].includes(to);
71
+ }
72
+
73
+ /**
74
+ * Advance a proposal to a new state, merging an optional patch. Returns
75
+ * { ok, proposal } on a legal transition or { ok: false, error } otherwise, so the
76
+ * controller can refuse rather than throw on an illegal progression.
77
+ */
78
+ export function transitionProposal(proposal, to, patch = {}) {
79
+ const from = proposal?.state;
80
+ if (!PROPOSAL_STATES.includes(to)) return { ok: false, error: `unknown target state: ${to}` };
81
+ if (!canTransition(from, to)) return { ok: false, error: `illegal transition: ${from} -> ${to}` };
82
+ return { ok: true, proposal: { ...proposal, ...patch, state: to } };
83
+ }
84
+
85
+ /**
86
+ * Hand-rolled validator (no ajv — Construct stays dependency-free at startup).
87
+ * Returns { valid, errors } against schemas/improvement-proposal.schema.json.
88
+ */
89
+ export function validateProposal(proposal) {
90
+ const errors = [];
91
+ if (!proposal || typeof proposal !== 'object') return { valid: false, errors: ['proposal is not an object'] };
92
+ if (proposal.schemaVersion !== IMPROVEMENT_PROPOSAL_SCHEMA_VERSION) errors.push(`schemaVersion must be ${IMPROVEMENT_PROPOSAL_SCHEMA_VERSION}`);
93
+ if (typeof proposal.id !== 'string' || !proposal.id) errors.push('id required');
94
+ if (!PROPOSAL_TYPES.includes(proposal.type)) errors.push(`type invalid: ${proposal.type}`);
95
+ if (!PROPOSAL_STATES.includes(proposal.state)) errors.push(`state invalid: ${proposal.state}`);
96
+ if (!Array.isArray(proposal.affectedProfiles)) errors.push('affectedProfiles must be an array');
97
+ if (!BLAST_RADIUS.includes(proposal.blastRadius)) errors.push(`blastRadius invalid: ${proposal.blastRadius}`);
98
+ if (!proposal.rollbackTarget || typeof proposal.rollbackTarget !== 'object' || typeof proposal.rollbackTarget.version !== 'string') {
99
+ errors.push('rollbackTarget.version required');
100
+ }
101
+ if (!Array.isArray(proposal.requiredGates)) errors.push('requiredGates must be an array');
102
+ if (!ROLLOUT_MODES.includes(proposal.rolloutMode)) errors.push(`rolloutMode invalid: ${proposal.rolloutMode}`);
103
+
104
+ const lineage = proposal.lineage;
105
+ if (!lineage || typeof lineage !== 'object') errors.push('lineage missing');
106
+ else {
107
+ if (!Array.isArray(lineage.inputTraceIds)) errors.push('lineage.inputTraceIds must be an array');
108
+ if (typeof lineage.baselineVersion !== 'string') errors.push('lineage.baselineVersion required');
109
+ if (typeof lineage.candidateVersion !== 'string') errors.push('lineage.candidateVersion required');
110
+ if (!lineage.capabilitySnapshot || typeof lineage.capabilitySnapshot !== 'object') errors.push('lineage.capabilitySnapshot required');
111
+ if (!Array.isArray(lineage.evaluatorVersions)) errors.push('lineage.evaluatorVersions must be an array');
112
+ }
113
+
114
+ const approver = proposal.approver;
115
+ if (approver !== null && (typeof approver !== 'object' || typeof approver.identity !== 'string')) {
116
+ errors.push('approver must be null or carry an identity');
117
+ }
118
+
119
+ return { valid: errors.length === 0, errors };
120
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * lib/improvement/specialist-loop.mjs — opt-in, attribution-first specialist
3
+ * improvement (construct-6zga.1.7).
4
+ *
5
+ * Specialist improvement is triggered, never ambient: no generic self-review loop
6
+ * is inserted into every prompt, and an evaluation runs only from one of the
7
+ * bounded triggers (human correction, deterministic failure, repeated baseline
8
+ * regression, or an explicit operator request). Before any change is proposed, a
9
+ * failure is attributed causally across upstream context, provider execution, the
10
+ * handoff contract, the downstream consumer, evaluator uncertainty, and only last
11
+ * the specialist's own output — so a specialist is never blamed for an upstream or
12
+ * provider failure. A change is proposed only when the specialist itself is at
13
+ * fault, targets a single specialist artifact, and is routed through the governed
14
+ * controller, which holds it to held-out evaluation and human approval. Nothing
15
+ * here applies a change or routes on a score. Reference shape:
16
+ * schemas/specialist-trace.schema.json.
17
+ */
18
+ import { governProposal } from './controller.mjs';
19
+
20
+ export const SPECIALIST_TRACE_SCHEMA_VERSION = 1;
21
+
22
+ export const FAILURE_CAUSES = Object.freeze([
23
+ 'upstream-context',
24
+ 'provider-execution',
25
+ 'handoff-contract',
26
+ 'downstream-consumer',
27
+ 'evaluator-uncertainty',
28
+ 'specialist-prompt',
29
+ 'none',
30
+ ]);
31
+
32
+ export const TRIGGER_KINDS = Object.freeze([
33
+ 'human-correction',
34
+ 'deterministic-failure',
35
+ 'repeated-baseline-regression',
36
+ 'operator-request',
37
+ ]);
38
+
39
+ export const SPECIALIST_TARGETS = Object.freeze(['prompt', 'skill', 'role-flavor', 'routing-rule', 'contract']);
40
+
41
+ const HUMAN_TARGET_TO_CAUSE = Object.freeze({
42
+ upstream: 'upstream-context',
43
+ contract: 'handoff-contract',
44
+ downstream: 'downstream-consumer',
45
+ provider: 'provider-execution',
46
+ specialist: 'specialist-prompt',
47
+ });
48
+
49
+ /**
50
+ * Attribute a failure to a single cause. External causes are checked before the
51
+ * specialist, so a specialist is blamed only once upstream context, provider
52
+ * execution, the input handoff contract, the downstream consumer, and evaluator
53
+ * certainty are all clean (construct-6zga.1.7 AC1, AC2). A human correction that
54
+ * names a non-specialist cause is authoritative.
55
+ */
56
+ export function attributeFailure(trace = {}) {
57
+ const t = trace || {};
58
+ const correctionTarget = t.humanCorrection?.target ?? null;
59
+
60
+ if (correctionTarget && correctionTarget !== 'specialist') {
61
+ return { cause: HUMAN_TARGET_TO_CAUSE[correctionTarget] || 'upstream-context', blameSpecialist: false, detail: `human correction -> ${correctionTarget}` };
62
+ }
63
+ if (t.upstream?.evidenceComplete === false || t.upstream?.inputsPresent === false) {
64
+ return { cause: 'upstream-context', blameSpecialist: false, detail: 'upstream evidence or inputs incomplete' };
65
+ }
66
+ if (t.provider?.executionError === true || t.provider?.degraded === true) {
67
+ return { cause: 'provider-execution', blameSpecialist: false, detail: 'provider execution error or degraded' };
68
+ }
69
+ if (t.handoff?.inputValid === false) {
70
+ return { cause: 'handoff-contract', blameSpecialist: false, detail: 'handoff input contract violated' };
71
+ }
72
+ if (t.downstream?.consumerError === true) {
73
+ return { cause: 'downstream-consumer', blameSpecialist: false, detail: 'downstream consumer error' };
74
+ }
75
+ if (t.evaluator?.abstained === true) {
76
+ return { cause: 'evaluator-uncertainty', blameSpecialist: false, detail: 'evaluator abstained' };
77
+ }
78
+
79
+ const specialistFault = t.specialistOutput?.evidenceVerdict === 'fail'
80
+ || t.handoff?.schemaValid === false
81
+ || correctionTarget === 'specialist';
82
+ if (specialistFault) {
83
+ return { cause: 'specialist-prompt', blameSpecialist: true, detail: 'specialist output failed with every external cause clean' };
84
+ }
85
+ return { cause: 'none', blameSpecialist: false, detail: 'no attributable failure' };
86
+ }
87
+
88
+ /**
89
+ * The improvement loop is opt-in and bounded: it runs only when explicitly enabled
90
+ * and only for one of the four recognized triggers (construct-6zga.1.7 AC3).
91
+ */
92
+ export function shouldTrigger({ kind = null, optIn = false } = {}) {
93
+ return optIn === true && TRIGGER_KINDS.includes(kind);
94
+ }
95
+
96
+ /**
97
+ * Build a scoped improvement proposal for a single specialist artifact — only when
98
+ * the specialist itself is at fault. A non-specialist cause yields no proposal, so
99
+ * an upstream or provider failure never rewrites a specialist (construct-6zga.1.7
100
+ * AC2, AC5).
101
+ */
102
+ export function proposeSpecialistChange({ trace = {}, attribution = null, target = 'prompt', baselineVersion = null, candidateVersion = null, heldOutProfiles = [], approver = null } = {}) {
103
+ const attr = attribution || attributeFailure(trace);
104
+ if (!attr.blameSpecialist) return { proposed: false, reason: attr.cause, attribution: attr };
105
+
106
+ const profiles = Array.isArray(heldOutProfiles) && heldOutProfiles.length
107
+ ? heldOutProfiles
108
+ : [trace.specialist?.profileId].filter(Boolean);
109
+ const traceIds = Array.isArray(trace.sourceTraceIds) && trace.sourceTraceIds.length
110
+ ? trace.sourceTraceIds
111
+ : [trace.id].filter(Boolean);
112
+
113
+ const proposal = {
114
+ schemaVersion: 1,
115
+ id: `prop-${trace.id || 'specialist'}`,
116
+ type: SPECIALIST_TARGETS.includes(target) ? target : 'prompt',
117
+ state: 'proposal_ready',
118
+ affectedProfiles: profiles,
119
+ blastRadius: 'single-surface',
120
+ rollbackTarget: { version: baselineVersion || trace.versions?.[target] || 'unknown', ref: null },
121
+ requiredGates: ['contract-schema', 'source-provenance', 'safety-permission'],
122
+ rolloutMode: 'staged',
123
+ dependencies: [],
124
+ evaluationReportId: null,
125
+ terminalReason: null,
126
+ lineage: {
127
+ inputTraceIds: traceIds,
128
+ baselineVersion: baselineVersion || 'unknown',
129
+ candidateVersion: candidateVersion || 'unknown',
130
+ capabilitySnapshot: { capabilityClass: trace.specialist?.capabilityClass || 'unknown' },
131
+ evaluatorVersions: ['gates@1'],
132
+ budgets: null,
133
+ },
134
+ approver: approver ? { identity: approver, approvedAt: null, decision: null } : null,
135
+ rollout: null,
136
+ };
137
+ return { proposed: true, attribution: attr, proposal };
138
+ }
139
+
140
+ /**
141
+ * Run the opt-in specialist improvement loop end to end: gate on the trigger,
142
+ * attribute the failure, and — only when the specialist is at fault — build a
143
+ * scoped proposal and route it through the governed controller for held-out
144
+ * evaluation and human approval. Never applies a change and never routes on a
145
+ * score (construct-6zga.1.7 AC4, AC5).
146
+ */
147
+ export function runSpecialistImprovement({
148
+ trace = {}, trigger = null, target = 'prompt', heldOutProfiles = [],
149
+ baselineVersion = null, candidateVersion = null, approver = null,
150
+ dataset = null, evaluationReport = null, knownApprovers = [], resolvedDependencies = [],
151
+ } = {}) {
152
+ if (!shouldTrigger(trigger)) return { triggered: false, reason: 'not-triggered' };
153
+
154
+ const attribution = attributeFailure(trace);
155
+ if (!attribution.blameSpecialist) return { triggered: true, attribution, proposed: false, reason: attribution.cause };
156
+
157
+ const { proposal } = proposeSpecialistChange({ trace, attribution, target, baselineVersion, candidateVersion, heldOutProfiles, approver });
158
+ const governance = governProposal({ proposal, dataset, evaluationReport, knownApprovers, resolvedDependencies });
159
+ return { triggered: true, attribution, proposed: true, proposal, governance };
160
+ }
161
+
162
+ /**
163
+ * Hand-rolled validator (no ajv — Construct stays dependency-free at startup).
164
+ * Returns { valid, errors } against schemas/specialist-trace.schema.json.
165
+ */
166
+ export function validateSpecialistTrace(trace) {
167
+ const errors = [];
168
+ if (!trace || typeof trace !== 'object') return { valid: false, errors: ['trace is not an object'] };
169
+ if (trace.schemaVersion !== SPECIALIST_TRACE_SCHEMA_VERSION) errors.push(`schemaVersion must be ${SPECIALIST_TRACE_SCHEMA_VERSION}`);
170
+ if (typeof trace.id !== 'string' || !trace.id) errors.push('id required');
171
+ if (!trace.specialist || typeof trace.specialist.role !== 'string') errors.push('specialist.role required');
172
+
173
+ for (const [key, fields] of [
174
+ ['upstream', ['evidenceComplete', 'inputsPresent']],
175
+ ['provider', ['executionError', 'degraded']],
176
+ ['handoff', ['inputValid', 'schemaValid']],
177
+ ]) {
178
+ const block = trace[key];
179
+ if (!block || typeof block !== 'object') { errors.push(`${key} missing`); continue; }
180
+ for (const f of fields) if (typeof block[f] !== 'boolean') errors.push(`${key}.${f} must be boolean`);
181
+ }
182
+
183
+ if (!trace.specialistOutput || !['pass', 'fail', 'unverified'].includes(trace.specialistOutput.evidenceVerdict)) {
184
+ errors.push('specialistOutput.evidenceVerdict invalid');
185
+ }
186
+ if (!trace.downstream || !['accepted', 'rejected', 'unknown'].includes(trace.downstream.outcome)) {
187
+ errors.push('downstream.outcome invalid');
188
+ }
189
+ if (!trace.evaluator || typeof trace.evaluator.abstained !== 'boolean') errors.push('evaluator.abstained must be boolean');
190
+
191
+ return { valid: errors.length === 0, errors };
192
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * lib/improvement/store.mjs — durable proposal records for the governed
3
+ * improvement loop (construct-6zga.1.11).
4
+ *
5
+ * Each submission is a versioned JSON file under `.cx/improvement/proposals/`
6
+ * holding the proposal, held-out dataset item, evaluation report, optional
7
+ * specialist trace, governance verdict, and rollout plan. The store is the
8
+ * operator surface's source of truth; the controller never mutates artifacts in
9
+ * place without persisting the new state here.
10
+ */
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+
14
+ export const IMPROVEMENT_STORE_SCHEMA_VERSION = 1;
15
+
16
+ export function improvementDir(projectDir) {
17
+ return path.join(projectDir, '.cx', 'improvement');
18
+ }
19
+
20
+ export function proposalsDir(projectDir) {
21
+ return path.join(improvementDir(projectDir), 'proposals');
22
+ }
23
+
24
+ export function proposalRecordPath(projectDir, id) {
25
+ return path.join(proposalsDir(projectDir), `${id}.json`);
26
+ }
27
+
28
+ export function approversPath(projectDir) {
29
+ return path.join(improvementDir(projectDir), 'approvers.json');
30
+ }
31
+
32
+ function ensureProposalsDir(projectDir) {
33
+ const dir = proposalsDir(projectDir);
34
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
35
+ return dir;
36
+ }
37
+
38
+ export function loadApprovers(projectDir) {
39
+ const file = approversPath(projectDir);
40
+ if (!fs.existsSync(file)) return null;
41
+ try {
42
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
43
+ return Array.isArray(parsed?.identities) ? parsed.identities : null;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ export function saveRecord(projectDir, record) {
50
+ ensureProposalsDir(projectDir);
51
+ const id = record?.id || record?.proposal?.id;
52
+ if (!id) throw new Error('record id required');
53
+ const now = new Date().toISOString();
54
+ const { projectDir: _omit, ...rest } = record;
55
+ const payload = {
56
+ schemaVersion: IMPROVEMENT_STORE_SCHEMA_VERSION,
57
+ ...rest,
58
+ id,
59
+ updatedAt: now,
60
+ submittedAt: record.submittedAt || now,
61
+ };
62
+ fs.writeFileSync(proposalRecordPath(projectDir, id), JSON.stringify(payload, null, 2), 'utf8');
63
+ return payload;
64
+ }
65
+
66
+ export function loadRecord(projectDir, id) {
67
+ const file = proposalRecordPath(projectDir, id);
68
+ if (!fs.existsSync(file)) return null;
69
+ try {
70
+ return JSON.parse(fs.readFileSync(file, 'utf8'));
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ export function listRecords(projectDir, { state = null } = {}) {
77
+ const dir = proposalsDir(projectDir);
78
+ if (!fs.existsSync(dir)) return [];
79
+ const out = [];
80
+ for (const name of fs.readdirSync(dir)) {
81
+ if (!name.endsWith('.json')) continue;
82
+ const record = loadRecord(projectDir, name.replace(/\.json$/, ''));
83
+ if (!record) continue;
84
+ if (state && record.proposal?.state !== state) continue;
85
+ out.push(record);
86
+ }
87
+ out.sort((a, b) => String(b.updatedAt || '').localeCompare(String(a.updatedAt || '')));
88
+ return out;
89
+ }