@geraldmaron/construct 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/.env.example +10 -7
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1116 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +525 -38
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. package/rules/zh/testing.md +0 -34
@@ -1,774 +1,60 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * lib/mcp/server.mjs — Construct MCP server exposing workflow, skill, and observability tools.
3
+ * lib/mcp/server.mjs — Construct MCP server: tool registry and request dispatcher.
4
4
  *
5
- * Provides MCP tools for agent health, workflow management, skill retrieval, and Langfuse telemetry.
5
+ * Thin dispatcher only all tool implementations live in lib/mcp/tools/*.mjs.
6
+ * Registers 40 tools across 7 modules: project, document, storage, skills, workflow, telemetry, memory.
6
7
  * Consumed by Claude Code, OpenCode, and any MCP-compatible host.
7
8
  */
8
9
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
9
10
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
11
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
11
- import { readFileSync, readdirSync, existsSync } from 'node:fs';
12
- import { join, resolve, dirname } from 'node:path';
12
+ import { resolve, dirname } from 'node:path';
13
13
  import { fileURLToPath } from 'node:url';
14
- import { homedir } from 'node:os';
15
- import { execSync } from 'node:child_process';
16
- import * as langfuse from '../telemetry/backends/langfuse.mjs';
17
- import { summarizePromptComposition } from '../prompt-composer.mjs';
18
- import { enrichMetadataWithPrompt } from '../prompt-metadata.mjs';
19
- import { readCurrentModels, resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from '../model-router.mjs';
14
+ import { realpathSync } from 'node:fs';
20
15
  import { loadToolkitEnv } from '../toolkit-env.mjs';
21
16
  import { loadConstructEnv } from '../env-config.mjs';
22
- import { inspectContextState } from '../context-state.mjs';
23
17
 
24
- const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
25
- const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
26
- loadToolkitEnv(ROOT_DIR);
27
- import {
28
- addTask,
29
- addTaskFromIntent,
30
- addTasksFromPlan,
31
- alignmentFindings,
32
- createNeedsMainInputPacket,
33
- initWorkflow,
34
- inspectWorkflowHealth,
35
- loadWorkflow,
36
- summarizeWorkflow,
37
- updateTask,
38
- validateWorkflowState,
39
- } from '../workflow-state.mjs';
40
- import { buildPublicHealthSurface, buildStatus } from '../status.mjs';
41
- import { routeRequest, requiresExecutiveApproval, TERMINAL_STATES } from '../orchestration-policy.mjs';
42
-
43
- const SECRET_PATTERNS = [
44
- { name: 'Anthropic API key', pattern: /ANTHROPIC_API_KEY\s*=\s*(sk-ant-[a-zA-Z0-9\-_]{20,})/i },
45
- { name: 'OpenAI API key', pattern: /OPENAI_API_KEY\s*=\s*(sk-[a-zA-Z0-9]{40,})/i },
46
- { name: 'OpenRouter key', pattern: /(sk-or-v1-[a-zA-Z0-9]{40,})/ },
47
- { name: 'AWS access key', pattern: /(AKIA[0-9A-Z]{16})/ },
48
- { name: 'Private key (PEM)', pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
49
- { name: 'GitHub personal access token', pattern: /(ghp_[a-zA-Z0-9]{36})/ },
50
- { name: 'GitHub Actions token', pattern: /(ghs_[a-zA-Z0-9]{36})/ },
51
- { name: 'Database URL with credentials', pattern: /DATABASE_URL\s*=\s*(postgresql:\/\/[^@]+:[^@]+@)/i },
52
- ];
53
-
54
- const PLACEHOLDER_PATTERNS = [
55
- /\.\.\./,
56
- /YOUR_KEY/i,
57
- /<[^>]+>/,
58
- /^sk-\.\.\./,
59
- /^pk-lf-\.\.\./,
60
- /__[A-Z_]+__/,
61
- ];
62
-
63
- function isPlaceholder(value) {
64
- return PLACEHOLDER_PATTERNS.some((p) => p.test(value));
65
- }
66
-
67
- function isBinary(buffer) {
68
- const len = Math.min(buffer.length, 512);
69
- for (let i = 0; i < len; i++) {
70
- if (buffer[i] === 0) return true;
71
- }
72
- return false;
73
- }
74
-
75
- function exec(cmd, cwd) {
76
- return execSync(cmd, { stdio: 'pipe', timeout: 10000, cwd }).toString().trim();
77
- }
78
-
79
- function readJSON(filePath) {
80
- if (!existsSync(filePath)) return null;
81
- try {
82
- return JSON.parse(readFileSync(filePath, 'utf8'));
83
- } catch {
84
- return null;
18
+ // Apply config.env values to process.env, letting config.env win over shell env
19
+ // so telemetry/OpenRouter credentials are always correct regardless of host env.
20
+ {
21
+ const confEnv = loadConstructEnv({ warn: false });
22
+ for (const [k, v] of Object.entries(confEnv)) {
23
+ process.env[k] = v;
85
24
  }
86
25
  }
87
26
 
88
- // Tool: agent_health
89
- function agentHealth(args) {
90
- const reviewDir = join(homedir(), '.cx', 'performance-reviews');
91
-
92
- if (!existsSync(reviewDir)) {
93
- return { error: "No performance reviews found. Run 'construct review' to generate one." };
94
- }
95
-
96
- let files;
97
- try {
98
- files = readdirSync(reviewDir);
99
- } catch {
100
- return { error: "No performance reviews found. Run 'construct review' to generate one." };
101
- }
102
-
103
- const processed = files
104
- .filter((f) => f.endsWith('.json') && !f.endsWith('-raw.json'))
105
- .sort()
106
- .reverse();
107
-
108
- const raw = files
109
- .filter((f) => f.endsWith('-raw.json'))
110
- .sort()
111
- .reverse();
112
-
113
- const candidates = processed.length > 0 ? processed : raw;
114
-
115
- if (candidates.length === 0) {
116
- return { error: "No performance reviews found. Run 'construct review' to generate one." };
117
- }
118
-
119
- let data;
120
- try {
121
- const content = readFileSync(join(reviewDir, candidates[0]), 'utf8');
122
- data = JSON.parse(content);
123
- } catch {
124
- return { error: "Failed to parse performance review file." };
125
- }
126
-
127
- const reviewDate = data.reviewDate ?? data.generatedAt ?? candidates[0].replace(/\.json$/, '');
128
- const period = data.period ?? data.windowDays ?? null;
129
-
130
- let agents = [];
131
-
132
- if (Array.isArray(data.agents)) {
133
- agents = data.agents;
134
- } else if (data.agentReviews) {
135
- agents = Object.entries(data.agentReviews).map(([name, review]) => ({
136
- name,
137
- status: review.status ?? review.health ?? 'unknown',
138
- avgScore: review.avgScore ?? review.averageScore ?? null,
139
- trend: review.trend ?? null,
140
- failureRate: review.failureRate ?? null,
141
- invocations: review.invocations ?? review.totalCalls ?? null,
142
- }));
143
- } else if (data.summary && Array.isArray(data.summary.agents)) {
144
- agents = data.summary.agents;
145
- }
146
-
147
- if (args.agent_name) {
148
- const target = args.agent_name.toLowerCase();
149
- agents = agents.filter((a) => (a.name ?? '').toLowerCase().includes(target));
150
- }
151
-
152
- const normalized = agents.map((a) => ({
153
- name: a.name ?? 'unknown',
154
- status: a.status ?? 'unknown',
155
- avgScore: a.avgScore ?? a.averageScore ?? null,
156
- trend: a.trend ?? null,
157
- failureRate: a.failureRate ?? null,
158
- invocations: a.invocations ?? a.totalCalls ?? null,
159
- }));
160
-
161
- return { reviewDate, period, agents: normalized };
162
- }
163
-
164
- // Tool: summarize_diff
165
- function summarizeDiff(args) {
166
- const baseRef = args.base_ref ?? 'HEAD~1';
167
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
168
-
169
- let stat, nameStatus;
170
- try {
171
- stat = exec(`git diff --stat ${baseRef}`, cwd);
172
- nameStatus = exec(`git diff --name-status ${baseRef}`, cwd);
173
- } catch {
174
- return { error: 'Not a git repository or git not available' };
175
- }
176
-
177
- const changes = nameStatus
178
- .split('\n')
179
- .filter(Boolean)
180
- .map((line) => {
181
- const [status, ...parts] = line.split('\t');
182
- return { status: status.trim(), file: parts.join('\t') };
183
- });
184
-
185
- // Parse insertions/deletions from stat summary line
186
- const statLines = stat.split('\n');
187
- const summaryLine = statLines[statLines.length - 1] ?? '';
188
- const filesMatch = summaryLine.match(/(\d+) files? changed/);
189
- const insertionsMatch = summaryLine.match(/(\d+) insertions?\(\+\)/);
190
- const deletionsMatch = summaryLine.match(/(\d+) deletions?\(-\)/);
191
-
192
- const filesChanged = filesMatch ? parseInt(filesMatch[1], 10) : changes.length;
193
- const insertions = insertionsMatch ? parseInt(insertionsMatch[1], 10) : 0;
194
- const deletions = deletionsMatch ? parseInt(deletionsMatch[1], 10) : 0;
195
-
196
- const added = changes.filter((c) => c.status === 'A').length;
197
- const modified = changes.filter((c) => c.status === 'M').length;
198
- const deleted = changes.filter((c) => c.status === 'D').length;
199
-
200
- const parts = [];
201
- if (modified > 0) parts.push(`${modified} file${modified > 1 ? 's' : ''} modified`);
202
- if (added > 0) parts.push(`${added} file${added > 1 ? 's' : ''} added`);
203
- if (deleted > 0) parts.push(`${deleted} file${deleted > 1 ? 's' : ''} deleted`);
204
-
205
- const summary =
206
- parts.length > 0
207
- ? `${parts.join(', ')} (+${insertions}/-${deletions} lines) vs ${baseRef}.`
208
- : `No changes vs ${baseRef}.`;
209
-
210
- return { base_ref: baseRef, files_changed: filesChanged, insertions, deletions, changes, summary };
211
- }
212
-
213
- // Tool: scan_file
214
- function scanFile(args) {
215
- const filePath = resolve(args.file_path);
216
-
217
- let rawBuffer;
218
- try {
219
- rawBuffer = readFileSync(filePath);
220
- } catch (err) {
221
- return { error: `Cannot read file: ${err.message}` };
222
- }
223
-
224
- if (isBinary(rawBuffer)) {
225
- return { file_path: filePath, secrets: [], quality_issues: [], clean: true };
226
- }
227
-
228
- const content = rawBuffer.toString('utf8');
229
- const lines = content.split('\n');
230
-
231
- // Secret scanning
232
- const secrets = [];
233
- for (let i = 0; i < lines.length; i++) {
234
- const line = lines[i];
235
- for (const { name, pattern } of SECRET_PATTERNS) {
236
- const match = pattern.exec(line);
237
- if (!match) continue;
238
- const captured = match[1] ?? match[0];
239
- if (isPlaceholder(captured)) continue;
240
- secrets.push({
241
- pattern: name,
242
- line: i + 1,
243
- masked_value: captured.slice(0, 20) + '...',
244
- });
245
- }
246
- }
247
-
248
- // Quality scanning
249
- const quality_issues = [];
250
-
251
- if (lines.length > 800) {
252
- quality_issues.push({ type: 'file_too_long', detail: `File has ${lines.length} lines (limit: 800)` });
253
- }
254
-
255
- // Detect oversized functions via simple heuristic
256
- const funcKeywords = /^\s*(function\s+\w+|async\s+function\s+\w+|\w+\s*=\s*(?:async\s+)?\(|def\s+\w+|func\s+\w+)/;
257
- let funcStart = null;
258
- let braceDepth = 0;
259
- let inBlock = false;
260
-
261
- for (let i = 0; i < lines.length; i++) {
262
- const line = lines[i];
263
-
264
- if (funcKeywords.test(line)) {
265
- funcStart = i + 1;
266
- braceDepth = 0;
267
- inBlock = false;
268
- }
269
-
270
- if (funcStart !== null) {
271
- for (const ch of line) {
272
- if (ch === '{') { braceDepth++; inBlock = true; }
273
- else if (ch === '}') { braceDepth--; }
274
- }
275
- if (inBlock && braceDepth <= 0) {
276
- const funcLen = i + 1 - funcStart + 1;
277
- if (funcLen > 50) {
278
- quality_issues.push({
279
- type: 'function_too_long',
280
- detail: `Function starting at line ${funcStart} is ${funcLen} lines (limit: 50)`,
281
- line: funcStart,
282
- });
283
- }
284
- funcStart = null;
285
- inBlock = false;
286
- braceDepth = 0;
287
- }
288
- }
289
-
290
- // TODO/FIXME/HACK comments
291
- const todoMatch = /\/\/\s*(TODO|FIXME|HACK)\b|#\s*(TODO|FIXME|HACK)\b/i.exec(line);
292
- if (todoMatch) {
293
- const tag = (todoMatch[1] ?? todoMatch[2]).toUpperCase();
294
- quality_issues.push({
295
- type: 'comment_marker',
296
- detail: `${tag} comment`,
297
- line: i + 1,
298
- });
299
- }
300
- }
301
-
302
- return {
303
- file_path: filePath,
304
- secrets,
305
- quality_issues,
306
- clean: secrets.length === 0 && quality_issues.length === 0,
307
- };
308
- }
309
-
310
- // Tool: project_context
311
- function projectContext(args) {
312
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
313
- const contextInspection = inspectContextState(cwd);
314
- const context_state = contextInspection.state;
315
- const context = context_state?.markdown || null;
316
- const has_context_file = contextInspection.hasFile;
317
- const workflow = loadWorkflow(cwd);
318
- const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
319
- const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
320
- const executionContractModel = resolveExecutionContractModelMetadata({
321
- envValues: mergedEnv,
322
- registryModels: registry.models ?? {},
323
- requestedTier: selectModelTierForWorkCategory(workflow?.tasks?.find((task) => task.key === workflow?.currentTaskKey)?.workCategory),
324
- workCategory: null,
325
- });
326
- const publicHealth = buildPublicHealthSurface({
327
- cwd,
328
- contextInspection,
329
- workflow,
330
- executionContractModel,
331
- });
332
-
333
- let recent_commits = [];
334
- let working_tree_status = 'clean';
335
- let changed_files = [];
336
-
337
- try {
338
- const log = exec('git log --oneline -10', cwd);
339
- recent_commits = log.split('\n').filter(Boolean);
340
- } catch {
341
- // not a git repo or no commits
342
- }
343
-
344
- try {
345
- const status = exec('git status --short', cwd);
346
- const statusLines = status.split('\n').filter(Boolean);
347
- changed_files = statusLines.map((l) => l.trim());
348
- working_tree_status = statusLines.length > 0 ? 'dirty' : 'clean';
349
- } catch {
350
- // not a git repo
351
- }
352
-
353
- return { cwd, has_context_file, context, context_state, recent_commits, working_tree_status, changed_files, publicHealth };
354
- }
355
-
356
- function orchestrationPolicy(args) {
357
- const route = routeRequest(args || {});
358
- const approvalRequired = requiresExecutiveApproval(args?.approval || {});
359
- return {
360
- ...route,
361
- approvalRequired,
362
- terminalStates: TERMINAL_STATES,
363
- };
364
- }
365
-
366
- // Tool: list_skills
367
- function listSkills() {
368
- const skillsDir = join(ROOT_DIR, 'skills');
369
- if (!existsSync(skillsDir)) return { error: 'Skills directory not found.' };
370
-
371
- const listDirRecursive = (dir, prefix = '') => {
372
- let results = [];
373
- const entries = readdirSync(dir, { withFileTypes: true });
374
- for (const entry of entries) {
375
- if (entry.isDirectory()) {
376
- results = results.concat(listDirRecursive(join(dir, entry.name), `${prefix}${entry.name}/`));
377
- } else if (entry.name.endsWith('.md') && entry.name !== 'SKILL.md') {
378
- results.push(`${prefix}${entry.name.replace('.md', '')}`);
379
- }
380
- }
381
- return results;
382
- };
383
-
384
- const skills = listDirRecursive(skillsDir).sort();
385
- return { skills };
386
- }
387
-
388
- // Tool: get_skill
389
- function getSkill(args) {
390
- const { path: skillPath } = args;
391
- if (!skillPath) return { error: 'Missing path argument' };
392
-
393
- const fullPath = join(ROOT_DIR, 'skills', `${skillPath}.md`);
394
-
395
- if (!existsSync(fullPath)) {
396
- return { error: `Skill not found: ${skillPath}` };
397
- }
398
- const content = readFileSync(fullPath, 'utf8');
399
- return { content };
400
- }
401
-
402
- // Tool: get_template
403
- function getTemplate(args) {
404
- const { name } = args;
405
- if (!name) return { error: 'Missing name argument' };
406
- const safe = String(name).replace(/[^a-zA-Z0-9._-]/g, '');
407
- if (!safe) return { error: 'Invalid template name' };
408
-
409
- const cwd = process.cwd();
410
- const candidates = [
411
- { source: 'project-override', path: join(cwd, '.cx', 'templates', 'docs', `${safe}.md`) },
412
- { source: 'shipped-default', path: join(ROOT_DIR, 'templates', 'docs', `${safe}.md`) },
413
- ];
414
- for (const { source, path: p } of candidates) {
415
- if (existsSync(p)) {
416
- return { source, path: p, content: readFileSync(p, 'utf8') };
417
- }
418
- }
419
- return { error: `Template not found: ${safe}`, available: listTemplatesRaw() };
420
- }
421
-
422
- function listTemplatesRaw() {
423
- const shipped = [];
424
- const override = [];
425
- const shippedDir = join(ROOT_DIR, 'templates', 'docs');
426
- if (existsSync(shippedDir)) {
427
- for (const f of readdirSync(shippedDir)) {
428
- if (f.endsWith('.md')) shipped.push(f.replace(/\.md$/, ''));
429
- }
430
- }
431
- const overrideDir = join(process.cwd(), '.cx', 'templates', 'docs');
432
- if (existsSync(overrideDir)) {
433
- for (const f of readdirSync(overrideDir)) {
434
- if (f.endsWith('.md')) override.push(f.replace(/\.md$/, ''));
435
- }
436
- }
437
- return { shipped: shipped.sort(), overridden: override.sort() };
438
- }
439
-
440
- // Tool: list_templates
441
- function listTemplates() {
442
- return listTemplatesRaw();
443
- }
444
-
445
- // Tool: search_skills
446
- function searchSkills(args) {
447
- const { pattern } = args;
448
- if (!pattern) return { error: 'Missing pattern argument' };
449
- const skillsDir = join(ROOT_DIR, 'skills');
450
-
451
- try {
452
- // Try ripgrep first
453
- const output = execSync(`rg -i "${pattern.replace(/"/g, '\\"')}" "${skillsDir}"`, { encoding: 'utf8' });
454
- return { results: output.split('\n').filter(Boolean) };
455
- } catch (err) {
456
- // Fallback to basic grep
457
- try {
458
- const output = execSync(`grep -ri "${pattern.replace(/"/g, '\\"')}" "${skillsDir}"`, { encoding: 'utf8' });
459
- return { results: output.split('\n').filter(Boolean) };
460
- } catch (err2) {
461
- return { results: [], note: 'No matches found or grep error' };
462
- }
463
- }
464
- }
465
-
466
- function workflowStatus(args) {
467
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
468
- const workflow = loadWorkflow(cwd);
469
- const workflowHealth = inspectWorkflowHealth(workflow, { cwd });
470
- const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
471
- const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
472
- const executionContractModel = resolveExecutionContractModelMetadata({
473
- envValues: mergedEnv,
474
- registryModels: registry.models ?? {},
475
- requestedTier: selectModelTierForWorkCategory(workflow?.tasks?.find((task) => task.key === workflow?.currentTaskKey)?.workCategory),
476
- workCategory: null,
477
- });
478
- const publicHealth = buildPublicHealthSurface({
479
- cwd,
480
- workflow,
481
- executionContractModel,
482
- });
483
- return {
484
- cwd,
485
- exists: Boolean(workflow),
486
- summary: summarizeWorkflow(workflow),
487
- workflow,
488
- alignment: {
489
- status: workflowHealth.alignment.status,
490
- findings: workflowHealth.alignment.findings,
491
- },
492
- publicHealth,
493
- };
494
- }
495
-
496
- function workflowInit(args) {
497
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
498
- const title = args.title || 'Untitled workflow';
499
- const { workflow, created } = initWorkflow(cwd, title, args.spec_ref ?? null);
500
- return { cwd, created, workflow, summary: summarizeWorkflow(workflow) };
501
- }
502
-
503
- // Tool: list_teams
504
- function listTeams() {
505
- const teamsPath = join(ROOT_DIR, 'agents', 'teams.json');
506
- if (!existsSync(teamsPath)) return { error: 'agents/teams.json not found' };
507
- const { templates } = JSON.parse(readFileSync(teamsPath, 'utf8'));
508
- return {
509
- teams: Object.entries(templates).map(([name, t]) => ({
510
- name,
511
- description: t.description,
512
- members: t.members,
513
- focus: t.focus,
514
- skills: t.skills,
515
- promotionGates: t.promotionGates,
516
- })),
517
- };
518
- }
519
-
520
- // Tool: get_team
521
- function getTeam(args) {
522
- const { name } = args;
523
- if (!name) return { error: 'Missing name argument' };
524
- const teamsPath = join(ROOT_DIR, 'agents', 'teams.json');
525
- if (!existsSync(teamsPath)) return { error: 'agents/teams.json not found' };
526
- const { templates } = JSON.parse(readFileSync(teamsPath, 'utf8'));
527
- const team = templates[name];
528
- if (!team) return { error: `Team not found: ${name}`, available: Object.keys(templates) };
529
- return { name, ...team };
530
- }
531
-
532
- function workflowAddTask(args) {
533
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
534
- if (args.request) {
535
- const workflow = addTaskFromIntent(cwd, args.request, {
536
- key: args.key,
537
- title: args.title,
538
- phase: args.phase,
539
- owner: args.owner,
540
- files: args.files,
541
- readFirst: args.readFirst,
542
- doNotChange: args.doNotChange,
543
- acceptanceCriteria: args.acceptanceCriteria,
544
- verification: args.verification,
545
- overlays: args.overlays,
546
- challengeRequired: args.challengeRequired,
547
- challengeStatus: args.challengeStatus,
548
- tokenBudget: args.tokenBudget,
549
- status: args.status,
550
- });
551
- if (!workflow) return { ok: true, skipped: true, reason: 'immediate-track' };
552
- return { cwd, workflow, summary: summarizeWorkflow(workflow), source: 'intent' };
553
- }
554
- const workflow = addTask(cwd, {
555
- key: args.key,
556
- title: args.title,
557
- phase: args.phase,
558
- owner: args.owner,
559
- files: args.files,
560
- readFirst: args.readFirst,
561
- doNotChange: args.doNotChange,
562
- acceptanceCriteria: args.acceptanceCriteria,
563
- verification: args.verification,
564
- dependsOn: args.dependsOn,
565
- overlays: args.overlays,
566
- challengeRequired: args.challengeRequired,
567
- challengeStatus: args.challengeStatus,
568
- });
569
- return { cwd, workflow, summary: summarizeWorkflow(workflow), source: 'manual' };
570
- }
571
-
572
- function workflowUpdateTask(args) {
573
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
574
- const workflow = updateTask(cwd, args.key, {
575
- status: args.status,
576
- owner: args.owner,
577
- phase: args.phase,
578
- note: args.note,
579
- verification: args.verification,
580
- overlays: args.overlays,
581
- challengeRequired: args.challengeRequired,
582
- challengeStatus: args.challengeStatus,
583
- });
584
- return { cwd, workflow, summary: summarizeWorkflow(workflow) };
585
- }
586
-
587
- function workflowNeedsMainInput(args) {
588
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
589
- const packet = createNeedsMainInputPacket(args);
590
- const workflow = updateTask(cwd, args.taskKey, {
591
- status: 'blocked_needs_user',
592
- note: `${packet.worker}: ${packet.blocker} | question: ${packet.question}`,
593
- });
594
- return { cwd, packet, workflow, summary: summarizeWorkflow(workflow) };
595
- }
596
-
597
- function workflowValidate(args) {
598
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
599
- const workflow = loadWorkflow(cwd);
600
- const result = validateWorkflowState(workflow);
601
- return { cwd, ...result };
602
- }
603
-
604
- function workflowImportPlan(args) {
605
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
606
- const markdown = args.markdown ?? '';
607
- const { workflow, count } = addTasksFromPlan(cwd, markdown, {
608
- phase: args.phase,
609
- owner: args.owner,
610
- readFirst: Array.isArray(args.readFirst) ? args.readFirst : undefined,
611
- doNotChange: Array.isArray(args.doNotChange) ? args.doNotChange : undefined,
612
- acceptanceCriteria: Array.isArray(args.acceptanceCriteria) ? args.acceptanceCriteria : undefined,
613
- workflowTitle: args.title,
614
- specRef: args.spec_ref,
615
- });
616
- return { cwd, count, workflow, summary: summarizeWorkflow(workflow) };
617
- }
618
-
619
- function resolveReleaseTag(cwd) {
620
- try {
621
- return execSync('git rev-parse --short HEAD', { stdio: 'pipe', cwd, timeout: 2000 }).toString().trim() || undefined;
622
- } catch {
623
- return undefined;
624
- }
625
- }
626
-
627
- function resolveSessionContext() {
628
- const cwd = process.cwd();
629
- let workflowId;
630
- let workflowPhase;
631
- let workflowOwner;
632
- try {
633
- const wf = loadWorkflow(cwd);
634
- if (wf) {
635
- workflowId = wf.id;
636
- workflowPhase = wf.phase;
637
- const active = (wf.tasks || []).find((t) => t.status === 'in-progress' || t.status === 'in_progress');
638
- if (active) workflowOwner = active.owner;
639
- }
640
- } catch { /* best effort */ }
641
- const sessionId = process.env.CLAUDE_SESSION_ID
642
- || process.env.CX_SESSION_ID
643
- || process.env.OPENCODE_SESSION_ID
644
- || workflowId;
645
- const userId = process.env.USER || process.env.USERNAME || process.env.LOGNAME;
646
- return { cwd, sessionId, userId, release: resolveReleaseTag(cwd), workflowPhase, workflowOwner, workflowId };
647
- }
648
-
649
- function langfuseHeaders() {
650
- const key = process.env.LANGFUSE_PUBLIC_KEY;
651
- const secret = process.env.LANGFUSE_SECRET_KEY;
652
- if (!key || !secret) throw new Error('LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY must be set.');
653
- return {
654
- Authorization: `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}`,
655
- 'Content-Type': 'application/json',
656
- };
657
- }
658
-
659
- function langfuseBaseUrl() {
660
- return (process.env.LANGFUSE_BASEURL ?? 'https://cloud.langfuse.com').replace(/\/$/, '');
661
- }
662
-
663
- async function cxTrace(args) {
664
- const ctx = resolveSessionContext();
665
- const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
666
- const registryModels = registry.models ?? {};
667
- const currentModels = readCurrentModels(join(ROOT_DIR, '.env'), registryModels, process.env);
668
- const route = typeof args.input === 'string' ? routeRequest({ request: args.input }) : null;
669
- const executionContractModel = resolveExecutionContractModelMetadata({
670
- envValues: currentModels,
671
- registryModels,
672
- requestedTier: selectModelTierForWorkCategory(route?.workCategory),
673
- workCategory: route?.workCategory || null,
674
- });
675
- const runtimePromptMetadata = summarizePromptComposition(args.name, {
676
- rootDir: ROOT_DIR,
677
- request: typeof args.input === 'string' ? args.input : '',
678
- route,
679
- registryModels,
680
- envValues: currentModels,
681
- executionContractModel,
682
- hostConstraints: {
683
- runtime: 'mcp',
684
- providerAgnostic: true,
685
- telemetryBackend: 'langfuse',
686
- },
687
- });
688
- const metadata = enrichMetadataWithPrompt(args.name, {
689
- ...(args.metadata && typeof args.metadata === 'object' ? args.metadata : {}),
690
- ...runtimePromptMetadata,
691
- workflowId: ctx.workflowId,
692
- workflowPhase: ctx.workflowPhase,
693
- workflowOwner: ctx.workflowOwner,
694
- release: ctx.release,
695
- }, { rootDir: ROOT_DIR });
696
- const traceId = args.id ?? crypto.randomUUID();
697
- try {
698
- const available = await langfuse.isAvailable();
699
- if (!available) return { ok: false, error: 'Langfuse credentials not configured', id: traceId };
700
- const teamId = args.metadata?.teamId ?? metadata.teamId;
701
- const body = {
702
- id: traceId,
703
- name: args.name,
704
- metadata: {
705
- ...metadata,
706
- agentName: args.name,
707
- goal: typeof args.input === 'string' ? args.input : JSON.stringify(args.input ?? ''),
708
- teamId,
709
- },
710
- tags: [args.name, teamId].filter(Boolean),
711
- userId: ctx.userId,
712
- sessionId: args.session_id || ctx.sessionId,
713
- input: args.input,
714
- output: args.output,
715
- timestamp: args.timestamp ?? new Date().toISOString(),
716
- release: ctx.release,
717
- };
718
- const res = await fetch(`${langfuseBaseUrl()}/api/public/traces`, {
719
- method: 'POST',
720
- headers: langfuseHeaders(),
721
- body: JSON.stringify(body),
722
- });
723
- if (!res.ok) {
724
- const text = await res.text().catch(() => '');
725
- return { ok: false, error: `Langfuse API error ${res.status}: ${text}`, id: traceId };
726
- }
727
- return { ok: true, id: traceId };
728
- } catch (err) {
729
- return { ok: false, error: err.message, id: traceId };
730
- }
731
- }
27
+ import {
28
+ agentHealth, summarizeDiff, scanFile, projectContext, workflowStatus,
29
+ } from './tools/project.mjs';
30
+ import {
31
+ extractDocumentText, ingestDocument, inferDocumentSchemaTool, listSchemaArtifactsTool,
32
+ } from './tools/document.mjs';
33
+ import {
34
+ storageStatus, storageSync, storageReset, deleteIngestedArtifactsTool,
35
+ } from './tools/storage.mjs';
36
+ import {
37
+ listSkills, getSkill, searchSkills, getTemplate, listTemplates,
38
+ agentContract, brokerCheck, orchestrationPolicy, workerRun, listTeams, getTeam,
39
+ } from './tools/skills.mjs';
40
+ import {
41
+ workflowInit, workflowAddTask, workflowUpdateTask,
42
+ workflowNeedsMainInput, workflowValidate, workflowImportPlan,
43
+ } from './tools/workflow.mjs';
44
+ import {
45
+ cxTrace, cxTraceUpdate, cxScore, sessionUsage, efficiencySnapshot,
46
+ } from './tools/telemetry.mjs';
47
+ import {
48
+ memorySearch, memoryAddObservations, memoryCreateEntities, memoryRecent,
49
+ sessionList, sessionLoad, sessionSearch, sessionSave, rovoSearch,
50
+ } from './tools/memory.mjs';
732
51
 
733
- async function cxScore(args) {
734
- const traceId = args.trace_id ?? '';
735
- try {
736
- const available = await langfuse.isAvailable();
737
- if (!available) return { ok: false, error: 'Langfuse credentials not configured' };
738
- const body = {
739
- id: crypto.randomUUID(),
740
- traceId,
741
- name: args.name ?? 'quality',
742
- value: args.value,
743
- dataType: 'NUMERIC',
744
- comment: args.comment,
745
- };
746
- const res = await fetch(`${langfuseBaseUrl()}/api/public/scores`, {
747
- method: 'POST',
748
- headers: langfuseHeaders(),
749
- body: JSON.stringify(body),
750
- });
751
- if (!res.ok) {
752
- const text = await res.text().catch(() => '');
753
- return { ok: false, error: `Langfuse API error ${res.status}: ${text}` };
754
- }
755
- return { ok: true, traceId };
756
- } catch (err) {
757
- return { ok: false, error: err.message };
758
- }
759
- }
52
+ const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
53
+ const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
54
+ loadToolkitEnv(ROOT_DIR);
760
55
 
761
- async function sessionUsage(args) {
762
- const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
763
- const homeDir = args.home_dir ? resolve(args.home_dir) : homedir();
764
- const status = await buildStatus({ rootDir: ROOT_DIR, cwd, homeDir, env: process.env });
765
- return {
766
- cwd,
767
- sessionUsage: status.sessionUsage,
768
- };
769
- }
56
+ const opts = { ROOT_DIR };
770
57
 
771
- // Server setup
772
58
  const server = new Server(
773
59
  { name: 'construct-mcp', version: '1.0.0' },
774
60
  { capabilities: { tools: {} } }
@@ -820,6 +106,198 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
820
106
  required: ['file_path'],
821
107
  },
822
108
  },
109
+ {
110
+ name: 'extract_document_text',
111
+ description: 'Extracts readable text from a local document path. Supports PDF on macOS plus common text and office document formats.',
112
+ inputSchema: {
113
+ type: 'object',
114
+ properties: {
115
+ file_path: {
116
+ type: 'string',
117
+ description: 'Absolute or relative path to the document file.',
118
+ },
119
+ max_chars: {
120
+ type: 'number',
121
+ description: 'Maximum characters to return (default 20000, hard cap 200000).',
122
+ },
123
+ },
124
+ required: ['file_path'],
125
+ },
126
+ },
127
+ {
128
+ name: 'ingest_document',
129
+ description: 'Converts a local document into a normalized markdown file, placing it into an indexed project path by default.',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {
133
+ file_path: {
134
+ type: 'string',
135
+ description: 'Absolute or relative path to the source document.',
136
+ },
137
+ out_path: {
138
+ type: 'string',
139
+ description: 'Optional explicit markdown output path.',
140
+ },
141
+ out_dir: {
142
+ type: 'string',
143
+ description: 'Optional directory for generated markdown output files.',
144
+ },
145
+ target: {
146
+ type: 'string',
147
+ description: 'Output mode: knowledge/internal, knowledge/external, knowledge/decisions, knowledge/how-tos, knowledge/reference, or sibling. Defaults to knowledge/internal.',
148
+ },
149
+ cwd: {
150
+ type: 'string',
151
+ description: 'Project root used to resolve default output paths and storage sync.',
152
+ },
153
+ sync: {
154
+ type: 'boolean',
155
+ description: 'When true, sync file-state into configured SQL/vector storage after writing output.',
156
+ },
157
+ },
158
+ required: ['file_path'],
159
+ },
160
+ },
161
+ {
162
+ name: 'infer_document_schema',
163
+ description: 'Infers a structured field schema from a local document using AI. Returns field names, types, formats, examples, and confidence. Supports all document types handled by extract_document_text. Pass multiple file_paths to get a reconciled unified schema across documents.',
164
+ inputSchema: {
165
+ type: 'object',
166
+ properties: {
167
+ file_path: {
168
+ type: 'string',
169
+ description: 'Absolute or relative path to the document file. For unified inference across multiple documents, use file_paths instead.',
170
+ },
171
+ file_paths: {
172
+ type: 'array',
173
+ items: { type: 'string' },
174
+ description: 'Multiple document paths for unified schema inference. Reconciles fields across all documents.',
175
+ },
176
+ max_chars: {
177
+ type: 'number',
178
+ description: 'Maximum characters of document text to send to the model (default 40000, hard cap 200000).',
179
+ },
180
+ save: {
181
+ type: 'boolean',
182
+ description: 'When true, write the schema result as a .schema.json artifact under .cx/knowledge/reference/schemas/.',
183
+ },
184
+ cwd: {
185
+ type: 'string',
186
+ description: 'Project root used to resolve output paths when save is true.',
187
+ },
188
+ sample_size: {
189
+ type: 'number',
190
+ description: 'For unified inference: max number of documents to sample (default 10).',
191
+ },
192
+ threshold: {
193
+ type: 'number',
194
+ description: 'For unified inference: minimum fraction of documents a field must appear in to be included (default 0.5).',
195
+ },
196
+ },
197
+ },
198
+ },
199
+ {
200
+ name: 'list_schema_artifacts',
201
+ description: 'Lists all inferred schema artifacts (.schema.json files) in the project.',
202
+ inputSchema: {
203
+ type: 'object',
204
+ properties: {
205
+ cwd: {
206
+ type: 'string',
207
+ description: 'Project directory to search (default: process.cwd()).',
208
+ },
209
+ },
210
+ },
211
+ },
212
+ {
213
+ name: 'storage_status',
214
+ description: 'Returns SQL, local vector index, and ingested-artifact status for the current project.',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ cwd: {
219
+ type: 'string',
220
+ description: 'Project directory to inspect.',
221
+ },
222
+ project: {
223
+ type: 'string',
224
+ description: 'Optional explicit project key for SQL document counts.',
225
+ },
226
+ },
227
+ },
228
+ },
229
+ {
230
+ name: 'storage_sync',
231
+ description: 'Syncs file-state documents into the local vector index and configured SQL storage.',
232
+ inputSchema: {
233
+ type: 'object',
234
+ properties: {
235
+ cwd: {
236
+ type: 'string',
237
+ description: 'Project directory to sync.',
238
+ },
239
+ project: {
240
+ type: 'string',
241
+ description: 'Optional explicit project key.',
242
+ },
243
+ },
244
+ },
245
+ },
246
+ {
247
+ name: 'storage_reset',
248
+ description: 'Resets SQL/vector storage state for a project. Requires explicit confirm=true.',
249
+ inputSchema: {
250
+ type: 'object',
251
+ properties: {
252
+ cwd: {
253
+ type: 'string',
254
+ description: 'Project directory whose storage should be reset.',
255
+ },
256
+ project: {
257
+ type: 'string',
258
+ description: 'Optional explicit project key.',
259
+ },
260
+ reset_sql: {
261
+ type: 'boolean',
262
+ description: 'Set false to keep SQL state intact.',
263
+ },
264
+ reset_vector: {
265
+ type: 'boolean',
266
+ description: 'Set false to keep the local vector index intact.',
267
+ },
268
+ reset_ingested: {
269
+ type: 'boolean',
270
+ description: 'Set true to also delete ingested markdown artifacts under .cx/knowledge/.',
271
+ },
272
+ confirm: {
273
+ type: 'boolean',
274
+ description: 'Must be true or the reset is rejected.',
275
+ },
276
+ },
277
+ },
278
+ },
279
+ {
280
+ name: 'delete_ingested_artifacts',
281
+ description: 'Deletes ingested markdown artifacts. Requires explicit confirm=true and only allows files under the ingested artifact directory.',
282
+ inputSchema: {
283
+ type: 'object',
284
+ properties: {
285
+ cwd: {
286
+ type: 'string',
287
+ description: 'Project directory whose ingested artifacts should be deleted.',
288
+ },
289
+ files: {
290
+ type: 'array',
291
+ description: 'Optional relative file paths under .cx/knowledge/. Omit to delete all ingested markdown artifacts.',
292
+ items: { type: 'string' },
293
+ },
294
+ confirm: {
295
+ type: 'boolean',
296
+ description: 'Must be true or deletion is rejected.',
297
+ },
298
+ },
299
+ },
300
+ },
823
301
  {
824
302
  name: 'project_context',
825
303
  description: 'Returns project context: .cx/context.md content, recent commits, and working tree status.',
@@ -846,7 +324,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
846
324
  },
847
325
  {
848
326
  name: 'orchestration_policy',
849
- description: 'Classifies a request into intent, execution track, specialists, and approval boundaries.',
327
+ description: 'Classifies a request into intent, execution track, specialists, approval boundaries, and the contract chain that applies. The contractChain field names the typed producer→consumer handoffs expected for this dispatch plan. When `candidates` is supplied, also returns `contextPackets` keyed by specialist with the role-filtered artifact bundle each specialist should receive (omitted artifacts include the reason they were dropped).',
850
328
  inputSchema: {
851
329
  type: 'object',
852
330
  properties: {
@@ -866,6 +344,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
866
344
  blockedDependency: { type: 'boolean' },
867
345
  },
868
346
  },
347
+ candidates: {
348
+ type: 'array',
349
+ description: 'Pre-retrieved artifacts to consider for each specialist. Each item: {id, path, title, kind, summary, score}. When supplied, the response includes `contextPackets` keyed by specialist.',
350
+ items: { type: 'object' },
351
+ },
352
+ budget: {
353
+ type: 'object',
354
+ description: 'Token budget for the per-specialist context packet (default: 6000).',
355
+ properties: { maxTokens: { type: 'number' } },
356
+ },
357
+ triage: {
358
+ type: 'object',
359
+ description: 'Optional R&D triage block from a related intake packet — surfaced in each specialist\'s taskSummary.',
360
+ },
361
+ intakeId: {
362
+ type: 'string',
363
+ description: 'Optional id of a pending intake packet. When supplied, the tool generates a task graph from the packet\'s triage, persists it to .cx/task-graphs/, emits a task_graph.created trace event correlated by traceId, and returns the graph in `taskGraph`.',
364
+ },
365
+ project: {
366
+ type: 'string',
367
+ description: 'Project scope for the generated task graph (defaults to basename(cwd)).',
368
+ },
369
+ traceId: {
370
+ type: 'string',
371
+ description: 'Optional traceId to link task_graph.created with the originating intake.received event.',
372
+ },
869
373
  },
870
374
  },
