@geraldmaron/construct 1.4.1 → 1.5.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 (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
package/lib/validator.mjs CHANGED
@@ -7,9 +7,9 @@
7
7
  * before they mutate persistent state.
8
8
  */
9
9
  import { loadRegistry } from './registry/loader.mjs';
10
- import { fileURLToPath, pathToFileURL } from 'node:url';
11
10
  import { existsSync, readFileSync } from 'node:fs';
12
- import { join, dirname } from 'node:path';
11
+ import { join } from 'node:path';
12
+ import { packageRoot, isMainModule } from './roots.mjs';
13
13
 
14
14
  const VALID_TIERS = new Set(['reasoning', 'standard', 'fast']);
15
15
  const VALID_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
@@ -54,7 +54,7 @@ function validateStringArray(errors, value, label, validValues = null) {
54
54
 
55
55
  export function validateRegistry(reg, options = {}) {
56
56
  const errors = [];
57
- const rootDir = options.rootDir ?? join(__dirname, '..');
57
+ const rootDir = options.rootDir ?? packageRoot;
58
58
 
59
59
  // Handle unified registry format (v2) - convert specialists from object to array
60
60
  // and extract orchestrator from specialists
@@ -252,9 +252,7 @@ export function validateRegistry(reg, options = {}) {
252
252
  return { valid: errors.length === 0, errors, summary };
253
253
  }
254
254
 
255
- const __dirname = dirname(fileURLToPath(import.meta.url));
256
-
257
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
255
+ if (isMainModule(import.meta.url)) {
258
256
  const reg = loadRegistry();
259
257
  const result = validateRegistry(reg);
260
258
  if (result.valid) {
@@ -29,6 +29,7 @@ import { FilesystemTaskGraphStore } from '../task-graph/store.mjs';
29
29
  import { runJob } from './run.mjs';
30
30
  import { evidenceFromJobResult, recordEvidence, blockedPacket } from './evidence.mjs';
31
31
  import { emitTraceEvent, newTraceId } from './trace.mjs';
32
+ import { isMainModule } from '../roots.mjs';
32
33
 
33
34
  const DEFAULT_POLL_INTERVAL_MS = 2_000;
34
35
  const DEFAULT_IDLE_TIMEOUT_SECONDS = 300;
@@ -211,7 +212,7 @@ export async function runWorkerLoop({
211
212
  }
212
213
 
213
214
  // CLI entrypoint when invoked directly (the Dockerfile.worker CMD).
214
- const invokedDirectly = import.meta.url === `file://${process.argv[1]}`;
215
+ const invokedDirectly = isMainModule(import.meta.url);
215
216
  if (invokedDirectly) {
216
217
  const args = parseArgs(process.argv);
217
218
  const workspace = args.workspace || process.env.CONSTRUCT_WORKSPACE || process.cwd();
@@ -28,12 +28,12 @@ import path from 'node:path';
28
28
 
29
29
  import { emitTraceEvent, newTraceId, newSpanId } from './trace.mjs';
30
30
  import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
31
+ import { resolveStateDir } from '../state-root.mjs';
31
32
 
32
33
  const DEFAULT_TIMEOUT_SECONDS = 300;
33
- const ARTIFACTS_SUBDIR = '.cx/runtime/worker';
34
34
 
35
35
  function artifactsDir(rootDir) {
36
- return path.join(rootDir, ARTIFACTS_SUBDIR);
36
+ return resolveStateDir(rootDir, 'runtime', 'worker');
37
37
  }
38
38
 
39
39
  function buildRestrictedEnv(allowedKeys = []) {
@@ -5,8 +5,9 @@
5
5
  * job → evidence record together via traceId + spanId. Events land in two
6
6
  * places concurrently:
7
7
  *
8
- * 1. `.cx/traces/<YYYY-MM-DD>.jsonl` append-only local JSONL. Always on,
9
- * no credentials required. Tail live, archive, or replay.
8
+ * 1. `<stateRoot>/traces/<YYYY-MM-DD>.jsonl` (ADR-0066: machine-scoped, not
9
+ * project-local) append-only local JSONL. Always on, no credentials
10
+ * required. Tail live, archive, or replay.
10
11
  * 2. Optional remote export — selected by CONSTRUCT_TRACE_BACKEND. Each
11
12
  * unique traceId triggers one trace-create plus an event-create per
12
13
  * emitted event. Failures never throw because observability must not
@@ -26,16 +27,13 @@
26
27
  * memory.written — durable memory record persisted
27
28
  */
28
29
 
29
- import { existsSync, mkdirSync } from 'node:fs';
30
30
  import { randomBytes } from 'node:crypto';
31
31
  import path from 'node:path';
32
32
 
33
33
  import { createTelemetryClient } from '../telemetry/client.mjs';
34
- import { ensureCxDir } from '../project-init-shared.mjs';
35
34
  import { appendWithRotationSync } from '../logging/rotate.mjs';
36
35
  import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
37
-
38
- const TRACE_SUBDIR = '.cx/traces';
36
+ import { resolveStateDir } from '../state-root.mjs';
39
37
 
40
38
  // GitHub rejects single files > 100 MB. Keep every trace shard strictly
41
39
  // under 100 MB so an accidental commit + push doesn't brick the repo. Cap
@@ -69,7 +67,7 @@ export const TRACE_EVENT_TYPES = [
69
67
  ];
70
68
 
71
69
  export function traceDir(rootDir) {
72
- return path.join(rootDir, TRACE_SUBDIR);
70
+ return resolveStateDir(rootDir, 'traces');
73
71
  }
74
72
 
75
73
  function todayShard() {
@@ -166,10 +164,6 @@ export function emitTraceEvent({
166
164
  }
167
165
 
168
166
  const dir = traceDir(rootDir);
169
- if (!existsSync(dir)) {
170
- ensureCxDir(rootDir);
171
- mkdirSync(dir, { recursive: true });
172
- }
173
167
 
174
168
  const event = {
175
169
  traceId: traceId || newTraceId(),
@@ -11,6 +11,7 @@ import fs from "node:fs";
11
11
  import path from "node:path";
12
12
  import { execSync } from "node:child_process";
13
13
  import { routeRequest } from "./orchestration-policy.mjs";
14
+ import { isMainModule } from './roots.mjs';
14
15
 
15
16
  const PHASES = ["research", "plan", "implement", "validate", "operate"];
16
17
  const VALID_STATUS = new Set(["todo", "in-progress", "blocked", "blocked_needs_user", "blocked_needs_executive", "done", "skipped"]);
@@ -55,6 +56,42 @@ function now() {
55
56
  return new Date().toISOString();
56
57
  }
57
58
 
59
+ // rules/common/no-fabrication.md requires evidence a reader can re-verify.
60
+ // A bare non-empty string ("did stuff") tells nobody how to check it, so the
61
+ // done-gate below must look past mere presence to a re-verifiable shape: a
62
+ // runnable command, a file path, a URL, a test reference, or a named
63
+ // specialist's structured verdict (the "cx-reviewer: APPROVED" / "cx-qa: 42
64
+ // tests passing" attestation format this workflow already standardizes on —
65
+ // see tests/concierge-routing.test.mjs).
66
+
67
+ const VERIFICATION_URL_RE = /https?:\/\/\S+/i;
68
+ const VERIFICATION_FILE_RE = /[\w.-]*\/[\w./-]+\.\w+|\b[\w-]+\.(mjs|cjs|js|ts|tsx|jsx|py|rb|go|java|json|ya?ml|sh|md)\b/i;
69
+ const VERIFICATION_COMMAND_RE = /\b(npm|yarn|pnpm|npx|node|python3?|pytest|go test|make|cargo|mvn|gradle|rspec|jest|vitest|bd)\b\s+\S/i;
70
+ const VERIFICATION_TEST_RE = /\b[\w-]+\.(test|spec)\.\w+\b|\btest\(['"]/i;
71
+ const VERIFICATION_ATTESTATION_RE = /\b(cx-[\w-]+|reviewer|qa|security|tester)\s*:\s*(APPROVED|PASS(?:ED)?|FAIL(?:ED)?|BLOCKED|REJECTED|\d+\s*(?:tests?|assertions?|%|percent))/i;
72
+
73
+ function isReverifiableEvidence(entry) {
74
+ const text = typeof entry === "string" ? entry : entry == null ? "" : JSON.stringify(entry);
75
+ if (!text.trim()) return false;
76
+ return (
77
+ VERIFICATION_URL_RE.test(text) ||
78
+ VERIFICATION_FILE_RE.test(text) ||
79
+ VERIFICATION_COMMAND_RE.test(text) ||
80
+ VERIFICATION_TEST_RE.test(text) ||
81
+ VERIFICATION_ATTESTATION_RE.test(text)
82
+ );
83
+ }
84
+
85
+ function hasReverifiableEvidence(entries) {
86
+ return Array.isArray(entries) && entries.some(isReverifiableEvidence);
87
+ }
88
+
89
+ // The done-gate applies to every phase where "done" claims real work occurred
90
+ // (research/plan tasks close on written artifacts the workflow already tracks
91
+ // elsewhere, not on independently re-verifiable evidence).
92
+
93
+ const VERIFICATION_GATED_PHASES = new Set(["implement", "validate", "operate"]);
94
+
58
95
  function slugify(value) {
59
96
  return String(value || "workflow")
60
97
  .toLowerCase()
@@ -220,7 +257,7 @@ export function buildTaskPacketFromIntent(request, options = {}) {
220
257
  const defaults = {
221
258
  title: options.title || request || "User-requested task",
222
259
  phase: options.phase || (route.intent === "research" ? "research" : route.intent === "fix" ? "implement" : route.intent === "evaluation" ? "validate" : route.intent === "implementation" ? "implement" : "implement"),
223
- owner: options.owner || (route.workCategory === "analysis" ? "cx-architect" : route.workCategory === "writing" ? "cx-docs-keeper" : route.workCategory === "visual" ? "cx-designer" : route.workCategory === "deep" ? "cx-engineer" : "cx-engineer"),
260
+ owner: options.owner || (route.workCategory === "analysis" ? "cx-architect" : route.workCategory === "writing" ? "cx-operations" : route.workCategory === "visual" ? "cx-designer" : route.workCategory === "deep" ? "cx-engineer" : "cx-engineer"),
224
261
  status: "todo",
225
262
  dependsOn: options.dependsOn || [],
226
263
  files: options.files || [],
@@ -237,7 +274,6 @@ export function buildTaskPacketFromIntent(request, options = {}) {
237
274
  workCategory: route.workCategory,
238
275
  track: route.track,
239
276
  specialists: route.specialists || [],
240
- dispatchPlan: route.dispatchPlan || null,
241
277
  request: String(request || ""),
242
278
  },
243
279
  };
@@ -413,7 +449,7 @@ export function addTasksFromPlan(root, markdown, options = {}) {
413
449
 
414
450
  export function updateTask(root, key, patch) {
415
451
  const workflow = loadWorkflow(root);
416
- if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow init` first.");
452
+ if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow new` first.");
417
453
  const cleanPatch = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined));
418
454
  let found = false;
419
455
  workflow.tasks = (workflow.tasks || []).map((task) => {
@@ -433,14 +469,20 @@ export function updateTask(root, key, patch) {
433
469
  if (cleanPatch.status && !VALID_STATUS.has(cleanPatch.status)) throw new Error(`Invalid status: ${cleanPatch.status}`);
434
470
  if (cleanPatch.status === "done") {
435
471
  const task = workflow.tasks.find((t) => t.key === key);
436
- if (task && task.phase === "implement" && workflow.alignment?.verificationRequiredBeforeDone !== false) {
472
+ if (task && VERIFICATION_GATED_PHASES.has(task.phase) && workflow.alignment?.verificationRequiredBeforeDone !== false) {
437
473
  const mergedVerification = cleanPatch.verification ?? task.verification ?? [];
438
474
  if (mergedVerification.length === 0) {
439
475
  throw new Error(
440
- `Cannot mark implement-phase task "${key}" as done without verification evidence. ` +
476
+ `Cannot mark ${task.phase}-phase task "${key}" as done without verification evidence. ` +
441
477
  `Add cx-reviewer and cx-qa results to task.verification before marking done.`
442
478
  );
443
479
  }
480
+ if (!hasReverifiableEvidence(mergedVerification)) {
481
+ throw new Error(
482
+ `Cannot mark ${task.phase}-phase task "${key}" as done: verification evidence is not re-verifiable. ` +
483
+ `Add a runnable command, file path, URL, test reference, or a cx-reviewer/cx-qa attestation.`
484
+ );
485
+ }
444
486
  }
445
487
  }
446
488
  if (cleanPatch.status === "in-progress" || cleanPatch.status === "blocked_needs_user") workflow.currentTaskKey = key;
@@ -454,7 +496,7 @@ export function transitionPhase(root, phase, status = "in-progress") {
454
496
  phase = normalizePhase(phase);
455
497
  if (!phase) throw new Error(`Invalid phase: ${phase}`);
456
498
  const workflow = loadWorkflow(root);
457
- if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow init` first.");
499
+ if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow new` first.");
458
500
  workflow.phase = phase;
459
501
  workflow.phases = workflow.phases || {};
460
502
  for (const p of PHASES) {
@@ -469,7 +511,7 @@ export function alignmentFindings(workflow) {
469
511
  return [{
470
512
  severity: "HIGH",
471
513
  issue: "No .cx/workflow.json found",
472
- fix: "Run `construct workflow init \"<title>\"` at the project root."
514
+ fix: "Run `construct workflow new \"<title>\"` at the project root."
473
515
  }];
474
516
  }
475
517
  const findings = [];
@@ -503,6 +545,8 @@ export function alignmentFindings(workflow) {
503
545
  }
504
546
  if (task.status === "done" && (!Array.isArray(task.verification) || task.verification.length === 0)) {
505
547
  findings.push({ severity: "HIGH", task: task.key, issue: "Done task has no verification evidence", fix: "Record commands, checks, or review evidence before marking done." });
548
+ } else if (task.status === "done" && Array.isArray(task.verification) && task.verification.length > 0 && VERIFICATION_GATED_PHASES.has(task.phase) && !hasReverifiableEvidence(task.verification)) {
549
+ findings.push({ severity: "HIGH", task: task.key, issue: "Done task's verification evidence is not re-verifiable", fix: "Record a runnable command, file path, URL, test reference, or a cx-reviewer/cx-qa attestation." });
506
550
  }
507
551
  for (const dep of task.dependsOn || []) {
508
552
  const depTask = tasks.find((candidate) => candidate.key === dep);
@@ -780,7 +824,7 @@ export function runWorkflowCli(argv = process.argv.slice(2), root = process.cwd(
780
824
  throw new Error(`Unknown workflow subcommand: ${command}`);
781
825
  }
782
826
 
783
- if (import.meta.url === `file://${process.argv[1]}`) {
827
+ if (isMainModule(import.meta.url)) {
784
828
  try {
785
829
  runWorkflowCli(process.argv.slice(2), process.cwd());
786
830
  } catch (error) {
@@ -0,0 +1,203 @@
1
+ /**
2
+ * lib/workflows/liveness.mjs — workflow manifest liveness validation (LMCP-C11).
3
+ *
4
+ * Schema validation (validate.mjs) covers shape only: required fields, types,
5
+ * compatVersion. A roleChain entry naming a specialist that does not exist, a
6
+ * workflow whose handoff chain loops back on itself, and a workflow no surface
7
+ * can ever invoke all pass schema validation and fail only when a user
8
+ * actually runs the workflow. checkWorkflowLiveness runs four checks per
9
+ * manifest, over the already-loaded manifest list and the pack-aware merged
10
+ * specialist registry (LMCP-E1 precedence: project > pack > builtin, via
11
+ * loadAllPacks):
12
+ *
13
+ * 1. roleChain resolution — every role resolves to an installed specialist.
14
+ * 2. skill existence — every skill the resolved specialists declare
15
+ * exists on disk (the concrete capability a
16
+ * resolved roleChain entry brings with it).
17
+ * 3. acyclic handoff graph — the roleChain's sequential handoffs
18
+ * (chain[i] -> chain[i+1]) never loop back to
19
+ * an earlier role in the same workflow.
20
+ * 4. surface/mode reachability — every workflow declares at least one
21
+ * surface and one mode, so some entrypoint can
22
+ * reach it.
23
+ *
24
+ * Returns { violations } without throwing and without classifying severity.
25
+ * Every violation — a bad roleChain entry, a missing skill file, a handoff
26
+ * cycle, or an unreachable workflow — is reported as a plain message; the
27
+ * caller (lib/graph/validate.mjs) applies mode policy uniformly, matching
28
+ * the existing C2 convention: liveness reports facts, the graph validator
29
+ * classifies error vs warning per deployment mode.
30
+ */
31
+
32
+ import { existsSync, readFileSync } from 'node:fs';
33
+ import { dirname, join, resolve } from 'node:path';
34
+ import { fileURLToPath } from 'node:url';
35
+
36
+ import { loadAllPacks } from '../packs/loader.mjs';
37
+
38
+ const __dirname = dirname(fileURLToPath(import.meta.url));
39
+
40
+ /** Root of the Construct package (parent of lib/); skills/ lives here, not under an arbitrary project rootDir. */
41
+ const PACKAGE_ROOT = resolve(__dirname, '..', '..');
42
+
43
+ /**
44
+ * Build the set of installed specialist ids (pack-aware, E1 precedence) and a
45
+ * lookup from bare role id ("architect") to specialist skills, by reading the
46
+ * merged pack list's specialists/org directories directly. loadAllPacks()
47
+ * already applies builtin < user < project precedence; the core pack's
48
+ * specialists come from specialists/org/specialists on rootDir.
49
+ *
50
+ * @param {{ rootDir?: string, packRoots?: string[] }} opts
51
+ * @returns {{ specialistIds: Set<string>, skillsByRole: Map<string, string[]> }}
52
+ */
53
+ function resolveSpecialistRegistry({ rootDir = process.cwd() } = {}) {
54
+ const { packs } = loadAllPacks({ rootDir });
55
+
56
+ const specialistIds = new Set();
57
+ const skillsByRole = new Map();
58
+
59
+ for (const pack of packs) {
60
+ const ids = Array.isArray(pack.specialists) ? pack.specialists : [];
61
+ for (const id of ids) specialistIds.add(id);
62
+
63
+ const sourceDir = pack._sourceDir || pack._packDir;
64
+ if (!sourceDir) continue;
65
+
66
+ for (const id of ids) {
67
+ const specPath = join(sourceDir, 'specialists', `${id}.json`);
68
+ if (!existsSync(specPath)) continue;
69
+ try {
70
+ const raw = JSON.parse(readFileSync(specPath, 'utf8'));
71
+ const roleId = id.startsWith('cx-') ? id.slice(3) : id;
72
+ skillsByRole.set(roleId, Array.isArray(raw.skills) ? raw.skills : []);
73
+ } catch {
74
+ // Malformed specialist file — resolution failure surfaces separately
75
+ // via roleChain checks when the role is referenced; skip here.
76
+ }
77
+ }
78
+ }
79
+
80
+ return { specialistIds, skillsByRole };
81
+ }
82
+
83
+ /**
84
+ * Normalize a roleChain entry to the registry-prefixed specialist id.
85
+ * roleChain entries are bare role ids ("architect"); the registry stores
86
+ * "cx-architect". Entries already carrying the prefix pass through.
87
+ *
88
+ * @param {string} role
89
+ * @returns {string}
90
+ */
91
+ function specialistIdFor(role) {
92
+ return role.startsWith('cx-') ? role : `cx-${role}`;
93
+ }
94
+
95
+ /**
96
+ * Detect a cycle in the sequential handoff graph of a roleChain: nodes are
97
+ * distinct roles, edges are chain[i] -> chain[i+1]. A role that reappears
98
+ * later in the same chain after handing off to a different role creates a
99
+ * back-edge, forming a cycle (e.g. [a, b, a] => a->b, b->a).
100
+ *
101
+ * @param {string[]} roleChain
102
+ * @returns {boolean}
103
+ */
104
+ function hasHandoffCycle(roleChain) {
105
+ const edges = new Map();
106
+ for (let i = 0; i < roleChain.length - 1; i++) {
107
+ const from = roleChain[i];
108
+ const to = roleChain[i + 1];
109
+ if (from === to) continue;
110
+ if (!edges.has(from)) edges.set(from, new Set());
111
+ edges.get(from).add(to);
112
+ }
113
+
114
+ const WHITE = 0, GRAY = 1, BLACK = 2;
115
+ const color = new Map();
116
+ for (const node of roleChain) if (!color.has(node)) color.set(node, WHITE);
117
+
118
+ function visit(node) {
119
+ color.set(node, GRAY);
120
+ for (const next of edges.get(node) || []) {
121
+ const c = color.get(next);
122
+ if (c === GRAY) return true;
123
+ if (c === WHITE && visit(next)) return true;
124
+ }
125
+ color.set(node, BLACK);
126
+ return false;
127
+ }
128
+
129
+ for (const node of color.keys()) {
130
+ if (color.get(node) === WHITE && visit(node)) return true;
131
+ }
132
+ return false;
133
+ }
134
+
135
+ /**
136
+ * checkWorkflowLiveness(manifests, opts)
137
+ *
138
+ * Runs the four liveness checks over an already-loaded, already
139
+ * schema-validated manifest list. Manifests missing roleChain/surfaces/modes
140
+ * entirely are skipped for the checks that need them — schema validation
141
+ * already flags genuinely malformed manifests; liveness only adds checks
142
+ * schema validation cannot express.
143
+ *
144
+ * @param {object[]} manifests - Loaded manifests, each carrying `_filePath`.
145
+ * @param {{ rootDir?: string, packRoots?: string[], packageRoot?: string }} [opts]
146
+ * rootDir selects the project whose pack tiers are merged (specialist
147
+ * resolution); packageRoot (defaults to the Construct install) is where
148
+ * skills/ is read from for the skill-existence check.
149
+ * @returns {{ violations: string[] }}
150
+ */
151
+ export function checkWorkflowLiveness(manifests, opts = {}) {
152
+ const violations = [];
153
+
154
+ const { specialistIds, skillsByRole } = resolveSpecialistRegistry(opts);
155
+
156
+ for (const manifest of manifests) {
157
+ // Embed manifests are a workflow specialization scheduled by the embed
158
+ // daemon, not reached through a surface+mode entrypoint or a role chain —
159
+ // their liveness is enforced by the embed-capability loader, so the
160
+ // executable-workflow liveness checks below do not apply.
161
+ if (manifest.type === 'embed') continue;
162
+
163
+ const label = manifest._filePath || manifest.id || '(unknown manifest)';
164
+ const roleChain = Array.isArray(manifest.roleChain) ? manifest.roleChain : [];
165
+
166
+ // Check 1: roleChain resolution against the pack-aware merged registry.
167
+ for (const role of roleChain) {
168
+ const specId = specialistIdFor(role);
169
+ if (!specialistIds.has(specId)) {
170
+ violations.push(`${label}: roleChain entry '${role}' does not resolve to an installed specialist (expected '${specId}')`);
171
+ }
172
+ }
173
+
174
+ // Check 2: skill existence for every resolved role's declared skills.
175
+ // skills/ is a package-level resource (shared across projects), so it
176
+ // resolves against packageRoot, not the project rootDir.
177
+ const packageRoot = opts.packageRoot || PACKAGE_ROOT;
178
+ for (const role of roleChain) {
179
+ const skills = skillsByRole.get(role);
180
+ if (!skills) continue;
181
+ for (const skillId of skills) {
182
+ const skillPath = join(packageRoot, 'skills', `${skillId}.md`);
183
+ if (!existsSync(skillPath)) {
184
+ violations.push(`${label}: role '${role}' declares skill '${skillId}' with no matching file at skills/${skillId}.md`);
185
+ }
186
+ }
187
+ }
188
+
189
+ // Check 3: acyclic handoff graph.
190
+ if (roleChain.length > 1 && hasHandoffCycle(roleChain)) {
191
+ violations.push(`${label}: roleChain contains a circular handoff (${roleChain.join(' -> ')})`);
192
+ }
193
+
194
+ // Check 4: reachability from at least one declared surface and mode.
195
+ const surfaces = Array.isArray(manifest.surfaces) ? manifest.surfaces : [];
196
+ const modes = Array.isArray(manifest.modes) ? manifest.modes : [];
197
+ if (surfaces.length === 0 || modes.length === 0) {
198
+ violations.push(`${label}: workflow '${manifest.id}' is unreachable — declares ${surfaces.length === 0 ? 'no surfaces' : `surfaces [${surfaces.join(', ')}]`} and ${modes.length === 0 ? 'no modes' : `modes [${modes.join(', ')}]`}`);
199
+ }
200
+ }
201
+
202
+ return { violations };
203
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * lib/workflows/loader.mjs — workflow manifest directory loader and merger.
3
+ *
4
+ * Three manifest tiers exist: builtin (shipped with Construct in
5
+ * lib/embedded-contract/workflows/), pack (in {pack_root}/workflows/), and
6
+ * project (in .cx/workflows/). Project manifests take highest precedence;
7
+ * pack manifests override builtin.
8
+ *
9
+ * `loadWorkflowManifestsFromDir` reads every *.manifest.json in a directory and
10
+ * validates each one. Errors are collected without throwing. `mergeWorkflowManifests`
11
+ * merges up to three tiers by id. `resolveWorkflowManifestDirs` returns the
12
+ * canonical paths for each tier.
13
+ *
14
+ * Provenance (LMCP-D3): every loaded manifest carries `_source` ('builtin' |
15
+ * 'pack' | 'project') and `_filePath`. When a higher tier overrides a lower
16
+ * tier's id, `mergeWorkflowManifests` records the overridden entries on the
17
+ * winner as `_shadowedBy` so a caller (e.g. `construct graph explain`) can
18
+ * show which source won and what it overrode without re-reading disk.
19
+ */
20
+
21
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
22
+ import { dirname, join, resolve } from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+
25
+ import { validateWorkflowManifest } from './validate.mjs';
26
+
27
+ const __dirname = dirname(fileURLToPath(import.meta.url));
28
+
29
+ /** Root of the Construct package (parent of lib/). */
30
+ const PACKAGE_ROOT = resolve(__dirname, '..', '..');
31
+
32
+ /**
33
+ * loadWorkflowManifestsFromDir(dirPath, { strict } = {})
34
+ *
35
+ * Reads all `*.manifest.json` files in dirPath, validates each one, and
36
+ * returns a result object. Files that fail JSON parsing or validation are
37
+ * collected in `errors`; valid manifests are collected in `manifests`.
38
+ *
39
+ * When `strict` is true, manifests are validated with the strict-mode
40
+ * hardening checks (unknown field rejection).
41
+ *
42
+ * @param {string} dirPath - Absolute path to search.
43
+ * @param {{ strict?: boolean, source?: string }} [opts]
44
+ * @returns {{ manifests: object[], errors: string[] }}
45
+ */
46
+ export function loadWorkflowManifestsFromDir(dirPath, { strict = false, source = 'unknown' } = {}) {
47
+ const manifests = [];
48
+ const errors = [];
49
+
50
+ if (!dirPath || !existsSync(dirPath)) {
51
+ return { manifests, errors };
52
+ }
53
+
54
+ let entries;
55
+ try {
56
+ entries = readdirSync(dirPath, { withFileTypes: true });
57
+ } catch (err) {
58
+ errors.push(`${dirPath}: failed to read directory (${err.message})`);
59
+ return { manifests, errors };
60
+ }
61
+
62
+ const manifestFiles = entries
63
+ .filter((e) => e.isFile() && e.name.endsWith('.manifest.json'))
64
+ .map((e) => join(dirPath, e.name))
65
+ .sort();
66
+
67
+ for (const filePath of manifestFiles) {
68
+ let parsed;
69
+ try {
70
+ parsed = JSON.parse(readFileSync(filePath, 'utf8'));
71
+ } catch (err) {
72
+ errors.push(`${filePath}: failed to parse JSON (${err.message})`);
73
+ continue;
74
+ }
75
+
76
+ const result = validateWorkflowManifest(parsed, { filePath, strict });
77
+ if (!result.valid) {
78
+ errors.push(...result.errors);
79
+ continue;
80
+ }
81
+
82
+ manifests.push({ ...parsed, _filePath: filePath, _source: source });
83
+ }
84
+
85
+ return { manifests, errors };
86
+ }
87
+
88
+ /**
89
+ * mergeWorkflowManifests(builtin, pack, project)
90
+ *
91
+ * Merges three manifest arrays by `id`. Precedence (highest wins): project >
92
+ * pack > builtin. When multiple tiers declare the same id, only the
93
+ * highest-precedence entry is kept.
94
+ *
95
+ * @param {object[]} builtin
96
+ * @param {object[]} pack
97
+ * @param {object[]} project
98
+ * @returns {object[]}
99
+ */
100
+ export function mergeWorkflowManifests(builtin = [], pack = [], project = []) {
101
+ const map = new Map();
102
+ const shadowed = new Map();
103
+
104
+ const layer = (list) => {
105
+ for (const m of list) {
106
+ const existing = map.get(m.id);
107
+ if (existing) {
108
+ const log = shadowed.get(m.id) || [];
109
+ log.push({ source: existing._source || 'unknown', filePath: existing._filePath || null });
110
+ shadowed.set(m.id, log);
111
+ }
112
+ map.set(m.id, m);
113
+ }
114
+ };
115
+
116
+ // Load lowest priority first so higher priority layers overwrite.
117
+ layer(builtin);
118
+ layer(pack);
119
+ layer(project);
120
+
121
+ for (const [id, winner] of map) {
122
+ const log = shadowed.get(id);
123
+ if (log?.length) winner._shadowedBy = log;
124
+ }
125
+
126
+ return [...map.values()];
127
+ }
128
+
129
+ /**
130
+ * resolveWorkflowManifestDirs({ rootDir, packRoots })
131
+ *
132
+ * Returns the canonical directory paths for each workflow manifest tier.
133
+ *
134
+ * @param {{ rootDir?: string, packRoots?: string[] }} opts
135
+ * @returns {{ builtin: string, pack: string[], project: string }}
136
+ */
137
+ export function resolveWorkflowManifestDirs({ rootDir = process.cwd(), packRoots = [] } = {}) {
138
+ return {
139
+ builtin: join(PACKAGE_ROOT, 'lib', 'embedded-contract', 'workflows'),
140
+ pack: (packRoots || []).map((pr) => join(pr, 'workflows')),
141
+ project: join(rootDir, '.cx', 'workflows'),
142
+ };
143
+ }
144
+
145
+ /**
146
+ * loadAllWorkflows(opts)
147
+ *
148
+ * Convenience function that discovers, loads, and merges all workflow manifests
149
+ * across all three tiers. Returns the merged list of manifests and any errors
150
+ * encountered.
151
+ *
152
+ * @param {{ rootDir?: string, packRoots?: string[], strict?: boolean }} opts
153
+ * @returns {{ workflows: object[], errors: string[] }}
154
+ */
155
+ export function loadAllWorkflows(opts = {}) {
156
+ const { rootDir, packRoots = [], strict = false } = opts;
157
+ const errors = [];
158
+
159
+ const dirs = resolveWorkflowManifestDirs({ rootDir, packRoots });
160
+
161
+ const builtin = loadWorkflowManifestsFromDir(dirs.builtin, { strict, source: 'builtin' });
162
+ errors.push(...builtin.errors);
163
+
164
+ const packManifests = [];
165
+ for (const packDir of dirs.pack) {
166
+ const result = loadWorkflowManifestsFromDir(packDir, { strict, source: 'pack' });
167
+ packManifests.push(...result.manifests);
168
+ errors.push(...result.errors);
169
+ }
170
+
171
+ const project = loadWorkflowManifestsFromDir(dirs.project, { strict, source: 'project' });
172
+ errors.push(...project.errors);
173
+
174
+ const workflows = mergeWorkflowManifests(builtin.manifests, packManifests, project.manifests);
175
+
176
+ return { workflows, errors };
177
+ }