@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,80 @@
1
+ /**
2
+ * lib/oracle/policy.mjs — bounded-auto policy for Oracle recommended actions.
3
+ *
4
+ * Classifies each action kind as auto (execute without approval), approve
5
+ * (queue for human review), auto-raise (beads create for high gaps), or deny.
6
+ * Hygiene and meta gaps are verdict-only — they surface in doctor, prelude, and
7
+ * construct oracle gaps but never auto-raise tracker beads.
8
+ */
9
+
10
+ export const AUTO_ACTIONS = new Set([
11
+ 'census-run',
12
+ 'registry-validate',
13
+ 'adapters-sync',
14
+ ]);
15
+
16
+ export const APPROVE_ACTIONS = new Set([
17
+ 'specialist-review',
18
+ 'doctor-followup',
19
+ 'trace-review',
20
+ 'outcomes-aggregate',
21
+ 'parity-fix-manual',
22
+ 'registry-consolidation',
23
+ 'executive-signoff-required',
24
+ 'structure-cleanup-proposal',
25
+ ]);
26
+
27
+ export const VERDICT_ONLY_GAP_IDS = new Set([
28
+ 'beads-hygiene',
29
+ 'workflow-misaligned',
30
+ 'propagation-stale',
31
+ 'census-stale',
32
+ 'outcomes-missing',
33
+ ]);
34
+
35
+ const DENY_ACTIONS = new Set([
36
+ 'git-push',
37
+ 'git-commit',
38
+ 'destructive-delete',
39
+ 'force-sync',
40
+ ]);
41
+
42
+ /**
43
+ * @param {string} kind — recommended action kind from synthesizeVerdict
44
+ * @returns {'auto'|'approve'|'deny'}
45
+ */
46
+ export function classifyAction(kind) {
47
+ if (!kind || typeof kind !== 'string') return 'deny';
48
+ if (DENY_ACTIONS.has(kind)) return 'deny';
49
+ if (AUTO_ACTIONS.has(kind)) return 'auto';
50
+ if (APPROVE_ACTIONS.has(kind)) return 'approve';
51
+ return 'approve';
52
+ }
53
+
54
+ /**
55
+ * Whether high-severity gaps should auto-raise beads (idempotent).
56
+ */
57
+ export function autoRaiseEnabled(env = process.env) {
58
+ if (env.CONSTRUCT_ORACLE === 'off' || env.CONSTRUCT_ORACLE === '0') return false;
59
+ if (env.CONSTRUCT_ORACLE_AUTO_RAISE === 'off' || env.CONSTRUCT_ORACLE_AUTO_RAISE === '0') return false;
60
+ return true;
61
+ }
62
+
63
+ /**
64
+ * @param {{ id?: string }} gap
65
+ * @returns {boolean}
66
+ */
67
+ export function isVerdictOnlyGap(gap) {
68
+ return VERDICT_ONLY_GAP_IDS.has(gap?.id);
69
+ }
70
+
71
+ /**
72
+ * @param {{ id?: string, severity?: string }} gap
73
+ * @param {NodeJS.ProcessEnv} [env]
74
+ * @returns {boolean}
75
+ */
76
+ export function autoRaiseEnabledForGap(gap, env = process.env) {
77
+ if (!autoRaiseEnabled(env)) return false;
78
+ if (isVerdictOnlyGap(gap)) return false;
79
+ return gap?.severity === 'high';
80
+ }
@@ -0,0 +1,398 @@
1
+ /**
2
+ * lib/oracle/read-model.mjs — deterministic signal collector for the Oracle
3
+ * meta-controller read model.
4
+ *
5
+ * Aggregates project-scoped observations, outcomes, contract violations,
6
+ * doctor audit lines, alignment census, org graph, dead code, beads hygiene,
7
+ * and adapter parity into one snapshot suitable for synthesis. Never throws —
8
+ * missing paths yield empty sections.
9
+ */
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { spawnSync } from 'node:child_process';
14
+
15
+ import { checkProjectParity } from '../parity.mjs';
16
+ import { validateCapabilityRegistry } from '../registry/validate.mjs';
17
+ import { isConstructPackageRepo } from '../host-disposition.mjs';
18
+ import { detectExistingContent } from '../init/detect-existing-structure.mjs';
19
+ import { collectOrgGraph } from './org-graph.mjs';
20
+ import { collectArtifactGateSignals } from './artifact-gate.mjs';
21
+ import { loadGraph, nodesByType, dependentsOf } from '../graph/store.mjs';
22
+ import { hashFiles } from '../graph/build-from-registry.mjs';
23
+ import { readViolationSupersedeCutoff } from '../contracts/violation-log.mjs';
24
+
25
+ const RECENT_MS = 24 * 60 * 60 * 1000;
26
+ const DOCTOR_LIMIT = 50;
27
+ const VIOLATION_LIMIT = 100;
28
+ const CENSUS_STALE_MS = 7 * 24 * 60 * 60 * 1000;
29
+
30
+ function readJsonSafe(filePath) {
31
+ try {
32
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ function readRecentJsonl(filePath, { since = 0, limit = 200 } = {}) {
39
+ if (!fs.existsSync(filePath)) return [];
40
+ try {
41
+ const lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
42
+ const out = [];
43
+ for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {
44
+ let row;
45
+ try { row = JSON.parse(lines[i]); } catch { continue; }
46
+ const ts = row.ts ?? row.timestamp ?? (row.createdAt ? Date.parse(row.createdAt) : 0);
47
+ if (since && ts < since) break;
48
+ out.push(row);
49
+ }
50
+ return out.reverse();
51
+ } catch {
52
+ return [];
53
+ }
54
+ }
55
+
56
+ function collectObservations(projectDir) {
57
+ const dir = path.join(projectDir, '.cx', 'observations');
58
+ if (!fs.existsSync(dir)) {
59
+ return { present: false, count: 0, indexCount: 0, recent: [] };
60
+ }
61
+
62
+ const indexPath = path.join(dir, 'index.json');
63
+ const index = readJsonSafe(indexPath);
64
+ const indexEntries = Array.isArray(index) ? index : (index?.entries ?? []);
65
+
66
+ let fileCount = 0;
67
+ try {
68
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
69
+ if (entry.isFile() && entry.name.endsWith('.json') && entry.name !== 'index.json') fileCount++;
70
+ }
71
+ } catch { /* ignore */ }
72
+
73
+ const cutoff = Date.now() - RECENT_MS;
74
+ const recent = [];
75
+ for (const item of indexEntries) {
76
+ const ts = item.timestamp || item.ts || item.createdAt;
77
+ if (!ts || Date.parse(ts) < cutoff) continue;
78
+ recent.push({
79
+ id: item.id,
80
+ role: item.role ?? null,
81
+ category: item.category ?? null,
82
+ summary: String(item.summary ?? '').slice(0, 240),
83
+ ts,
84
+ });
85
+ if (recent.length >= 25) break;
86
+ }
87
+
88
+ return {
89
+ present: true,
90
+ count: fileCount,
91
+ indexCount: indexEntries.length,
92
+ recent,
93
+ };
94
+ }
95
+
96
+ function collectOutcomesSummary(projectDir) {
97
+ const file = path.join(projectDir, '.cx', 'outcomes', '_summary.json');
98
+ const data = readJsonSafe(file);
99
+ if (!data) return { present: false, roles: {} };
100
+ return {
101
+ present: true,
102
+ generatedAt: data.generatedAt ?? null,
103
+ roles: data.roles ?? {},
104
+ };
105
+ }
106
+
107
+ function collectContractViolations(projectDir) {
108
+ const file = path.join(projectDir, '.cx', 'contract-violations.jsonl');
109
+ const since = Date.now() - RECENT_MS;
110
+ const supersedeCutoff = readViolationSupersedeCutoff(projectDir);
111
+ const recent = readRecentJsonl(file, { since, limit: VIOLATION_LIMIT })
112
+ .filter((row) => {
113
+ if (!row?.ts) return false;
114
+ if (supersedeCutoff != null && new Date(row.ts).getTime() <= supersedeCutoff) return false;
115
+ return true;
116
+ });
117
+ return {
118
+ present: fs.existsSync(file),
119
+ recentCount: recent.length,
120
+ recent: recent.map((r) => ({
121
+ ts: r.ts,
122
+ contractId: r.contractId ?? null,
123
+ agent: r.agent ?? null,
124
+ verdict: r.verdict ?? 'CONTRACT_VIOLATION',
125
+ direction: r.direction ?? null,
126
+ })),
127
+ };
128
+ }
129
+
130
+ function collectDoctorLog(homeDir) {
131
+ const file = path.join(homeDir, '.cx', 'doctor-log.jsonl');
132
+ const since = Date.now() - RECENT_MS;
133
+ const recent = readRecentJsonl(file, { since, limit: DOCTOR_LIMIT });
134
+ return {
135
+ present: fs.existsSync(file),
136
+ recentCount: recent.length,
137
+ recent: recent.map((r) => ({
138
+ ts: r.ts,
139
+ kind: r.kind ?? null,
140
+ watcher: r.watcher ?? null,
141
+ action: r.action ?? null,
142
+ result: r.result ?? null,
143
+ summary: String(r.summary ?? '').slice(0, 240),
144
+ })),
145
+ };
146
+ }
147
+
148
+ function collectAlignmentCensus(rootDir) {
149
+ const file = path.join(rootDir, 'audit-artifacts', 'alignment-census.json');
150
+ const data = readJsonSafe(file);
151
+ if (!data) return { present: false };
152
+ const generatedAt = data.generatedAt ?? data.ts ?? null;
153
+ const ageMs = generatedAt ? Date.now() - Date.parse(generatedAt) : Infinity;
154
+ return {
155
+ present: true,
156
+ generatedAt,
157
+ ageMs: Number.isFinite(ageMs) ? ageMs : null,
158
+ stale: Number.isFinite(ageMs) && ageMs > CENSUS_STALE_MS,
159
+ summary: data.summary ?? null,
160
+ dimensions: data.dimensions ?? null,
161
+ counts: data.counts ?? null,
162
+ audit: {
163
+ findingsCount: data.audit?.findingsCount ?? (data.audit?.findings?.length ?? 0),
164
+ findings: (data.audit?.findings ?? []).slice(0, 20),
165
+ regressions: data.audit?.ratchet?.regressions ?? [],
166
+ },
167
+ rootLayout: data.rootLayout ?? null,
168
+ skills: {
169
+ trueOrphanCount: data.skills?.trueOrphanCount ?? 0,
170
+ composerReachableCount: data.skills?.composerReachableCount ?? null,
171
+ pass: data.skills?.pass ?? null,
172
+ },
173
+ };
174
+ }
175
+
176
+ function collectRegistryValidate(rootDir) {
177
+ try {
178
+ const report = validateCapabilityRegistry({ rootDir });
179
+ return {
180
+ valid: report.valid,
181
+ errorCount: report.errors?.length ?? 0,
182
+ warningCount: report.warnings?.length ?? 0,
183
+ warnings: (report.warnings ?? []).slice(0, 10),
184
+ needsRun: !report.valid || (report.warnings?.length ?? 0) > 0,
185
+ };
186
+ } catch (err) {
187
+ return { valid: false, errorCount: 1, warningCount: 0, needsRun: true, error: err.message };
188
+ }
189
+ }
190
+
191
+ function collectBeadsOpenCount(projectDir) {
192
+ try {
193
+ const result = spawnSync('bd', ['list', '--json', '--status=open'], {
194
+ cwd: projectDir,
195
+ encoding: 'utf8',
196
+ stdio: ['ignore', 'pipe', 'pipe'],
197
+ });
198
+ if (result.status !== 0) return { present: false, openCount: 0 };
199
+ const parsed = JSON.parse(result.stdout || '[]');
200
+ const issues = Array.isArray(parsed) ? parsed : (parsed.issues ?? []);
201
+ return { present: true, openCount: issues.length };
202
+ } catch {
203
+ return { present: false, openCount: 0 };
204
+ }
205
+ }
206
+
207
+ function collectHookFailures(homeDir) {
208
+ const file = path.join(homeDir, '.cx', 'doctor-log.jsonl');
209
+ const since = Date.now() - RECENT_MS;
210
+ const recent = readRecentJsonl(file, { since, limit: DOCTOR_LIMIT });
211
+ const failures = recent.filter((r) =>
212
+ (r.kind === 'hook' || String(r.watcher ?? '').includes('hook'))
213
+ && (r.result === 'failed' || r.result === 'error'),
214
+ );
215
+ return { count: failures.length, recent: failures.slice(0, 10) };
216
+ }
217
+
218
+ function collectStructureSprawl(projectDir) {
219
+ try {
220
+ const detection = detectExistingContent(projectDir);
221
+ const laneCount = Object.keys(detection.existingLanes ?? {}).length;
222
+ const duplicateLanes = Object.entries(detection.existingLanes ?? {})
223
+ .filter(([, entries]) => (entries?.length ?? 0) > 1)
224
+ .map(([lane]) => lane);
225
+ return {
226
+ present: laneCount > 0 || !!detection.customIntake?.ingestScript,
227
+ laneCount,
228
+ duplicateLanes,
229
+ customIntake: detection.customIntake ?? null,
230
+ rootTemplates: detection.rootTemplates ?? null,
231
+ };
232
+ } catch {
233
+ return { present: false, laneCount: 0, duplicateLanes: [] };
234
+ }
235
+ }
236
+
237
+ const GRAPH_SEED_FILES = [
238
+ 'registry/capabilities.json',
239
+ 'specialists/contracts.json',
240
+ 'lib/embedded-contract/workflow-defs.mjs',
241
+ ];
242
+
243
+ // The living dependency matrix: file↔capability↔workflow↔test graph at
244
+ // .cx/graph/. Surfaces three oversight signals — staleness (seeds changed
245
+ // since last build), coverage (capabilities/workflows with no impl or test
246
+ // edge), and freshness (a capability whose realizing files changed after its
247
+ // lastValidated stamp). Never throws; an absent graph yields present:false.
248
+
249
+ function collectDependencyGraph(rootDir, projectDir) {
250
+ const graph = loadGraph(projectDir);
251
+ if (!graph.exists) {
252
+ return { present: false, applicable: isConstructPackageRepo(projectDir) };
253
+ }
254
+
255
+ let stale = false;
256
+ let staleReason = null;
257
+ try {
258
+ const current = hashFiles(rootDir, GRAPH_SEED_FILES);
259
+ if (graph.meta?.sourceHash && current !== graph.meta.sourceHash) {
260
+ stale = true;
261
+ staleReason = 'registry/contracts/workflow seeds changed since last build';
262
+ }
263
+ } catch { /* hashing best-effort */ }
264
+
265
+ const capabilitiesWithoutTest = [];
266
+ const capabilitiesWithoutImpl = [];
267
+ for (const cap of nodesByType(graph, 'capability')) {
268
+ if (dependentsOf(graph, cap.id, 'validates').length === 0) capabilitiesWithoutTest.push(cap.id.slice('capability:'.length));
269
+ if (dependentsOf(graph, cap.id, 'realizes').length === 0) capabilitiesWithoutImpl.push(cap.id.slice('capability:'.length));
270
+ }
271
+ const workflowsUncovered = nodesByType(graph, 'workflow')
272
+ .filter((wf) => dependentsOf(graph, wf.id, 'embeds').length === 0)
273
+ .map((wf) => wf.id.slice('workflow:'.length));
274
+ const orphanFileCount = nodesByType(graph, 'file')
275
+ .filter((f) => (graph.out.get(f.id) || []).every((e) => e.rel !== 'realizes')).length;
276
+
277
+ const untested = [];
278
+ for (const cap of nodesByType(graph, 'capability')) {
279
+ const lastValidated = cap.attrs?.lastValidated;
280
+ if (!lastValidated) continue;
281
+ const stamp = Date.parse(lastValidated);
282
+ if (!Number.isFinite(stamp)) continue;
283
+ const realizers = dependentsOf(graph, cap.id, 'realizes').map((id) => id.slice('file:'.length));
284
+ let changed = 0;
285
+ for (const rel of realizers) {
286
+ try {
287
+ if (fs.statSync(path.join(rootDir, rel)).mtimeMs > stamp) changed++;
288
+ } catch { /* file gone — coverage handles it */ }
289
+ }
290
+ if (changed > 0) untested.push({ capability: cap.id.slice('capability:'.length), changedFiles: changed, lastValidated });
291
+ }
292
+
293
+ return {
294
+ present: true,
295
+ applicable: true,
296
+ generatedAt: graph.meta?.generatedAt ?? null,
297
+ sourceHash: graph.meta?.sourceHash ?? null,
298
+ stale,
299
+ staleReason,
300
+ nodeCount: graph.nodes.size,
301
+ edgeCount: graph.edges.length,
302
+ nodesByType: graph.meta?.nodesByType ?? {},
303
+ edgesByRel: graph.meta?.edgesByRel ?? {},
304
+ coverage: { capabilitiesWithoutTest, capabilitiesWithoutImpl, workflowsUncovered, orphanFileCount },
305
+ untested,
306
+ };
307
+ }
308
+
309
+ function collectDeadCodeSync(rootDir, projectDir) {
310
+ if (!isConstructPackageRepo(projectDir)) {
311
+ return { present: false, skipped: true, deadCount: 0, regressions: [] };
312
+ }
313
+ return { present: false, pendingAsync: true, deadCount: 0, regressions: [] };
314
+ }
315
+
316
+ /**
317
+ * Collect the Oracle read model from durable Construct signals.
318
+ *
319
+ * @param {object} opts
320
+ * @param {string} opts.rootDir — Construct package root (census, parity registry)
321
+ * @param {string} opts.projectDir — active project root (.cx/ signals)
322
+ * @param {string} opts.homeDir — user home (~/.cx/doctor-log.jsonl)
323
+ */
324
+ export function collectReadModel({ rootDir, projectDir, homeDir }) {
325
+ const collectedAt = new Date().toISOString();
326
+ let parity;
327
+ try {
328
+ parity = checkProjectParity({ rootDir, projectDir });
329
+ } catch (err) {
330
+ parity = { ok: false, skipped: false, error: err.message, surfaces: [], summary: [`parity check failed: ${err.message}`] };
331
+ }
332
+
333
+ const registryValidate = collectRegistryValidate(rootDir);
334
+ const orgGraph = collectOrgGraph({ rootDir, projectDir, parity, registryValidate });
335
+ const artifactGate = collectArtifactGateSignals({ rootDir, projectDir });
336
+
337
+ return {
338
+ collectedAt,
339
+ rootDir,
340
+ projectDir,
341
+ homeDir,
342
+ observations: collectObservations(projectDir),
343
+ outcomes: collectOutcomesSummary(projectDir),
344
+ contractViolations: collectContractViolations(projectDir),
345
+ doctorLog: collectDoctorLog(homeDir),
346
+ alignmentCensus: collectAlignmentCensus(rootDir),
347
+ registryValidate,
348
+ parity,
349
+ orgGraph,
350
+ artifactGate,
351
+ beads: collectBeadsOpenCount(projectDir),
352
+ hookFailures: collectHookFailures(homeDir),
353
+ structure: collectStructureSprawl(projectDir),
354
+ deadCode: collectDeadCodeSync(rootDir, projectDir),
355
+ dependencyGraph: collectDependencyGraph(rootDir, projectDir),
356
+ };
357
+ }
358
+
359
+ /**
360
+ * Async enrichment for beads drift and dead-code audit (tool repo only).
361
+ */
362
+ export async function enrichReadModel(model) {
363
+ let beads = { ...model.beads };
364
+ try {
365
+ const { detectBeadsDrift } = await import('../beads/drift.mjs');
366
+ const drift = detectBeadsDrift();
367
+ beads = {
368
+ ...beads,
369
+ stuckInProgress: drift.counts?.stuckInProgress ?? 0,
370
+ staleOpen: drift.counts?.staleOpen ?? 0,
371
+ mergeDrift: drift.counts?.mergeDrift ?? 0,
372
+ totalIssues: drift.totalIssues ?? 0,
373
+ };
374
+ } catch { /* bd unavailable */ }
375
+
376
+ let deadCode = model.deadCode;
377
+ if (isConstructPackageRepo(model.projectDir)) {
378
+ try {
379
+ const { deadcodeFindings } = await import('../../scripts/audit/02-deadcode.mjs');
380
+ const findings = deadcodeFindings();
381
+ const baselinePath = path.join(model.rootDir, 'scripts', 'audit', 'baseline.json');
382
+ const baseline = readJsonSafe(baselinePath) ?? { acceptedIds: [] };
383
+ const accepted = new Set(baseline.acceptedIds ?? []);
384
+ const regressions = findings.filter((f) => f.type === 'dead-module' && !accepted.has(f.target));
385
+ deadCode = {
386
+ present: true,
387
+ deadCount: findings.filter((f) => f.type === 'dead-module').length,
388
+ testOnlyCount: findings.filter((f) => f.type === 'module-test-only').length,
389
+ regressions: regressions.map((f) => f.target),
390
+ findings: findings.slice(0, 15),
391
+ };
392
+ } catch {
393
+ deadCode = { present: false, deadCount: 0, regressions: [] };
394
+ }
395
+ }
396
+
397
+ return { ...model, beads, deadCode };
398
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * lib/oracle/reconcile.mjs — one-time cleanup for duplicate oracle hygiene beads.
3
+ *
4
+ * Closes duplicate open [oracle/beads-hygiene|workflow-misaligned] beads,
5
+ * keeping the newest per gapId, and prunes stale raised-issues records.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ import { runBd } from '../beads-client.mjs';
12
+ import { markContractViolationsSuperseded, recentViolations } from '../contracts/violation-log.mjs';
13
+ import { VERDICT_ONLY_GAP_IDS } from './policy.mjs';
14
+ import { oracleBeadTitlePrefix } from './issues.mjs';
15
+
16
+ const RAISED_FILE = 'raised-issues.jsonl';
17
+ const RECONCILE_GAP_IDS = [...VERDICT_ONLY_GAP_IDS].filter((id) =>
18
+ id === 'beads-hygiene' || id === 'workflow-misaligned',
19
+ );
20
+ const OPEN_STATUSES = ['open', 'in_progress'];
21
+
22
+ function parseBdList(output) {
23
+ try {
24
+ const parsed = JSON.parse(String(output ?? ''));
25
+ if (Array.isArray(parsed)) return parsed;
26
+ if (Array.isArray(parsed?.issues)) return parsed.issues;
27
+ } catch { /* empty */ }
28
+ return [];
29
+ }
30
+
31
+ function beadUpdatedAt(bead) {
32
+ const raw = bead?.updated || bead?.updatedAt || bead?.created || bead?.createdAt;
33
+ const ts = Date.parse(String(raw ?? ''));
34
+ return Number.isFinite(ts) ? ts : 0;
35
+ }
36
+
37
+ /**
38
+ * @returns {Promise<Array<{ id: string, title: string, gapId: string, status: string, updatedAt: number }>>}
39
+ */
40
+ export async function listOpenOracleHygieneBeads() {
41
+ const found = [];
42
+ for (const status of OPEN_STATUSES) {
43
+ const result = await runBd(['list', '--json', `--status=${status}`], {
44
+ actor: 'oracle-reconcile',
45
+ silent: true,
46
+ timeoutSeconds: 20,
47
+ commandTimeoutSeconds: 40,
48
+ });
49
+ if (!result.success) continue;
50
+ for (const bead of parseBdList(result.output)) {
51
+ const title = String(bead?.title ?? '');
52
+ for (const gapId of RECONCILE_GAP_IDS) {
53
+ if (title.startsWith(oracleBeadTitlePrefix(gapId))) {
54
+ found.push({
55
+ id: bead.id,
56
+ title,
57
+ gapId,
58
+ status,
59
+ updatedAt: beadUpdatedAt(bead),
60
+ });
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return found;
66
+ }
67
+
68
+ /**
69
+ * @param {Array<{ id: string, gapId: string, updatedAt: number }>} beads
70
+ * @returns {{ keep: Map<string, string>, close: string[] }}
71
+ */
72
+ export function planHygieneReconcile(beads) {
73
+ if (!beads.length) return { keep: new Map(), close: [] };
74
+ return {
75
+ keep: new Map(),
76
+ close: beads.map((bead) => bead.id),
77
+ };
78
+ }
79
+
80
+ function pruneRaisedIssues(projectDir, { dryRun = false } = {}) {
81
+ const file = path.join(projectDir, '.cx', 'oracle', RAISED_FILE);
82
+ if (!fs.existsSync(file)) return { pruned: 0 };
83
+ const rows = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map((line) => {
84
+ try { return JSON.parse(line); } catch { return null; }
85
+ }).filter(Boolean);
86
+ const kept = rows.filter((r) => !RECONCILE_GAP_IDS.includes(r.gapId));
87
+ const pruned = rows.length - kept.length;
88
+ if (!dryRun && pruned > 0) {
89
+ fs.writeFileSync(file, kept.map((r) => JSON.stringify(r)).join('\n') + (kept.length ? '\n' : ''), 'utf8');
90
+ }
91
+ return { pruned };
92
+ }
93
+
94
+ const BARE_GOAL_FIELD_LABELS = ['intent', 'workCategory', 'riskFlags', 'acceptanceCriteria'];
95
+
96
+ function missingBareGoalConstructFields(missing) {
97
+ if (!Array.isArray(missing) || missing.length !== BARE_GOAL_FIELD_LABELS.length) return false;
98
+ return BARE_GOAL_FIELD_LABELS.every((field) =>
99
+ missing.some((entry) => String(entry).includes(field)),
100
+ );
101
+ }
102
+
103
+ function isBareGoalConstructViolation(row) {
104
+ if (row?.contractId !== 'construct-to-orchestrator') return false;
105
+ if (!missingBareGoalConstructFields(row.missing)) return false;
106
+ const keys = row.packet_keys ?? row.packetKeys;
107
+ return Array.isArray(keys) && keys.length === 1 && keys[0] === 'goal';
108
+ }
109
+
110
+ /**
111
+ * @param {string} projectDir
112
+ * @returns {{ shouldSupersede: boolean, recentCount: number, reason: string }}
113
+ */
114
+ export function planContractViolationSupersede(projectDir) {
115
+ const recent = recentViolations({ repoRoot: projectDir });
116
+ if (!recent.length) {
117
+ return { shouldSupersede: false, recentCount: 0, reason: 'no recent contract violations' };
118
+ }
119
+ const allBareGoal = recent.every(isBareGoalConstructViolation);
120
+ if (!allBareGoal) {
121
+ return {
122
+ shouldSupersede: false,
123
+ recentCount: recent.length,
124
+ reason: 'recent violations include non-bare-goal construct-to-orchestrator or other contracts',
125
+ };
126
+ }
127
+ return {
128
+ shouldSupersede: true,
129
+ recentCount: recent.length,
130
+ reason: 'historical bare-goal construct-to-orchestrator violations superseded after handoff enrichment fix',
131
+ };
132
+ }
133
+
134
+ /**
135
+ * @param {object} opts
136
+ * @param {string} opts.projectDir
137
+ * @param {boolean} [opts.dryRun]
138
+ */
139
+ export async function reconcileContractViolationHygiene({ projectDir, dryRun = false }) {
140
+ const plan = planContractViolationSupersede(projectDir);
141
+ if (!plan.shouldSupersede) {
142
+ return { ok: true, dryRun, superseded: false, ...plan };
143
+ }
144
+ if (dryRun) {
145
+ return { ok: true, dryRun, superseded: false, wouldSupersede: true, ...plan };
146
+ }
147
+ const marker = markContractViolationsSuperseded({
148
+ repoRoot: projectDir,
149
+ reason: plan.reason,
150
+ });
151
+ return { ok: true, dryRun, superseded: true, cutoff: marker.cutoff, ...plan };
152
+ }
153
+
154
+ /**
155
+ * @param {object} opts
156
+ * @param {string} opts.projectDir
157
+ * @param {boolean} [opts.dryRun]
158
+ */
159
+ export async function reconcileOracleHygieneBeads({ projectDir, dryRun = false }) {
160
+ const beads = await listOpenOracleHygieneBeads();
161
+ const { keep, close } = planHygieneReconcile(beads);
162
+ const closed = [];
163
+ const reason = 'Oracle verdict-only policy: hygiene gaps no longer auto-raise beads; duplicate closed by reconcile.';
164
+
165
+ if (!dryRun) {
166
+ for (const beadId of close) {
167
+ const result = await runBd(['close', beadId, '--reason', reason], {
168
+ actor: 'oracle-reconcile',
169
+ silent: true,
170
+ timeoutSeconds: 15,
171
+ commandTimeoutSeconds: 30,
172
+ });
173
+ if (result.success) closed.push(beadId);
174
+ }
175
+ }
176
+
177
+ const prune = pruneRaisedIssues(projectDir, { dryRun });
178
+ const contractViolations = await reconcileContractViolationHygiene({ projectDir, dryRun });
179
+
180
+ return {
181
+ ok: true,
182
+ dryRun,
183
+ found: beads.length,
184
+ kept: Object.fromEntries(keep),
185
+ plannedClose: close,
186
+ closed: dryRun ? [] : closed,
187
+ raisedPruned: prune.pruned,
188
+ contractViolations,
189
+ reason,
190
+ };
191
+ }