871
375
  },
@@ -877,6 +381,59 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
877
381
  properties: {},
878
382
  },
879
383
  },
384
+ {
385
+ name: 'worker_run',
386
+ description: 'Runs a bounded shell command via the worker plane: timeout, path-policy denial, restricted env, stdout/stderr artifacts under `.cx/runtime/worker/<jobId>.{stdout,stderr}.log`. When `graphId` + `nodeId` are supplied, an evidence record is appended to that task graph node so it can transition to `done`. Emits `worker.started` / `worker.completed` / `evidence.recorded` trace events correlated by `traceId`.',
387
+ inputSchema: {
388
+ type: 'object',
389
+ properties: {
390
+ command: { type: 'string', description: 'Shell command to run (e.g. `npm test`).' },
391
+ args: { type: 'array', items: { type: 'string' }, description: 'Argv to pass alongside the command (when not embedded in the command string).' },
392
+ workspaceRef: { type: 'string', description: 'Absolute path the job runs in. Must be inside `allowedPaths` (default: cwd).' },
393
+ allowedPaths: { type: 'array', items: { type: 'string' }, description: 'Absolute path allowlist for the workspace. Default: [cwd] so the job can\'t reach outside the project.' },
394
+ timeoutSeconds: { type: 'number', description: 'Hard timeout. Default: 300.' },
395
+ envPolicy: { type: 'string', description: 'restricted (default — only PATH/HOME/USER/TZ/LANG/LC_ALL/TMPDIR plus allowedEnvKeys) | inherit.' },
396
+ allowedEnvKeys: { type: 'array', items: { type: 'string' }, description: 'Additional env keys allowed through under restricted policy.' },
397
+ graphId: { type: 'string', description: 'Optional task graph id — when present with `nodeId`, evidence is recorded on that node.' },
398
+ nodeId: { type: 'string', description: 'Optional task graph node id — when present with `graphId`, evidence is recorded.' },
399
+ evidenceType: { type: 'string', description: 'Evidence type (e.g. `test-result`, `lint-result`, `build-result`). Default: test-result.' },
400
+ evidenceSummary: { type: 'string', description: 'Optional override for the evidence summary string.' },
401
+ traceId: { type: 'string', description: 'Optional traceId to correlate with the rest of the agent\'s trace.' },
402
+ taskId: { type: 'string', description: 'Optional task id (used when graphId/nodeId aren\'t supplied — purely for trace context).' },
403
+ project: { type: 'string', description: 'Optional project scope.' },
404
+ jobId: { type: 'string', description: 'Optional job id. Default: worker-<ts>-<rand>.' },
405
+ },
406
+ required: ['command'],
407
+ },
408
+ },
409
+ {
410
+ name: 'broker_check',
411
+ description: 'Queries the MCP broker policy gate for a pending action without executing it. Agents call this BEFORE attempting a high-risk action so the response (allowed / approvalRequired / reason / source) can be surfaced to the user in the agent\'s voice rather than triggering a denial after the fact. In solo mode the broker is off by default — returns `brokerActive: false` with `allowed: true` so the call is cheap and agents don\'t waste tokens on an inactive gate. Always emits a `tool.called` trace event for audit-trail parity. Reads agents/role-manifests.json fence rules in team / enterprise mode.',
412
+ inputSchema: {
413
+ type: 'object',
414
+ properties: {
415
+ role: { type: 'string', description: 'Persona name (e.g. `engineer`, `security`). Must match a key in agents/role-manifests.json for team / enterprise mode.' },
416
+ project: { type: 'string', description: 'Optional project scope for the decision.' },
417
+ tool: { type: 'string', description: 'The tool the agent wants to invoke (e.g. `github`, `fs`).' },
418
+ action: { type: 'string', description: 'The action on that tool (e.g. `create_pr`, `edit:lib/foo.mjs`).' },
419
+ risk: { type: 'string', description: 'low | medium | high. high actions need approval for non-autonomous roles.' },
420
+ traceId: { type: 'string', description: 'Optional traceId to correlate this check with the rest of the agent\'s trace.' },
421
+ },
422
+ required: ['role', 'tool', 'action'],
423
+ },
424
+ },
425
+ {
426
+ name: 'agent_contract',
427
+ description: 'Looks up explicit agent-to-agent service contracts (from agents/contracts.json). Specialists should call this at the start of a handoff to see the expected input shape, preconditions, and what postconditions they must satisfy. Use without args to get all contracts; pass producer/consumer to narrow; pass id for a specific contract.',
428
+ inputSchema: {
429
+ type: 'object',
430
+ properties: {
431
+ id: { type: 'string', description: 'Exact contract id (e.g. "architect-to-engineer")' },
432
+ producer: { type: 'string', description: 'Producer agent name (e.g. "cx-architect"). Returns outgoing contracts.' },
433
+ consumer: { type: 'string', description: 'Consumer agent name (e.g. "cx-engineer"). Returns incoming contracts.' },
434
+ },
435
+ },
436
+ },
880
437
  {
881
438
  name: 'get_template',
882
439
  description: 'Reads a doc template by name (e.g. "prd", "meta-prd", "prfaq", "evidence-brief", "adr", "runbook"). Resolves .cx/templates/docs/{name}.md first, then templates/docs/{name}.md.',
@@ -905,162 +462,230 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
905
462
  },
