@geraldmaron/construct 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/README.md +30 -18
  2. package/bin/construct +392 -91
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +112 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/audit-skills.mjs +4 -0
  20. package/lib/audit-specialists.mjs +285 -0
  21. package/lib/audit-trail.mjs +77 -10
  22. package/lib/auto-docs.mjs +87 -18
  23. package/lib/beads/drift.mjs +27 -6
  24. package/lib/beads-client.mjs +49 -121
  25. package/lib/beads-optimistic.mjs +5 -12
  26. package/lib/brand-fonts.mjs +93 -0
  27. package/lib/brand-prose.mjs +214 -0
  28. package/lib/brand-tokens.mjs +92 -0
  29. package/lib/bridges/copilot-proxy.mjs +13 -26
  30. package/lib/capability-ledger.mjs +156 -0
  31. package/lib/certification/artifact-fixtures.mjs +132 -0
  32. package/lib/certification/artifact-gates.mjs +63 -0
  33. package/lib/certification/artifact-provenance.mjs +97 -0
  34. package/lib/certification/canonical-scenarios.mjs +78 -0
  35. package/lib/certification/cli.mjs +191 -0
  36. package/lib/certification/dashboard-api.mjs +71 -0
  37. package/lib/certification/demo-parity.mjs +116 -0
  38. package/lib/certification/document-io-fixtures.mjs +246 -0
  39. package/lib/certification/document-workflow.mjs +97 -0
  40. package/lib/certification/eval-bridge.mjs +77 -0
  41. package/lib/certification/model-routing.mjs +149 -0
  42. package/lib/certification/prompt-budget.mjs +119 -0
  43. package/lib/certification/rc-gate.mjs +206 -0
  44. package/lib/certification/real-llm-scenarios.mjs +303 -0
  45. package/lib/certification/role-cards.mjs +113 -0
  46. package/lib/certification/role-overlays.mjs +117 -0
  47. package/lib/certification/run.mjs +122 -0
  48. package/lib/certification/runner.mjs +323 -0
  49. package/lib/certification/scenarios.mjs +51 -0
  50. package/lib/certification/skill-inventory.mjs +289 -0
  51. package/lib/certification/skill-scenarios.mjs +147 -0
  52. package/lib/certification/specialist-contracts.mjs +85 -0
  53. package/lib/certification/specialist-scenarios.mjs +175 -0
  54. package/lib/certification/stale-impact.mjs +146 -0
  55. package/lib/certification/status.mjs +252 -0
  56. package/lib/certification/store.mjs +77 -0
  57. package/lib/chat/cli.mjs +333 -0
  58. package/lib/chat/command-suggest.mjs +161 -0
  59. package/lib/chat/commands.mjs +215 -0
  60. package/lib/chat/config.mjs +142 -0
  61. package/lib/chat/context-compactor.mjs +250 -0
  62. package/lib/chat/context-continuation.mjs +253 -0
  63. package/lib/chat/continuation-source.mjs +58 -0
  64. package/lib/chat/demo-guide.mjs +61 -0
  65. package/lib/chat/design-tokens.mjs +91 -0
  66. package/lib/chat/desktop-binary.mjs +79 -0
  67. package/lib/chat/desktop-build.mjs +130 -0
  68. package/lib/chat/desktop-launcher.mjs +133 -0
  69. package/lib/chat/evidence.mjs +145 -0
  70. package/lib/chat/export.mjs +74 -0
  71. package/lib/chat/harness/driver.mjs +91 -0
  72. package/lib/chat/list-picker.mjs +112 -0
  73. package/lib/chat/model-picker.mjs +356 -0
  74. package/lib/chat/openrouter-fallback.mjs +151 -0
  75. package/lib/chat/permission-prompt.mjs +33 -0
  76. package/lib/chat/picker-catalog.mjs +45 -0
  77. package/lib/chat/policy-telemetry.mjs +34 -0
  78. package/lib/chat/present.mjs +245 -0
  79. package/lib/chat/session-context.mjs +39 -0
  80. package/lib/chat/session-persist.mjs +73 -0
  81. package/lib/chat/session-restore.mjs +71 -0
  82. package/lib/chat/session-settings.mjs +53 -0
  83. package/lib/chat/system-prompt.mjs +52 -0
  84. package/lib/chat/transparency.mjs +93 -0
  85. package/lib/chat/tui/color-scheme.mjs +42 -0
  86. package/lib/chat/tui/markdown.mjs +123 -0
  87. package/lib/chat/tui/presentation.mjs +100 -0
  88. package/lib/chat/tui/render.mjs +500 -0
  89. package/lib/chat/tui/turn-block.mjs +284 -0
  90. package/lib/chat/tui/turn-present.mjs +18 -0
  91. package/lib/chat/tui/turn-state.mjs +88 -0
  92. package/lib/chat/tui/usage.mjs +122 -0
  93. package/lib/chat/web-commands.mjs +146 -0
  94. package/lib/chat/web-launcher.mjs +63 -0
  95. package/lib/chat/web-picker-keys.mjs +46 -0
  96. package/lib/chat/web-session.mjs +159 -0
  97. package/lib/cli-commands.mjs +232 -11
  98. package/lib/comment-lint.mjs +6 -4
  99. package/lib/config/schema.mjs +36 -5
  100. package/lib/contract-schemas/decision.json +50 -0
  101. package/lib/contract-schemas/implementation.json +51 -0
  102. package/lib/contract-schemas/review-report.json +32 -0
  103. package/lib/contract-schemas/test-report.json +43 -0
  104. package/lib/contracts/construct-handoff.mjs +60 -0
  105. package/lib/contracts/validate.mjs +32 -3
  106. package/lib/contracts/violation-log.mjs +58 -5
  107. package/lib/dashboard-demo.mjs +71 -0
  108. package/lib/dashboard-static.mjs +19 -5
  109. package/lib/decisions/registry.mjs +5 -3
  110. package/lib/deck-export-pptx.mjs +1152 -0
  111. package/lib/demo-recording.mjs +142 -0
  112. package/lib/demo-script.mjs +114 -0
  113. package/lib/demo-surface.mjs +249 -0
  114. package/lib/demo.mjs +703 -0
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +302 -0
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +4 -2
  119. package/lib/doctor/project-adapters.mjs +44 -0
  120. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  121. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  122. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  123. package/lib/doctor/watchers/service-health.mjs +1 -1
  124. package/lib/document-export.mjs +360 -36
  125. package/lib/document-extract.mjs +88 -0
  126. package/lib/document-ingest.mjs +32 -18
  127. package/lib/embed/cli.mjs +2 -2
  128. package/lib/embed/daemon.mjs +18 -46
  129. package/lib/embed/semantic.mjs +5 -2
  130. package/lib/embed/worker.mjs +2 -2
  131. package/lib/embedded-contract/model-resolve.mjs +10 -9
  132. package/lib/env-config.mjs +26 -0
  133. package/lib/evals/dataset.mjs +137 -0
  134. package/lib/evals/gates.mjs +175 -0
  135. package/lib/graph/build-co-change.mjs +54 -0
  136. package/lib/graph/build-from-registry.mjs +136 -0
  137. package/lib/graph/build-import-graph.mjs +153 -0
  138. package/lib/graph/cli.mjs +110 -0
  139. package/lib/graph/impact-cli.mjs +89 -0
  140. package/lib/graph/impact.mjs +108 -0
  141. package/lib/graph/staleness.mjs +44 -0
  142. package/lib/graph/store.mjs +172 -0
  143. package/lib/handoffs/cleanup.mjs +1 -1
  144. package/lib/health-check.mjs +90 -76
  145. package/lib/hooks/artifact-release-gate.mjs +43 -0
  146. package/lib/hooks/audit-trail.mjs +14 -28
  147. package/lib/hooks/brand-prose-lint.mjs +38 -0
  148. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  149. package/lib/hooks/session-optimize.mjs +84 -207
  150. package/lib/hooks/session-start.mjs +4 -5
  151. package/lib/host-disposition.mjs +7 -0
  152. package/lib/improvement/cli.mjs +189 -0
  153. package/lib/improvement/controller.mjs +137 -0
  154. package/lib/improvement/proposal.mjs +120 -0
  155. package/lib/improvement/specialist-loop.mjs +192 -0
  156. package/lib/improvement/store.mjs +89 -0
  157. package/lib/improvement/surface.mjs +219 -0
  158. package/lib/ingest-tooling.mjs +97 -0
  159. package/lib/init/doc-lanes.mjs +165 -0
  160. package/lib/init-docs.mjs +31 -178
  161. package/lib/init-unified.mjs +27 -97
  162. package/lib/init-update-guide.mjs +102 -0
  163. package/lib/init-update.mjs +32 -1
  164. package/lib/init.mjs +8 -0
  165. package/lib/install/desktop-binary-download.mjs +85 -0
  166. package/lib/install/legacy-global-cleanup.mjs +189 -0
  167. package/lib/intake/daemon.mjs +2 -0
  168. package/lib/intake/git-queue.mjs +9 -8
  169. package/lib/intake/queue.mjs +2 -1
  170. package/lib/intake/session-prelude.mjs +90 -1
  171. package/lib/libreoffice-export.mjs +97 -0
  172. package/lib/logging/rotate.mjs +9 -0
  173. package/lib/maintenance/docker-reclaim.mjs +206 -0
  174. package/lib/mcp/external-schema-cost.mjs +74 -0
  175. package/lib/mcp/server.mjs +84 -9
  176. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  177. package/lib/mcp/tool-budget.mjs +55 -4
  178. package/lib/mcp/tools/document.mjs +18 -4
  179. package/lib/mcp/tools/skills.mjs +32 -3
  180. package/lib/mcp/tools/workflow.mjs +13 -1
  181. package/lib/model-free-selector.mjs +18 -0
  182. package/lib/model-registry.mjs +13 -229
  183. package/lib/model-router.mjs +425 -93
  184. package/lib/models/behavior-matrix.mjs +289 -0
  185. package/lib/models/catalog.mjs +209 -0
  186. package/lib/models/execution-capability-profile.mjs +196 -0
  187. package/lib/models/execution-policy.mjs +307 -0
  188. package/lib/models/provider-poll.mjs +383 -0
  189. package/lib/npm-spawn-env.mjs +17 -0
  190. package/lib/ollama/installed-models.mjs +129 -0
  191. package/lib/oracle/actions.mjs +187 -0
  192. package/lib/oracle/artifact-gate.mjs +99 -0
  193. package/lib/oracle/cli.mjs +204 -0
  194. package/lib/oracle/daemon-entry.mjs +14 -0
  195. package/lib/oracle/dispatch.mjs +81 -0
  196. package/lib/oracle/execute.mjs +143 -0
  197. package/lib/oracle/gaps.mjs +76 -0
  198. package/lib/oracle/index.mjs +84 -0
  199. package/lib/oracle/issues.mjs +164 -0
  200. package/lib/oracle/org-graph.mjs +170 -0
  201. package/lib/oracle/policy.mjs +80 -0
  202. package/lib/oracle/read-model.mjs +398 -0
  203. package/lib/oracle/reconcile.mjs +191 -0
  204. package/lib/oracle/routing.mjs +89 -0
  205. package/lib/oracle/synthesize.mjs +384 -0
  206. package/lib/oracle/verdicts.mjs +51 -0
  207. package/lib/orchestration/worker.mjs +88 -27
  208. package/lib/orchestration-policy.mjs +50 -0
  209. package/lib/parity.mjs +96 -2
  210. package/lib/persona-sections.mjs +66 -0
  211. package/lib/playwright-demo.mjs +252 -0
  212. package/lib/project-init-shared.mjs +4 -1
  213. package/lib/prompt-composer.js +9 -4
  214. package/lib/prompt-validation-contract.mjs +24 -0
  215. package/lib/provider-capabilities.js +57 -11
  216. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  217. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  218. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  219. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  220. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  221. package/lib/providers/contract/contract-tests.mjs +57 -0
  222. package/lib/providers/contract/errors.mjs +48 -0
  223. package/lib/providers/contract/interface.mjs +50 -0
  224. package/lib/providers/contract/registry.mjs +102 -0
  225. package/lib/providers/copilot-auth.mjs +297 -0
  226. package/lib/providers/credential-bootstrap.mjs +178 -0
  227. package/lib/providers/credential-catalog.mjs +46 -0
  228. package/lib/providers/credential-sources.mjs +63 -0
  229. package/lib/providers/creds.mjs +5 -2
  230. package/lib/providers/op-run.mjs +59 -0
  231. package/lib/providers/secret-resolver.mjs +159 -0
  232. package/lib/publish-template.mjs +163 -0
  233. package/lib/publish-tooling.mjs +119 -0
  234. package/lib/publish.mjs +305 -0
  235. package/lib/registry/cli.mjs +82 -0
  236. package/lib/registry/consolidation.mjs +147 -0
  237. package/lib/registry/generate-docs.mjs +75 -0
  238. package/lib/registry/skill-verification.mjs +57 -0
  239. package/lib/registry/surface-map.mjs +76 -0
  240. package/lib/registry/validate.mjs +135 -0
  241. package/lib/resources/budget.mjs +82 -0
  242. package/lib/resources/process-budget.mjs +45 -0
  243. package/lib/rules-delivery.mjs +9 -2
  244. package/lib/rules-read.mjs +26 -0
  245. package/lib/runtime-env.mjs +23 -0
  246. package/lib/runtime-pressure.mjs +51 -7
  247. package/lib/schema-infer.mjs +13 -2
  248. package/lib/server/chat-loop.mjs +622 -0
  249. package/lib/server/demo-preview.mjs +63 -0
  250. package/lib/server/index.mjs +229 -8
  251. package/lib/server/langfuse-login.mjs +3 -3
  252. package/lib/service-manager.mjs +61 -9
  253. package/lib/session-store.mjs +1 -1
  254. package/lib/setup.mjs +102 -2
  255. package/lib/specialists/prompt-schema.mjs +19 -10
  256. package/lib/specialists/roster.mjs +43 -0
  257. package/lib/specialists/scaffold.mjs +56 -0
  258. package/lib/storage/backend.mjs +6 -6
  259. package/lib/storage/embeddings-local.mjs +6 -3
  260. package/lib/storage/file-lock.mjs +18 -11
  261. package/lib/storage/hybrid-query.mjs +7 -4
  262. package/lib/storage/state-source.mjs +5 -5
  263. package/lib/storage/sync.mjs +2 -3
  264. package/lib/telemetry/rule-calls.mjs +52 -0
  265. package/lib/template-registry.mjs +2 -2
  266. package/lib/templates/visual-requirements.mjs +27 -51
  267. package/lib/test-corpus-inventory.mjs +313 -0
  268. package/lib/uninstall/uninstall.mjs +19 -7
  269. package/lib/update.mjs +12 -0
  270. package/lib/upgrade.mjs +14 -0
  271. package/lib/wireframe.mjs +20 -14
  272. package/lib/worker/run.mjs +17 -4
  273. package/lib/worker/trace.mjs +11 -3
  274. package/package.json +29 -13
  275. package/personas/construct.md +13 -13
  276. package/platforms/claude/settings.template.json +36 -0
  277. package/rules/common/patterns.md +1 -1
  278. package/rules/common/release-gates.md +4 -3
  279. package/scripts/sync-specialists.mjs +187 -60
  280. package/skills/devops/data-engineering.md +1 -1
  281. package/skills/docs/adr-workflow.md +1 -0
  282. package/skills/docs/backlog-proposal-workflow.md +1 -0
  283. package/skills/docs/codebase-research-workflow.md +40 -0
  284. package/skills/docs/customer-profile-workflow.md +1 -0
  285. package/skills/docs/document-ingest-workflow.md +1 -0
  286. package/skills/docs/evidence-ingest-workflow.md +1 -0
  287. package/skills/docs/init-docs.md +2 -2
  288. package/skills/docs/init-project.md +8 -3
  289. package/skills/docs/prd-workflow.md +24 -1
  290. package/skills/docs/prfaq-workflow.md +1 -0
  291. package/skills/docs/product-intelligence-workflow.md +1 -0
  292. package/skills/docs/product-signal-workflow.md +1 -0
  293. package/skills/docs/research-workflow.md +54 -37
  294. package/skills/docs/runbook-workflow.md +1 -0
  295. package/skills/docs/strategy-workflow.md +1 -0
  296. package/skills/docs/user-research-workflow.md +40 -0
  297. package/skills/operating/orchestration-reference.md +1 -1
  298. package/skills/roles/architect.md +5 -0
  299. package/skills/roles/operator.docs.md +4 -0
  300. package/skills/routing.md +4 -2
  301. package/specialists/artifact-manifest.json +480 -0
  302. package/specialists/artifact-manifest.schema.json +53 -0
  303. package/specialists/audit-enrichments.json +454 -0
  304. package/specialists/contracts.json +37 -25
  305. package/specialists/prompts/_shared/validation-contract.md +26 -0
  306. package/specialists/prompts/cx-accessibility.md +21 -2
  307. package/specialists/prompts/cx-ai-engineer.md +24 -1
  308. package/specialists/prompts/cx-architect.md +4 -1
  309. package/specialists/prompts/cx-business-strategist.md +23 -2
  310. package/specialists/prompts/cx-data-analyst.md +22 -1
  311. package/specialists/prompts/cx-data-engineer.md +23 -2
  312. package/specialists/prompts/cx-debugger.md +21 -2
  313. package/specialists/prompts/cx-designer.md +26 -3
  314. package/specialists/prompts/cx-devil-advocate.md +19 -2
  315. package/specialists/prompts/cx-docs-keeper.md +28 -1
  316. package/specialists/prompts/cx-engineer.md +22 -1
  317. package/specialists/prompts/cx-evaluator.md +18 -1
  318. package/specialists/prompts/cx-explorer.md +21 -2
  319. package/specialists/prompts/cx-legal-compliance.md +21 -2
  320. package/specialists/prompts/cx-operations.md +21 -2
  321. package/specialists/prompts/cx-oracle.md +94 -0
  322. package/specialists/prompts/cx-orchestrator.md +20 -1
  323. package/specialists/prompts/cx-platform-engineer.md +25 -2
  324. package/specialists/prompts/cx-product-manager.md +32 -1
  325. package/specialists/prompts/cx-qa.md +24 -2
  326. package/specialists/prompts/cx-rd-lead.md +23 -2
  327. package/specialists/prompts/cx-release-manager.md +23 -2
  328. package/specialists/prompts/cx-researcher.md +22 -2
  329. package/specialists/prompts/cx-reviewer.md +18 -1
  330. package/specialists/prompts/cx-security.md +22 -2
  331. package/specialists/prompts/cx-sre.md +30 -3
  332. package/specialists/prompts/cx-test-automation.md +7 -1
  333. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  334. package/specialists/prompts/cx-ux-researcher.md +21 -2
  335. package/specialists/registry.json +52 -173
  336. package/specialists/tone-profiles.json +42 -0
  337. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  338. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  339. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  340. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  341. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  342. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  343. package/templates/demos/vhs/construct-cockpit.json +24 -0
  344. package/templates/distribution/construct-brand.typ +446 -0
  345. package/templates/distribution/construct-decision.typ +38 -0
  346. package/templates/distribution/construct-deck.html +95 -0
  347. package/templates/distribution/construct-pdf.typ +38 -0
  348. package/templates/distribution/construct-prd.typ +38 -0
  349. package/templates/distribution/construct-research.typ +38 -0
  350. package/templates/distribution/construct-web.html +92 -0
  351. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  352. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  353. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  354. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  355. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  356. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  357. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  358. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  359. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  360. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  361. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  362. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  363. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  364. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  365. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  366. package/templates/distribution/fonts/README.md +36 -0
  367. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  368. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  369. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  370. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  371. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  372. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  373. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  374. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  375. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  376. package/templates/distribution/icons/activity.svg +15 -0
  377. package/templates/distribution/icons/alert-triangle.svg +17 -0
  378. package/templates/distribution/icons/book-open.svg +16 -0
  379. package/templates/distribution/icons/bot.svg +20 -0
  380. package/templates/distribution/icons/brain.svg +22 -0
  381. package/templates/distribution/icons/circle-check.svg +16 -0
  382. package/templates/distribution/icons/clipboard-check.svg +17 -0
  383. package/templates/distribution/icons/cpu.svg +28 -0
  384. package/templates/distribution/icons/database.svg +17 -0
  385. package/templates/distribution/icons/eye.svg +16 -0
  386. package/templates/distribution/icons/file-text.svg +19 -0
  387. package/templates/distribution/icons/gauge.svg +16 -0
  388. package/templates/distribution/icons/git-branch.svg +17 -0
  389. package/templates/distribution/icons/key.svg +17 -0
  390. package/templates/distribution/icons/layers.svg +17 -0
  391. package/templates/distribution/icons/list-checks.svg +19 -0
  392. package/templates/distribution/icons/lock.svg +16 -0
  393. package/templates/distribution/icons/message-square.svg +15 -0
  394. package/templates/distribution/icons/network.svg +19 -0
  395. package/templates/distribution/icons/route.svg +17 -0
  396. package/templates/distribution/icons/scale.svg +19 -0
  397. package/templates/distribution/icons/search.svg +16 -0
  398. package/templates/distribution/icons/send.svg +16 -0
  399. package/templates/distribution/icons/server.svg +18 -0
  400. package/templates/distribution/icons/shield-check.svg +16 -0
  401. package/templates/distribution/icons/users.svg +18 -0
  402. package/templates/distribution/icons/webhook.svg +17 -0
  403. package/templates/distribution/icons/workflow.svg +17 -0
  404. package/templates/distribution/icons/wrench.svg +15 -0
  405. package/templates/distribution/run.mjs +18 -1
  406. package/templates/docs/adr.md +7 -0
  407. package/templates/docs/construct_guide.md +10 -10
  408. package/templates/docs/customer-profile.md +4 -0
  409. package/templates/docs/one-pager.md +4 -0
  410. package/templates/docs/postmortem.md +31 -0
  411. package/templates/docs/prd-platform.md +19 -0
  412. package/templates/docs/prd.md +15 -0
  413. package/templates/docs/prfaq.md +7 -0
  414. package/templates/docs/qa-strategy.md +103 -0
  415. package/templates/docs/research-brief.md +8 -0
  416. package/templates/docs/rfc-platform.md +12 -0
  417. package/templates/docs/test-plan.md +7 -0
  418. package/lib/ingest/chunker.mjs +0 -94
  419. package/lib/ingest/pipeline.mjs +0 -53
  420. package/lib/ingest/store.mjs +0 -82
  421. package/lib/mode-commands.mjs +0 -122
  422. package/lib/policy/unified-gates.mjs +0 -96
  423. package/lib/profiles/validate-custom.mjs +0 -114
  424. package/lib/services/telemetry-backend.mjs +0 -177
  425. package/lib/storage/fusion.mjs +0 -95
@@ -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
+ }
@@ -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
+ }