@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
@@ -0,0 +1,95 @@
1
+ /**
2
+ * lib/mcp/tool-safety.mjs — per-tool safety classification for the MCP catalog.
3
+ *
4
+ * One entry per tool name in lib/mcp/server.mjs's ALL_TOOL_DEFS (plus the `call`
5
+ * gateway). `class` is the host-facing risk tier; `filesystem`/`network`/`process`
6
+ * name which resource classes the tool can touch, derived from each tool's own
7
+ * description and input schema. withSafetyEnvelope (server.mjs) throws if a tool
8
+ * is missing here, so a newly added tool cannot ship without classification.
9
+ */
10
+
11
+ const READ = 'read';
12
+ const WRITE = 'write';
13
+ const DESTRUCTIVE = 'destructive';
14
+
15
+ export const DEFAULT_OUTPUT_SCHEMA = Object.freeze({ type: 'object' });
16
+
17
+ export const TOOL_SAFETY = Object.freeze({
18
+ agent_health: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
19
+ summarize_diff: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
20
+ scan_file: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
21
+ extract_document_text: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
22
+ ingest_document: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
23
+ infer_document_schema: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'spawn' },
24
+ list_schema_artifacts: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
25
+ storage_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
26
+ storage_sync: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
27
+ storage_reset: { class: DESTRUCTIVE, filesystem: 'write', network: 'none', process: 'none' },
28
+ delete_ingested_artifacts: { class: DESTRUCTIVE, filesystem: 'write', network: 'none', process: 'none' },
29
+ project_context: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
30
+ get_skill: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
31
+ orchestration_policy: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
32
+ list_skills: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
33
+ worker_run: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'spawn' },
34
+ broker_check: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
35
+ agent_contract: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
36
+ find_tool: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
37
+ get_template: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
38
+ list_templates: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
39
+ search_skills: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
40
+ list_teams: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
41
+ suggest_skills: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
42
+ cx_trace: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
43
+ cx_score: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
44
+ cx_trace_update: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
45
+ session_list: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
46
+ session_load: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
47
+ session_search: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
48
+ session_save: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
49
+ memory_search: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
50
+ memory_add_observations: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
51
+ memory_create_entities: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
52
+ memory_recent: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
53
+ rovo_search: { class: READ, filesystem: 'none', network: 'fetch', process: 'none' },
54
+ efficiency_snapshot: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
55
+ session_usage: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
56
+ provider_fetch: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'none' },
57
+ provider_write: { class: DESTRUCTIVE, filesystem: 'write', network: 'fetch', process: 'none' },
58
+ scope_show: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
59
+ scope_list: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
60
+ scope_drafts: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
61
+ scope_health: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
62
+ outcomes_summary: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
63
+ outcomes_record: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
64
+ knowledge_add: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
65
+ scope_create: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
66
+ scope_archive: { class: DESTRUCTIVE, filesystem: 'write', network: 'none', process: 'none' },
67
+ sandbox_list: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
68
+ knowledge_graph_ask: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
69
+ learning_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
70
+ document_export: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
71
+ publish_detect: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
72
+ publish_run: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
73
+ knowledge_search: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
74
+ workflow_init: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
75
+ workflow_add_task: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
76
+ workflow_update_task: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
77
+ workflow_needs_main_input: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
78
+ workflow_validate: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
79
+ workflow_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
80
+ workflow_contract_validate: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
81
+ workflow_import_plan: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
82
+ cx_trace_telemetry: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
83
+ artifact_workflow: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
84
+ author_artifact: { class: WRITE, filesystem: 'write', network: 'none', process: 'none' },
85
+ model_resolve: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
86
+ triage_recommend: { class: READ, filesystem: 'read', network: 'none', process: 'spawn' },
87
+ workflow_invoke: { class: WRITE, filesystem: 'write', network: 'none', process: 'spawn' },
88
+ capability_describe: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
89
+ construct_execution_resolve: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
90
+ orchestration_run: { class: WRITE, filesystem: 'write', network: 'fetch', process: 'none' },
91
+ web_search: { class: READ, filesystem: 'none', network: 'fetch', process: 'none' },
92
+ orchestration_status: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
93
+ orchestration_readiness: { class: READ, filesystem: 'read', network: 'none', process: 'none' },
94
+ call: { class: DESTRUCTIVE, filesystem: 'write', network: 'fetch', process: 'spawn' },
95
+ });
@@ -0,0 +1,60 @@
1
+ /**
2
+ * lib/mcp/tool-surface-parity.mjs — partition invariant for the MCP tool surface.
3
+ *
4
+ * The exposed surface is split into a flat core (CORE_TOOL_NAMES) and a long tail
5
+ * reached through the `call` gateway. Both are hand-maintained beside the catalog
6
+ * (ALL_TOOL_DEFS), so a typo in a core name silently drops a tool from BOTH the
7
+ * flat surface and the gateway enum, making it unreachable. These helpers enforce
8
+ * the partition from the catalog as the single source of truth: every declared
9
+ * core name must be a real catalog tool, and the flat core plus the gateway enum
10
+ * must together cover the catalog exactly once (no gap, no overlap, no phantom).
11
+ */
12
+
13
+ // A core name that is not in the catalog is a typo or a stale entry; it must be
14
+ // rejected loudly rather than silently dropping the tool from every surface.
15
+
16
+ export function assertCoreSubsetOfCatalog(coreNames, catalog) {
17
+ const catalogSet = catalog instanceof Set ? catalog : new Set(catalog);
18
+ const phantom = [...coreNames].filter((name) => !catalogSet.has(name));
19
+ if (phantom.length > 0) {
20
+ throw new Error(
21
+ `tool-surface-parity: ${phantom.length} core tool name(s) absent from the catalog: ${phantom.join(', ')} — `
22
+ + 'a typo in CORE_TOOL_NAMES drops the tool from both the flat surface and the call enum.',
23
+ );
24
+ }
25
+ return true;
26
+ }
27
+
28
+ // The flat core and the gateway enum must partition the catalog exactly once.
29
+ // Returns the partition diagnostics; assertToolSurfacePartition throws on any break.
30
+
31
+ export function checkToolSurfacePartition({ catalog, flat, enumNames }) {
32
+ const catalogSet = catalog instanceof Set ? catalog : new Set(catalog);
33
+ const surfaced = [...flat, ...enumNames];
34
+
35
+ const overlap = flat.filter((name) => enumNames.includes(name));
36
+ const duplicates = [...new Set(surfaced.filter((name, idx) => surfaced.indexOf(name) !== idx))];
37
+ const missing = [...catalogSet].filter((name) => !flat.includes(name) && !enumNames.includes(name));
38
+ const phantom = [...new Set(surfaced.filter((name) => !catalogSet.has(name)))];
39
+
40
+ return {
41
+ ok: overlap.length === 0 && duplicates.length === 0 && missing.length === 0 && phantom.length === 0,
42
+ overlap: overlap.sort(),
43
+ duplicates: duplicates.sort(),
44
+ missing: missing.sort(),
45
+ phantom: phantom.sort(),
46
+ };
47
+ }
48
+
49
+ export function assertToolSurfacePartition({ catalog, flat, enumNames }) {
50
+ const result = checkToolSurfacePartition({ catalog, flat, enumNames });
51
+ if (!result.ok) {
52
+ const parts = [];
53
+ if (result.overlap.length) parts.push(`flat AND enum: ${result.overlap.join(', ')}`);
54
+ if (result.duplicates.length) parts.push(`surfaced twice: ${result.duplicates.join(', ')}`);
55
+ if (result.missing.length) parts.push(`unreachable: ${result.missing.join(', ')}`);
56
+ if (result.phantom.length) parts.push(`not in catalog: ${result.phantom.join(', ')}`);
57
+ throw new Error(`tool-surface-parity: partition broken — ${parts.join('; ')}`);
58
+ }
59
+ return true;
60
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * lib/mcp/tools/orchestration-delegation-next.tool.mjs — self-registered MCP
3
+ * tool (lib/mcp/tool-registry.mjs) that advances an orchestration-policy
4
+ * route's delegation chain by exactly one step (ADR-0067, construct-rf26.9).
5
+ *
6
+ * orchestration_policy classifies a request into intent/track/specialists/
7
+ * gates and — for a non-immediate track — the full contractChain, for a
8
+ * caller to read and self-sequence. This tool is the flow-engine-backed
9
+ * alternative for the SEQUENCING part specifically: it rebuilds the same
10
+ * deterministic route (routeRequest is a pure function of request/fileCount/
11
+ * moduleCount/introducesContract/explicitDrive) and drives its implied
12
+ * delegation flow (lib/orchestration/delegation-flow.mjs) one checkpointed
13
+ * step at a time, returning only the current specialist's delegation — never
14
+ * the rest of the chain. Call it once per specialist, with the same run_id,
15
+ * until `done` is true; state persists across calls (and across process
16
+ * restarts) via lib/flows/checkpoint.mjs under the machine-scoped state root.
17
+ */
18
+
19
+ import { routeRequest } from '../../orchestration-policy.mjs';
20
+ import { advanceDelegation } from '../../orchestration/delegation-flow.mjs';
21
+
22
+ export const TOOL_DEFS = [
23
+ {
24
+ name: 'orchestration_delegation_next',
25
+ description:
26
+ 'Advance a request\'s delegation chain by exactly one step. Pass the same classification inputs '
27
+ + 'you would give orchestration_policy (request/fileCount/moduleCount/introducesContract/explicitDrive) '
28
+ + 'plus a run_id you mint and keep for this dispatch (e.g. a session or task id). The first call starts '
29
+ + 'the chain and returns the first specialist\'s delegation; each subsequent call with the same run_id '
30
+ + 'returns the next one. Never returns more than the current step — act on currentDelegation only, then '
31
+ + 'call again. `done: true` with `currentDelegation: null` means the chain is exhausted (or the route\'s '
32
+ + 'track resolved to no specialists at all, e.g. immediate).',
33
+ inputSchema: {
34
+ type: 'object',
35
+ required: ['request', 'run_id'],
36
+ properties: {
37
+ request: { type: 'string', description: 'The same request text passed to orchestration_policy.' },
38
+ run_id: { type: 'string', description: 'Caller-chosen id for this dispatch. Reuse it across calls to advance the same chain.' },
39
+ fileCount: { type: 'number', description: 'Approximate number of files involved.' },
40
+ moduleCount: { type: 'number', description: 'Approximate number of modules involved.' },
41
+ introducesContract: { type: 'boolean', description: 'Whether the change introduces a new contract/dependency.' },
42
+ explicitDrive: { type: 'boolean', description: 'Whether drive/full-send mode is explicitly active.' },
43
+ },
44
+ },
45
+ outputSchema: { type: 'object' },
46
+ safety: { class: 'write', filesystem: 'write', network: 'none', process: 'none' },
47
+ },
48
+ ];
49
+
50
+ export async function orchestrationDelegationNext(args = {}, { cwd = process.cwd() } = {}) {
51
+ const { request, run_id: runId } = args;
52
+ if (!request || typeof request !== 'string') return { error: 'Missing "request".' };
53
+ if (!runId || typeof runId !== 'string') return { error: 'Missing "run_id" — mint and reuse one id for this dispatch.' };
54
+
55
+ const route = routeRequest({
56
+ request,
57
+ fileCount: args.fileCount ?? 0,
58
+ moduleCount: args.moduleCount ?? 0,
59
+ introducesContract: args.introducesContract ?? false,
60
+ explicitDrive: args.explicitDrive ?? false,
61
+ });
62
+
63
+ return advanceDelegation(cwd, runId, route);
64
+ }
65
+
66
+ export const TOOL_HANDLERS = {
67
+ orchestration_delegation_next: orchestrationDelegationNext,
68
+ };
@@ -13,16 +13,61 @@
13
13
  */
14
14
 
15
15
  import { runOrchestration, startRun, getRun, getRuns } from '../../orchestration/runtime.mjs';
16
+ import { HOST } from '../../orchestration/worker.mjs';
17
+ import { loadProjectConfig } from '../../config/project-config.mjs';
18
+ import { governWebResults } from './web-search-governance.mjs';
19
+ import { resolveNonNegativeSetting } from '../../env-config.mjs';
16
20
 
17
- const TERMINAL = ['completed', 'completed-with-failures', 'cancelled', 'error'];
21
+ // Host loop instructions echoed on an awaiting-host run so the calling agent
22
+ // never has to guess the protocol: execute each materialized prompt as that
23
+ // specialist, submit the result, and follow next_task until it is null.
24
+ // Worded to be un-misreadable as a failure: a VS Code host once read "did not
25
+ // execute" as prepare-only-and-stuck, retried with worker_backend "provider",
26
+ // and died on missing API credits — so the text must lead with success and
27
+ // explicitly forbid that fallback.
28
+
29
+ const HOST_INSTRUCTIONS =
30
+ 'ACTION REQUIRED — this run succeeded and is now waiting on YOU, the calling agent. The host worker '
31
+ + 'backend means Construct materialized each specialist prompt and you execute them in your own '
32
+ + 'session (no API credits are spent). This is the normal path, not a failure: do NOT re-run with '
33
+ + 'worker_backend "provider" (real API spend, needs provider credits) and do not report the run as '
34
+ + 'incomplete. For each task in `tasks`, execute its `system`/`user` prompt yourself as that '
35
+ + 'specialist role, in order, then submit the result by calling orchestration_task_result with '
36
+ + '{ run_id, task_id, output }. Its response carries next_task (the next prompt to execute) or null '
37
+ + 'once the run is terminal — keep submitting until next_task is null, then re-read the run via '
38
+ + 'orchestration_status if you need the final consolidated output.';
39
+
40
+ // Every state a run can rest in. `degraded` and `completed-prepare-only` are
41
+ // terminal too — the poll loop must stop on them or a finished degraded/prepare-only
42
+ // remote run reads as "still executing" until the overall deadline elapses.
43
+
44
+ const TERMINAL = ['completed', 'completed-with-failures', 'completed-prepare-only', 'degraded', 'cancelled', 'error'];
45
+
46
+ export function shapeRun(run) {
47
+ const hasTasks = Array.isArray(run.tasks) && run.tasks.length > 0;
48
+ const allPrepared = hasTasks && run.tasks.every((t) => t.status === 'prepared');
49
+ const degraded = run.degraded ?? run.execution?.degraded ?? false;
50
+
51
+ // Terminal-status taxonomy (construct-fbxv.1): a degraded run never surfaces as a
52
+ // bare 'completed'. All-prepared is the more specific "no specialist executed"
53
+ // signal, so it wins; otherwise any degraded run — whether it prepared zero tasks
54
+ // or executed with a capability gap — reports 'degraded'. Legacy runs persisted as
55
+ // bare 'completed' before this taxonomy are re-derived the same way.
56
+ const shapedStatus = allPrepared
57
+ ? 'completed-prepare-only'
58
+ : degraded && run.status === 'completed'
59
+ ? 'degraded'
60
+ : run.status;
18
61
 
19
- function shapeRun(run) {
20
62
  return {
21
63
  runId: run.runId,
22
- status: run.status,
64
+ status: shapedStatus,
65
+ prepareOnly: allPrepared,
66
+ runMode: run.runMode ?? null,
67
+ semantics: run.semantics ?? null,
23
68
  executionMode: run.execution?.executionMode,
24
- degraded: run.execution?.degraded ?? false,
25
- degradationReason: run.execution?.degradationReason ?? null,
69
+ degraded,
70
+ degradationReason: run.degradationReason ?? run.execution?.degradationReason ?? null,
26
71
  intent: run.plan?.intent ?? null,
27
72
  track: run.plan?.track ?? null,
28
73
  suggestedWorkflowType: run.plan?.suggestedWorkflowType ?? null,
@@ -31,10 +76,38 @@ function shapeRun(run) {
31
76
  tasks: (run.tasks || []).map((t) => ({
32
77
  id: t.id, role: t.role, status: t.status, executor: t.executor,
33
78
  output: t.output ?? null, reasoning: t.reasoning ?? null, error: t.error ?? null,
79
+ webCapability: t.webCapability ?? null,
80
+ webEvidence: t.webEvidence ?? null,
81
+ webSearchRequests: t.webSearchRequests ?? 0,
82
+ // Host-backend fields only: the materialized prompt an awaiting-host task
83
+ // carries (nulled out once submitHostTaskResult records a real result),
84
+ // and provenanceSource — present only on a host-reported result, so it can
85
+ // never be confused with a construct-verified provider execution.
86
+ ...(t.hostPrompt ? { system: t.hostPrompt.system, user: t.hostPrompt.user } : {}),
87
+ ...(t.provenanceSource ? { provenanceSource: t.provenanceSource } : {}),
34
88
  })),
35
89
  };
36
90
  }
37
91
 
92
+ // Fail-closed remote ingress guard (ADR-0050): the remote orchestration service is
93
+ // out-of-repo and cannot be trusted to govern. Re-run every task's webEvidence through
94
+ // the single F08 grader so a citation can never arrive trusted or ungoverned, and mark the
95
+ // run degraded if any item was not already trust:'untrusted' with a valid Admiralty grade.
96
+
97
+ export function governRemoteWebEvidence(run, now = Date.now()) {
98
+ let tampered = false;
99
+ for (const t of run.tasks || []) {
100
+ if (!Array.isArray(t.webEvidence) || t.webEvidence.length === 0) continue;
101
+ if (t.webEvidence.some((e) => e?.trust !== 'untrusted' || !/^[A-F][1-6]$/.test(e?.admiralty || ''))) tampered = true;
102
+ t.webEvidence = governWebResults(t.webEvidence, { now });
103
+ }
104
+ if (tampered) {
105
+ run.degraded = true;
106
+ run.degradationReason = run.degradationReason || 'remote-web-evidence-regoverned';
107
+ }
108
+ return run;
109
+ }
110
+
38
111
  function toRequest(args) {
39
112
  return {
40
113
  request: args.request,
@@ -48,6 +121,27 @@ function toRequest(args) {
48
121
  };
49
122
  }
50
123
 
124
+ // Only MCP dispatch reaches orchestrationRun() — an in-process CLI call goes
125
+ // through runOrchestration/planRun directly (see bin/construct's `orchestrate
126
+ // run`), never through this function — so the host-execution default belongs
127
+ // exactly here: when the caller gave no explicit worker_backend AND the
128
+ // project's own construct.config.json declares none, an MCP-originated run
129
+ // defaults to the host executing specialists in its own session rather than
130
+ // inline prepare-only or a provider API spend the caller may have no credits
131
+ // for. The RAW (pre-default-merge) project config is the check that matters —
132
+ // loadProjectConfig always merges DEFAULT_PROJECT_CONFIG in, so the merged
133
+ // config's workerBackend is never actually absent; only the raw file reveals
134
+ // whether the user configured one.
135
+
136
+ function resolveMcpDefaultBackend(explicitBackend, { cwd, env }) {
137
+ if (explicitBackend) return explicitBackend;
138
+ try {
139
+ const { raw } = loadProjectConfig(cwd, env);
140
+ if (raw?.orchestration?.workerBackend) return undefined;
141
+ } catch { /* no resolvable config — fall through to the host default */ }
142
+ return HOST;
143
+ }
144
+
51
145
  // A remote orchestration service is opt-in: only when CONSTRUCT_ORCHESTRATION_URL
52
146
  // is set (team / enterprise). The token comes from the Construct env, not from
53
147
  // the dashboard server, so the in-process default path carries no dashboard coupling.
@@ -67,6 +161,17 @@ function unreachable(base, detail) {
67
161
  };
68
162
  }
69
163
 
164
+ // A bounded fetch timing out is not the same failure as a genuinely unreachable host
165
+ // (ORCH-004 AC3): the error text must say timeout and name the bound so a healthy but
166
+ // slow service is never misreported as network-down.
167
+
168
+ function remoteFetchFailed(base, err) {
169
+ if (err?.name === 'RemoteFetchTimeoutError') {
170
+ return { error: `${err.message}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, service: base, failFast: true };
171
+ }
172
+ return unreachable(base, err.message);
173
+ }
174
+
70
175
  export async function orchestrationRun(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
71
176
  const { request, worker_backend, wait = true, timeout_ms = 120000 } = args;
72
177
  if (!request || typeof request !== 'string') {
@@ -75,15 +180,17 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
75
180
 
76
181
  const reqObj = toRequest(args);
77
182
  const remote = remoteService(env);
78
- if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl });
183
+ if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl, env });
79
184
 
80
- const opts = { env, cwd, workerBackend: worker_backend, fetchImpl };
185
+ const workerBackend = resolveMcpDefaultBackend(worker_backend, { cwd, env });
186
+ const opts = { env, cwd, workerBackend, fetchImpl };
81
187
  try {
82
188
  if (!wait) {
83
189
  const planned = await startRun(reqObj, opts);
84
190
  return { runId: planned.runId, status: planned.status, executionMode: planned.execution?.executionMode, poll: 'orchestration_status with run_id' };
85
191
  }
86
- return shapeRun(await runOrchestration(reqObj, opts));
192
+ const shaped = shapeRun(await runOrchestration(reqObj, opts));
193
+ return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
87
194
  } catch (err) {
88
195
  return { error: `Orchestration failed: ${err.message}`, failFast: true };
89
196
  }
@@ -92,13 +199,16 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
92
199
  export async function orchestrationStatus(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
93
200
  const { run_id, limit = 20 } = args;
94
201
  const remote = remoteService(env);
95
- if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl });
202
+ if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl, env });
96
203
  try {
97
204
  if (run_id) {
98
205
  const run = await getRun(cwd, run_id, { env });
99
- return run || { error: `No run found with id ${run_id}.` };
206
+ if (!run) return { error: `No run found with id ${run_id}.` };
207
+ const shaped = shapeRun(run);
208
+ return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
100
209
  }
101
- return await getRuns(cwd, { env, limit });
210
+ const runs = await getRuns(cwd, { env, limit });
211
+ return runs.map(shapeRun);
102
212
  } catch (err) {
103
213
  return { error: `Failed to read orchestration runs: ${err.message}` };
104
214
  }
@@ -108,13 +218,64 @@ export async function orchestrationStatus(args = {}, { env = process.env, cwd =
108
218
  // is set; the wire shape matches the standalone orchestration service's
109
219
  // /api/orchestration/runs contract (ADR-0022).
110
220
 
111
- async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl }) {
221
+ // Reachability fail-fast belongs to the deadline check in the poll loop, not the
222
+ // per-request budget: a remote LLM-backed run needs a generous per-request window
223
+ // (ORCH-004; construct-o6t8.2). Default is 30s, well above the industry reachability-probe
224
+ // mistake this replaces, and short of the 600s ceiling the OpenAI/Anthropic SDKs use for
225
+ // long completions — an orchestration run/status round trip is shorter-lived than that.
226
+ // Env-overridable via the shared non-negative resolver so an explicit 0 is honored
227
+ // (near-instant abort) rather than silently treated as unset, and garbage/negative
228
+ // values fall back to the default instead of producing a NaN AbortSignal delay.
229
+
230
+ export const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
231
+
232
+ function requestTimeoutMs(env) {
233
+ return resolveNonNegativeSetting(env, 'CONSTRUCT_ORCHESTRATION_TIMEOUT_MS', DEFAULT_REQUEST_TIMEOUT_MS);
234
+ }
235
+
236
+ // Per-request bound on every remote fetch (ORCH-004; construct-o6t8.2 / construct-neq9.6):
237
+ // a timer aborts the request at perRequestMs and races it against a timeout rejection, so
238
+ // a hung request settles the call even when the underlying fetch ignores its own signal.
239
+ // The timer is cleared the instant either side of the race wins, so a healthy fast response
240
+ // leaves no scheduled timer alive to fire and resolve a promise outside its own async scope
241
+ // — the leak that tripped node:test's "resolution still pending after the loop resolved". The
242
+ // rejection carries the numbered RemoteFetchTimeoutError so a bounded timeout stays
243
+ // distinguishable from a genuinely unreachable host, never the signal's own DOMException
244
+ // message ("aborted due to timeout" with no number).
245
+
246
+ class RemoteFetchTimeoutError extends Error {
247
+ constructor(perRequestMs) {
248
+ super(`Remote orchestration request timed out after ${perRequestMs}ms`);
249
+ this.name = 'RemoteFetchTimeoutError';
250
+ this.timeoutMs = perRequestMs;
251
+ }
252
+ }
253
+
254
+ async function timedRemoteFetch(fetchImpl, url, opts, perRequestMs) {
255
+ const controller = new AbortController();
256
+ let clearTimer;
257
+ const timeout = new Promise((_, reject) => {
258
+ const timer = setTimeout(() => {
259
+ const err = new RemoteFetchTimeoutError(perRequestMs);
260
+ controller.abort(err);
261
+ reject(err);
262
+ }, perRequestMs);
263
+ clearTimer = () => clearTimeout(timer);
264
+ });
265
+ try {
266
+ return await Promise.race([fetchImpl(url, { ...opts, signal: controller.signal }), timeout]);
267
+ } finally {
268
+ clearTimer();
269
+ }
270
+ }
271
+
272
+ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl, env = process.env, perRequestTimeoutMs = requestTimeoutMs(env) } = {}) {
112
273
  const { base, token } = remote;
113
274
  const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
114
275
  const body = JSON.stringify({ ...reqObj, workerBackend });
115
276
  let started;
116
277
  try {
117
- const res = await fetchImpl(`${base}/api/orchestration/runs`, { method: 'POST', headers, body });
278
+ const res = await timedRemoteFetch(fetchImpl, `${base}/api/orchestration/runs`, { method: 'POST', headers, body }, perRequestTimeoutMs);
118
279
  if (res.status === 401 || res.status === 403) {
119
280
  return { error: `Remote service rejected the request (HTTP ${res.status}). Set CONSTRUCT_ORCHESTRATION_TOKEN (or CONSTRUCT_DASHBOARD_TOKEN).`, service: base, failFast: true };
120
281
  }
@@ -122,7 +283,7 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
122
283
  if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
123
284
  started = envelope.data;
124
285
  } catch (err) {
125
- return unreachable(base, err.message);
286
+ return remoteFetchFailed(base, err);
126
287
  }
127
288
 
128
289
  if (!wait) {
@@ -137,27 +298,44 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
137
298
  }
138
299
  await new Promise((r) => setTimeout(r, 250));
139
300
  try {
140
- const res = await fetchImpl(`${base}/api/orchestration/runs/${encodeURIComponent(started.runId)}`, { headers });
301
+ const res = await timedRemoteFetch(fetchImpl, `${base}/api/orchestration/runs/${encodeURIComponent(started.runId)}`, { headers }, perRequestTimeoutMs);
141
302
  const envelope = await res.json();
142
303
  if (!res.ok) return { error: `Remote service error while polling (HTTP ${res.status}): ${envelope.error || 'unknown'}`, runId: started.runId, service: base };
143
304
  run = envelope.data;
144
305
  } catch (err) {
306
+ if (err?.name === 'RemoteFetchTimeoutError') {
307
+ return { error: `${err.message} while polling run ${started.runId}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, runId: started.runId, service: base, failFast: true };
308
+ }
145
309
  return { error: `Lost connection to the remote service while polling run ${started.runId}: ${err.message}`, runId: started.runId, service: base, failFast: true };
146
310
  }
147
311
  }
148
- return { ...shapeRun(run), service: base };
312
+ return { ...shapeRun(governRemoteWebEvidence(run)), service: base };
149
313
  }
150
314
 
151
- async function statusViaService(remote, { run_id, limit }, { fetchImpl }) {
315
+ // The status poll GET carries the same AbortSignal bound as the run POST (ORCH-004),
316
+ // so a service that accepts the connection but never replies cannot hang this call
317
+ // indefinitely. `timeoutMs` is overridable directly for tests; production callers
318
+ // resolve it from env the same way runViaService does.
319
+
320
+ export async function statusViaService(remote, { run_id, limit } = {}, { fetchImpl, env = process.env, timeoutMs = requestTimeoutMs(env) } = {}) {
152
321
  const { base, token } = remote;
153
322
  const headers = token ? { Authorization: `Bearer ${token}` } : {};
154
323
  const path = run_id ? `/api/orchestration/runs/${encodeURIComponent(run_id)}` : `/api/orchestration/runs?limit=${encodeURIComponent(limit)}`;
155
324
  try {
156
- const res = await fetchImpl(`${base}${path}`, { headers });
325
+ const res = await timedRemoteFetch(fetchImpl, `${base}${path}`, { headers }, timeoutMs);
157
326
  const envelope = await res.json();
158
327
  if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
328
+ // The remote service is out-of-repo and cannot be trusted to have governed its
329
+ // own web evidence (ADR-0050); re-run every run through the F08 grader on the
330
+ // status path exactly as runViaService does, so a citation can never arrive
331
+ // pre-marked trusted. shapeRun then applies the honest terminal-status taxonomy.
332
+ if (Array.isArray(envelope.data)) {
333
+ return envelope.data.map((r) => shapeRun(governRemoteWebEvidence(r)));
334
+ } else if (envelope.data) {
335
+ return shapeRun(governRemoteWebEvidence(envelope.data));
336
+ }
159
337
  return envelope.data;
160
338
  } catch (err) {
161
- return unreachable(base, err.message);
339
+ return remoteFetchFailed(base, err);
162
340
  }
163
341
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * lib/mcp/tools/orchestration-task-result.tool.mjs — self-registered MCP tool
3
+ * (LMCP-B5, lib/mcp/tool-registry.mjs) that ingests one host-executed
4
+ * specialist task result for a run planned with worker_backend=host.
5
+ *
6
+ * orchestration_run materializes each specialist's prompt (system + user turn)
7
+ * and stands the run at 'awaiting-host' without ever calling a model itself —
8
+ * the calling host executes each prompt in its own session (the model/
9
+ * subscription it is already running under) and reports the result back here.
10
+ * submitHostTaskResult (lib/orchestration/runtime.mjs) is the single place that
11
+ * validates and records the result and finalizes the run once every task is
12
+ * terminal; this file only shapes that call as an MCP tool and echoes back the
13
+ * next materialized prompt so a host can drive the whole run in a simple loop:
14
+ * call orchestration_run once, then call this tool per task until next_task
15
+ * is null.
16
+ */
17
+
18
+ import { submitHostTaskResult } from '../../orchestration/runtime.mjs';
19
+ import { shapeRun } from './orchestration-run.mjs';
20
+
21
+ export const TOOL_DEFS = [
22
+ {
23
+ name: 'orchestration_task_result',
24
+ description:
25
+ 'Submit one host-executed specialist task result for an orchestration run planned with '
26
+ + 'worker_backend=host (the MCP default). orchestration_run returns each task\'s materialized '
27
+ + 'prompt (system/user) without executing it; execute that prompt yourself as the named '
28
+ + 'specialist role, then call this tool with the run_id, task_id, and your output. The '
29
+ + 'response carries next_task (the next awaiting prompt) or null once the run is terminal — '
30
+ + 'loop until null. model/provider/reasoning are optional, self-reported fields (never '
31
+ + 'independently verified), recorded with provenanceSource "host-reported".',
32
+ inputSchema: {
33
+ type: 'object',
34
+ required: ['run_id', 'task_id', 'output'],
35
+ properties: {
36
+ run_id: { type: 'string', description: 'The run id returned by orchestration_run.' },
37
+ task_id: { type: 'string', description: 'The task id this result answers (e.g. "t1"), from orchestration_run\'s task list or a prior call\'s next_task.' },
38
+ output: { type: 'string', description: 'The specialist output you produced for this task. Must be non-empty.' },
39
+ model: { type: 'string', description: 'Optional: the model you used to execute this task (self-reported).' },
40
+ provider: { type: 'string', description: 'Optional: the provider/vendor family you used (self-reported).' },
41
+ reasoning: { type: 'string', description: 'Optional: your reasoning/thinking for this task, if you want it disclosed.' },
42
+ },
43
+ },
44
+ outputSchema: { type: 'object' },
45
+ safety: { class: 'write', filesystem: 'write', network: 'none', process: 'none' },
46
+ },
47
+ ];
48
+
49
+ /**
50
+ * @param {object} args { run_id, task_id, output, model?, provider?, reasoning? }
51
+ * @param {object} [opts] { env, cwd } — defaulted so this behaves identically whether called
52
+ * directly (tests, CLI) or dispatched by the MCP server (which does not inject opts today,
53
+ * matching orchestration_run/orchestration_status's own convention).
54
+ */
55
+ export async function orchestrationTaskResult(args = {}, { env = process.env, cwd = process.cwd() } = {}) {
56
+ const { run_id, task_id, output, model, provider, reasoning } = args;
57
+ if (!run_id || typeof run_id !== 'string') return { error: 'Missing "run_id" — the run id from orchestration_run.' };
58
+ if (!task_id || typeof task_id !== 'string') return { error: 'Missing "task_id".' };
59
+
60
+ try {
61
+ const { run, nextTask } = await submitHostTaskResult(cwd, run_id, task_id, { output, model, provider, reasoning }, { env });
62
+ const shaped = shapeRun(run);
63
+ return {
64
+ accepted: true,
65
+ run_status: shaped.status,
66
+ next_task: nextTask
67
+ ? { task_id: nextTask.id, role: nextTask.role, system: nextTask.hostPrompt?.system ?? null, user: nextTask.hostPrompt?.user ?? null }
68
+ : null,
69
+ };
70
+ } catch (err) {
71
+ return { accepted: false, error: err.message, code: err.code || 'HOST_RESULT_REJECTED' };
72
+ }
73
+ }
74
+
75
+ export const TOOL_HANDLERS = {
76
+ orchestration_task_result: orchestrationTaskResult,
77
+ };