906
463
  },
907
464
  {
908
- name: 'workflow_status',
909
- description: 'Returns Construct .cx/workflow.json state and alignment findings.',
465
+ name: 'list_teams',
466
+ description: 'Lists all available Construct team templates with members, focus, and promotion gates.',
467
+ inputSchema: { type: 'object', properties: {} },
468
+ },
469
+ {
470
+ name: 'get_team',
471
+ description: 'Returns the full definition of a named team template including members, skills, and promotion gates.',
472
+ inputSchema: {
473
+ type: 'object',
474
+ properties: {
475
+ name: { type: 'string', description: 'Team template name (e.g. feature, incident, architecture).' },
476
+ },
477
+ required: ['name'],
478
+ },
479
+ },
480
+ {
481
+ name: 'cx_trace',
482
+ description: 'Records an agent trace for observability. Call at the start of every significant task with your agent name and the user goal.',
483
+ inputSchema: {
484
+ type: 'object',
485
+ properties: {
486
+ name: { type: 'string', description: 'Agent name (e.g. cx-engineer)' },
487
+ id: { type: 'string', description: 'Optional trace UUID — auto-generated if omitted' },
488
+ session_id: { type: 'string', description: 'Session ID to group related spans' },
489
+ metadata: { type: 'object', description: 'Extra metadata (teamId, workflowId, etc.)' },
490
+ input: { type: ['string', 'object'], description: 'Agent goal or user request' },
491
+ output: { type: ['string', 'object'], description: 'Agent deliverable or response' },
492
+ timestamp: { type: 'string', description: 'ISO start time (default: now)' },
493
+ },
494
+ required: ['name'],
495
+ },
496
+ },
497
+ {
498
+ name: 'cx_score',
499
+ description: 'Attaches a quality score to a trace. Call after producing a significant deliverable.',
500
+ inputSchema: {
501
+ type: 'object',
502
+ properties: {
503
+ trace_id: { type: 'string', description: 'The trace ID returned by cx_trace' },
504
+ name: { type: 'string', description: 'Score name — use "quality"' },
505
+ value: { type: 'number', description: 'Score from 0.0 (poor) to 1.0 (excellent)' },
506
+ comment: { type: 'string', description: 'Brief explanation of the score' },
507
+ },
508
+ required: ['trace_id', 'name', 'value'],
509
+ },
510
+ },
511
+ {
512
+ name: 'cx_trace_update',
513
+ description: 'Updates an existing trace with output and metadata. Use when a trace was created early but the result becomes available later.',
514
+ inputSchema: {
515
+ type: 'object',
516
+ properties: {
517
+ trace_id: { type: 'string', description: 'The trace ID returned by cx_trace' },
518
+ output: { type: ['string', 'object'], description: 'Final output / deliverable content' },
519
+ metadata: { type: 'object', description: 'Additional metadata to merge into the trace' },
520
+ },
521
+ required: ['trace_id'],
522
+ },
523
+ },
524
+ {
525
+ name: 'session_list',
526
+ description: 'List construct sessions for the current project. Returns distilled session index entries with id, project, status, and summary.',
910
527
  inputSchema: {
911
528
  type: 'object',
912
529
  properties: {
913
530
  cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
531
+ status: { type: 'string', description: 'Filter by status: active, completed, closed.' },
532
+ limit: { type: 'number', description: 'Max results (default: 20).' },
914
533
  },
915
534
  },
916
535
  },
917
536
  {
918
- name: 'workflow_init',
919
- description: 'Creates .cx/workflow.json if missing and returns the workflow state.',
537
+ name: 'session_load',
538
+ description: 'Load a full distilled session record by ID. Returns summary, decisions, files changed, open questions, and task snapshot.',
920
539
  inputSchema: {
921
540
  type: 'object',
922
541
  properties: {
923
542
  cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
924
- title: { type: 'string', description: 'Workflow title.' },
925
- spec_ref: { type: 'string', description: 'Path to the governing spec/plan document (e.g. .cx/plans/feature-x.md).' },
543
+ session_id: { type: 'string', description: 'The session ID to load.' },
926
544
  },
545
+ required: ['session_id'],
927
546
  },
928
547
  },
929
548
  {
930
- name: 'workflow_add_task',
931
- description: 'Adds a Construct task packet to .cx/workflow.json.',
549
+ name: 'session_search',
550
+ description: 'Search sessions by keyword in summary or project name.',
932
551
  inputSchema: {
933
552
  type: 'object',
934
553
  properties: {
935
- cwd: { type: 'string' },
936
- key: { type: 'string' },
937
- title: { type: 'string' },
938
- phase: { type: 'string' },
939
- owner: { type: 'string' },
940
- files: { type: 'array', items: { type: 'string' } },
941
- readFirst: { type: 'array', items: { type: 'string' } },
942
- doNotChange: { type: 'array', items: { type: 'string' } },
943
- acceptanceCriteria: { type: 'array', items: { type: 'string' } },
944
- verification: { type: 'array', items: { type: 'string' } },
945
- dependsOn: { type: 'array', items: { type: 'string' } },
946
- overlays: { type: 'array', items: { type: 'string' } },
947
- challengeRequired: { type: 'boolean' },
948
- challengeStatus: { type: 'string' },
554
+ cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
555
+ query: { type: 'string', description: 'Search keyword.' },
949
556
  },
950
- required: ['title', 'owner', 'acceptanceCriteria'],
557
+ required: ['query'],
951
558
  },
952
559
  },
953
560
  {
954
- name: 'workflow_update_task',
955
- description: 'Updates a task status, owner, phase, note, or verification evidence in .cx/workflow.json.',
561
+ name: 'session_save',
562
+ description: 'Update the active session with distilled context: summary, decisions, files changed, open questions, task snapshot.',
956
563
  inputSchema: {
957
564
  type: 'object',
958
565
  properties: {
959
- cwd: { type: 'string' },
960
- key: { type: 'string' },
961
- status: { type: 'string' },
962
- owner: { type: 'string' },
963
- phase: { type: 'string' },
964
- note: { type: 'string' },
965
- verification: { type: 'array', items: { type: 'string' } },
966
- overlays: { type: 'array', items: { type: 'string' } },
967
- challengeRequired: { type: 'boolean' },
968
- challengeStatus: { type: 'string' },
566
+ cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
567
+ session_id: { type: 'string', description: 'The session ID to update.' },
568
+ summary: { type: 'string', description: 'Brief summary of what happened (2-3 sentences).' },
569
+ decisions: { type: 'array', items: { type: 'string' }, description: 'Key decisions made during the session.' },
570
+ files_changed: { type: 'array', items: { type: 'object', properties: { path: { type: 'string' }, reason: { type: 'string' } } }, description: 'Files modified with reasons.' },
571
+ open_questions: { type: 'array', items: { type: 'string' }, description: 'Unresolved questions or blockers.' },
572
+ task_snapshot: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, subject: { type: 'string' }, status: { type: 'string' } } }, description: 'Current task state.' },
573
+ status: { type: 'string', description: 'Session status: active, completed, closed.' },
969
574
  },
970
- required: ['key'],
575
+ required: ['session_id'],
971
576
  },
972
577
  },
973
578
  {
974
- name: 'workflow_needs_main_input',
975
- description: 'Records a blocked_needs_user task and returns a NEEDS_MAIN_INPUT packet for the primary persona.',
579
+ name: 'memory_search',
580
+ description: 'Search the observation store for patterns, decisions, and insights learned by specialists across sessions. Returns semantically matched observations scoped by role, category, or project.',
976
581
  inputSchema: {
977
582
  type: 'object',
978
583
  properties: {
979
- cwd: { type: 'string' },
980
- taskKey: { type: 'string' },
981
- worker: { type: 'string' },
982
- blocker: { type: 'string' },
983
- question: { type: 'string' },
984
- safeDefault: { type: 'string' },
985
- context: { type: 'array', items: { type: 'string' } },
584
+ query: { type: 'string', description: 'Semantic search query (e.g., project name, pattern, component).' },
585
+ role: { type: 'string', description: 'Filter by specialist role (e.g., cx-engineer, cx-architect).' },
586
+ category: { type: 'string', description: 'Filter by category: pattern, anti-pattern, dependency, decision, insight, session-summary.' },
587
+ project: { type: 'string', description: 'Filter by project name.' },
588
+ limit: { type: 'number', description: 'Max results (default: 10).' },
986
589
  },
987
- required: ['taskKey', 'worker', 'blocker', 'question'],
590
+ required: ['query'],
988
591
  },
989
592
  },
990
593
  {
991
- name: 'workflow_validate',
992
- description: 'Validates .cx/workflow.json schema and high-severity alignment findings.',
594
+ name: 'memory_add_observations',
595
+ description: 'Record observations (patterns, insights, decisions, anti-patterns) that specialists discover during work. These are indexed for semantic search and surface in future sessions.',
993
596
  inputSchema: {
994
597
  type: 'object',
995
598
  properties: {
996
- cwd: { type: 'string' },
599
+ observations: {
600
+ type: 'array',
601
+ items: {
602
+ type: 'object',
603
+ properties: {
604
+ role: { type: 'string', description: 'Specialist role (e.g., cx-engineer).' },
605
+ category: { type: 'string', description: 'Category: pattern, anti-pattern, dependency, decision, insight.' },
606
+ summary: { type: 'string', description: 'Brief summary (max 500 chars).' },
607
+ content: { type: 'string', description: 'Detailed observation (max 2000 chars).' },
608
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags for filtering.' },
609
+ confidence: { type: 'number', description: 'Confidence 0.0-1.0 (default: 0.8).' },
610
+ },
611
+ required: ['summary'],
612
+ },
613
+ description: 'Observations to record (max 10 per call).',
614
+ },
997
615
  },
616
+ required: ['observations'],
998
617
  },
999
618
  },
1000
619
  {
1001
- name: 'workflow_import_plan',
1002
- description: 'Parses a plan document (markdown) into structured workflow task packets and writes them to .cx/workflow.json. Supports rich ### T1 Title sections with Owner/Phase/Files/Depends on/Read first/Do not change/Acceptance criteria fields.',
620
+ name: 'memory_create_entities',
621
+ description: 'Track recurring entities (components, services, APIs, dependencies) that specialists encounter. Enables "what do we know about X?" queries.',
1003
622
  inputSchema: {
1004
623
  type: 'object',
1005
624
  properties: {
1006
- markdown: { type: 'string', description: 'The plan document markdown to parse into tasks' },
1007
- cwd: { type: 'string', description: 'Project directory (default: cwd)' },
1008
- title: { type: 'string', description: 'Workflow title if creating a new workflow' },
1009
- spec_ref: { type: 'string', description: 'Path to the governing spec/plan document (e.g. .cx/plans/feature-x.md)' },
1010
- phase: { type: 'string', description: 'Default phase for tasks without an explicit phase (default: implement)' },
1011
- owner: { type: 'string', description: 'Default owner for tasks without an explicit owner (default: cx-engineer)' },
1012
- readFirst: { type: 'array', items: { type: 'string' }, description: 'Default readFirst list applied to tasks without one' },
1013
- doNotChange: { type: 'array', items: { type: 'string' }, description: 'Default doNotChange list applied to all tasks' },
1014
- acceptanceCriteria: { type: 'array', items: { type: 'string' }, description: 'Extra acceptance criteria appended to each task' },
625
+ entities: {
626
+ type: 'array',
627
+ items: {
628
+ type: 'object',
629
+ properties: {
630
+ name: { type: 'string', description: 'Entity name (normalized to lowercase).' },
631
+ type: { type: 'string', description: 'Type: component, service, dependency, api, concept, file-group.' },
632
+ summary: { type: 'string', description: 'Brief description (max 500 chars).' },
633
+ observation_ids: { type: 'array', items: { type: 'string' }, description: 'Link to observation IDs.' },
634
+ },
635
+ required: ['name'],
636
+ },
637
+ description: 'Entities to create or update (max 10 per call).',
638
+ },
1015
639
  },
1016
- required: ['markdown'],
640
+ required: ['entities'],
1017
641
  },
1018
642
  },
1019
643
  {
1020
- name: 'list_teams',
1021
- description: 'Lists all available Construct team templates with members, focus, and promotion gates.',
1022
- inputSchema: { type: 'object', properties: {} },
644
+ name: 'memory_recent',
645
+ description: 'Returns the most recent observations for the current project, deduplicated by (role, summary). Use this when the session-start hint indicates prior observations are available — fetch them on demand instead of paying for them every session.',
646
+ inputSchema: {
647
+ type: 'object',
648
+ properties: {
649
+ cwd: { type: 'string', description: 'Project directory (default: process.cwd()).' },
650
+ project: { type: 'string', description: 'Filter by project name (default: inferred from cwd).' },
651
+ limit: { type: 'number', description: 'Max distinct observations (default: 10, max: 50).' },
652
+ },
653
+ },
1023
654
  },
1024
655
  {
1025
- name: 'get_team',
1026
- description: 'Returns the full definition of a named team template including members, skills, and promotion gates.',
656
+ name: 'rovo_search',
657
+ description: 'Cross-system semantic search via Atlassian Rovo. Searches Jira, Confluence, and other accessible sources. Returns excerpts with source attribution. Does NOT store results in the observation store. Use for broad queries across all sources, not just focal projects.',
1027
658
  inputSchema: {
1028
659
  type: 'object',
1029
660
  properties: {
1030
- name: { type: 'string', description: 'Team template name (e.g. feature, incident, architecture).' },
661
+ query: { type: 'string', description: 'Search query (e.g., "Iverson reliability", "DR failover").' },
662
+ top_k: { type: 'number', description: 'Max results (default: 10, max: 50).' },
663
+ sources: { type: 'string', description: 'Comma-separated source filter (e.g., "jira,confluence").' },
1031
664
  },
1032
- required: ['name'],
665
+ required: ['query'],
1033
666
  },
1034
667
  },
1035
668
  {
1036
- name: 'cx_trace',
1037
- description: 'Records an agent trace in Langfuse for observability. Call at the start of every significant task with your agent name and the user goal.',
669
+ name: 'rovo_search',
670
+ description: 'Cross-system semantic search via Atlassian Rovo. Searches Jira, Confluence, and other accessible sources. Returns excerpts with source attribution. Does NOT store results in the observation store. Use for broad queries across all sources, not just focal projects.',
1038
671
  inputSchema: {
1039
672
  type: 'object',
1040
673
  properties: {
1041
- name: { type: 'string', description: 'Agent name (e.g. cx-engineer)' },
1042
- id: { type: 'string', description: 'Optional trace UUID auto-generated if omitted' },
1043
- session_id: { type: 'string', description: 'Session ID to group related spans' },
1044
- metadata: { type: 'object', description: 'Extra metadata (teamId, workflowId, etc.)' },
1045
- input: { type: ['string', 'object'], description: 'Agent goal or user request' },
1046
- output: { type: ['string', 'object'], description: 'Agent deliverable or response' },
1047
- timestamp: { type: 'string', description: 'ISO start time (default: now)' },
674
+ query: { type: 'string', description: 'Search query (e.g., "Iverson reliability", "DR failover").' },
675
+ top_k: { type: 'number', description: 'Max results (default: 10, max: 50).' },
676
+ sources: { type: 'string', description: 'Comma-separated source filter (e.g., "jira,confluence").' },
1048
677
  },
1049
- required: ['name'],
678
+ required: ['query'],
1050
679
  },
1051
680
  },
1052
681
  {
1053
- name: 'cx_score',
1054
- description: 'Attaches a quality score to a trace in Langfuse. Call after producing a significant deliverable.',
682
+ name: 'efficiency_snapshot',
683
+ description: 'Returns the read-efficiency snapshot for the current session — repeated reads, large reads, hot-spot files, and recommendations. Use this when investigating why a session feels slow or to surface optimization opportunities.',
1055
684
  inputSchema: {
1056
685
  type: 'object',
1057
686
  properties: {
1058
- trace_id: { type: 'string', description: 'The trace ID returned by cx_trace' },
1059
- name: { type: 'string', description: 'Score name — use "quality"' },
1060
- value: { type: 'number', description: 'Score from 0.0 (poor) to 1.0 (excellent)' },
1061
- comment: { type: 'string', description: 'Brief explanation of the score' },
687
+ home_dir: { type: 'string', description: 'Home directory override (default: os.homedir()).' },
1062
688
  },
1063
- required: ['trace_id', 'name', 'value'],
1064
689
  },
1065
690
  },
1066
691
  {
@@ -1074,6 +699,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1074
699
  },
1075
700
  },
1076
701
  },
702
+ {
703
+ name: 'provider_fetch',
704
+ description: 'Look up current data for a configured repo, project, or team. This is an internal lookup against sources the operator has already authorized (set in config.env). Call this immediately — no user approval needed — whenever the user asks about a specific repo, project, or team name (e.g. "what is project iverson", "cloud-reliability status", "PLAT issues"). Pass the user\'s query and the tool resolves the right source automatically. Returns repo metadata, README, docs, open PRs, issues, and recent commits, then stores them as observations.',
705
+ inputSchema: {
706
+ type: 'object',
707
+ required: ['query'],
708
+ properties: {
709
+ query: { type: 'string', description: 'The user\'s question or the project/repo name (e.g. "project iverson", "hashicorp/project-iverson", "PLAT"). The tool matches this against configured GITHUB_REPOS, JIRA_PROJECTS, LINEAR_TEAMS.' },
710
+ root_dir: { type: 'string', description: 'Data root dir override (default: homedir()). Use CX_DATA_DIR value if set.' },
711
+ },
712
+ },
713
+ },
714
+ {
715
+ name: 'knowledge_search',
716
+ description: 'Search Construct\'s own documentation, knowledge base, and distilled embed observations. Call this immediately — no approval needed — when the user asks what Construct is, how a feature works, what commands exist, or anything about Construct\'s architecture or configuration. Also searches embed observations from GitHub, Jira, and other configured sources. Returns relevant excerpts with source file and heading.',
717
+ inputSchema: {
718
+ type: 'object',
719
+ required: ['query'],
720
+ properties: {
721
+ query: { type: 'string', description: 'Natural-language question or keyword (e.g. "what is construct", "how does embed mode work", "provider authority guard", "slack configuration", "open Jira issues").' },
722
+ top_k: { type: 'number', description: 'Max excerpts to return (default: 5).' },
723
+ repo_root: { type: 'string', description: 'Repo root override (default: auto-detected from server location).' },
724
+ root_dir: { type: 'string', description: 'Data directory where .cx/observations/ lives (default: home directory). Pass this to search embed observations from a custom data dir.' },
725
+ },
726
+ },
727
+ },
1077
728
  ],
1078
729
  }));
1079
730
 
@@ -1085,25 +736,56 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1085
736
  if (name === 'agent_health') result = agentHealth(args);
1086
737
  else if (name === 'summarize_diff') result = summarizeDiff(args);
1087
738
  else if (name === 'scan_file') result = scanFile(args);
1088
- else if (name === 'project_context') result = projectContext(args);
1089
- else if (name === 'orchestration_policy') result = orchestrationPolicy(args);
1090
- else if (name === 'list_skills') result = listSkills();
1091
- else if (name === 'get_skill') result = getSkill(args);
1092
- else if (name === 'search_skills') result = searchSkills(args);
1093
- else if (name === 'get_template') result = getTemplate(args);
1094
- else if (name === 'list_templates') result = listTemplates();
1095
- else if (name === 'workflow_status') result = workflowStatus(args);
739
+ else if (name === 'extract_document_text') result = extractDocumentText(args);
740
+ else if (name === 'ingest_document') result = await ingestDocument(args);
741
+ else if (name === 'infer_document_schema') result = await inferDocumentSchemaTool(args);
742
+ else if (name === 'list_schema_artifacts') result = listSchemaArtifactsTool(args);
743
+ else if (name === 'storage_status') result = await storageStatus(args);
744
+ else if (name === 'storage_sync') result = await storageSync(args);
745
+ else if (name === 'storage_reset') result = await storageReset(args);
746
+ else if (name === 'delete_ingested_artifacts') result = deleteIngestedArtifactsTool(args);
747
+ else if (name === 'project_context') result = projectContext(args, opts);
748
+ else if (name === 'orchestration_policy') result = await orchestrationPolicy(args);
749
+ else if (name === 'list_skills') result = listSkills(opts);
750
+ else if (name === 'get_skill') result = getSkill(args, opts);
751
+ else if (name === 'search_skills') result = searchSkills(args, opts);
752
+ else if (name === 'get_template') result = getTemplate(args, opts);
753
+ else if (name === 'list_templates') result = listTemplates(opts);
754
+ else if (name === 'agent_contract') result = await agentContract(args);
755
+ else if (name === 'broker_check') result = await brokerCheck(args);
756
+ else if (name === 'worker_run') result = await workerRun(args);
757
+ else if (name === 'workflow_status') result = workflowStatus(args, opts);
1096
758
  else if (name === 'workflow_init') result = workflowInit(args);
1097
759
  else if (name === 'workflow_add_task') result = workflowAddTask(args);
1098
760
  else if (name === 'workflow_update_task') result = workflowUpdateTask(args);
1099
761
  else if (name === 'workflow_needs_main_input') result = workflowNeedsMainInput(args);
1100
762
  else if (name === 'workflow_validate') result = workflowValidate(args);
1101
763
  else if (name === 'workflow_import_plan') result = workflowImportPlan(args);
1102
- else if (name === 'list_teams') result = listTeams();
1103
- else if (name === 'get_team') result = getTeam(args);
1104
- else if (name === 'cx_trace') result = await cxTrace(args);
764
+ else if (name === 'list_teams') result = listTeams(opts);
765
+ else if (name === 'get_team') result = getTeam(args, opts);
766
+ else if (name === 'cx_trace_telemetry') { const m = await import('./tools/telemetry.mjs'); result = await m.cxTrace(args, opts); }
767
+ else if (name === 'cx_trace') result = await cxTrace(args, opts);
1105
768
  else if (name === 'cx_score') result = await cxScore(args);
1106
- else if (name === 'session_usage') result = await sessionUsage(args);
769
+ else if (name === 'cx_trace_update') result = await cxTraceUpdate(args);
770
+ else if (name === 'session_list') result = sessionList(args);
771
+ else if (name === 'session_load') result = sessionLoad(args);
772
+ else if (name === 'session_search') result = sessionSearch(args);
773
+ else if (name === 'session_save') result = sessionSave(args);
774
+ else if (name === 'memory_search') result = await memorySearch(args);
775
+ else if (name === 'memory_add_observations') result = memoryAddObservations(args);
776
+ else if (name === 'memory_create_entities') result = memoryCreateEntities(args);
777
+ else if (name === 'memory_recent') result = memoryRecent(args);
778
+ else if (name === 'rovo_search') result = await rovoSearch(args);
779
+ else if (name === 'efficiency_snapshot') result = efficiencySnapshot(args);
780
+ else if (name === 'session_usage') result = await sessionUsage(args, opts);
781
+ else if (name === 'provider_fetch') {
782
+ const { demandFetch } = await import('../embed/demand-fetch.mjs');
783
+ result = await demandFetch({ query: args.query, rootDir: args.root_dir });
784
+ }
785
+ else if (name === 'knowledge_search') {
786
+ const { knowledgeSearch } = await import('../knowledge/search.mjs');
787
+ result = knowledgeSearch({ query: args.query, topK: args.top_k, repoRoot: args.repo_root, rootDir: args.root_dir });
788
+ }
1107
789
  else result = { error: `Unknown tool: ${name}` };
1108
790
  } catch (err) {
1109
791
  result = { error: err.message ?? String(err) };
@@ -1114,9 +796,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1114
796
  };
1115
797
  });
1116
798
 
1117
- export { cxTrace, projectContext, workflowStatus };
1118
-
1119
- if (import.meta.url === `file://${process.argv[1]}`) {
799
+ const cxTraceBound = (args) => cxTrace(args, opts);
800
+ const projectContextBound = (args) => projectContext(args, opts);
801
+ const workflowStatusBound = (args) => workflowStatus(args, opts);
802
+
803
+ export {
804
+ cxTraceBound as cxTrace,
805
+ projectContextBound as projectContext,
806
+ workflowStatusBound as workflowStatus,
807
+ extractDocumentText,
808
+ ingestDocument,
809
+ inferDocumentSchemaTool,
810
+ listSchemaArtifactsTool,
811
+ storageStatus,
812
+ storageSync,
813
+ storageReset,
814
+ deleteIngestedArtifactsTool,
815
+ agentContract,
816
+ };
817
+
818
+ const argv1Real = (() => { try { return realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
819
+ if (fileURLToPath(import.meta.url) === argv1Real) {
1120
820
  console.error('[construct-mcp] server started');
1121
821
  const transport = new StdioServerTransport();
1122
822
  await server.connect(transport);