@geraldmaron/construct 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
package/lib/publish.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Single compound run: artifact release gate (default), detect tooling, export
5
5
  * markdown (pandoc-ext/diagram + Construct PDF template), optional VHS terminal
6
- * demo, optional Playwright dashboard demo. --strict requires toolchain and gate.
6
+ * demo, optional Playwright recording. --strict requires toolchain and gate.
7
7
  */
8
8
 
9
9
  import fs from 'node:fs';
@@ -11,7 +11,6 @@ import path from 'node:path';
11
11
  import { exportMarkdown, parsePublishFrontmatter, EXPORT_FORMATS } from './document-export.mjs';
12
12
  import { detectPublishPipeline } from './publish-tooling.mjs';
13
13
  import { runDemoRecord } from './demo.mjs';
14
- import { recordDashboardDemo } from './dashboard-demo.mjs';
15
14
  import { loadDemoRecording } from './demo-recording.mjs';
16
15
  import { recordPlaywrightDemo } from './playwright-demo.mjs';
17
16
  import { validateArtifactRelease } from './artifact-release-gate.mjs';
@@ -46,7 +45,6 @@ export function runPublish({
46
45
  format = 'pdf',
47
46
  outputPath,
48
47
  demos = [],
49
- dashboardDemos = [],
50
48
  recordings = [],
51
49
  figures = true,
52
50
  strict = true,
@@ -62,27 +60,26 @@ export function runPublish({
62
60
  return { ok: false, message: `Input not found: ${resolvedIn}` };
63
61
  }
64
62
 
63
+ const explicitDemos = demos.length > 0;
64
+ const explicitRecordings = recordings.length > 0;
65
65
  let demoNames = demos;
66
- let dashNames = dashboardDemos;
67
66
  let recordingNames = recordings;
68
- if (!demoNames.length && !dashNames.length && !recordingNames.length) {
67
+ if (!demoNames.length && !recordingNames.length) {
69
68
  const fm = parsePublishFrontmatter(resolvedIn);
70
69
  if (fm?.demo) demoNames = [fm.demo];
71
- if (fm?.dashboardDemo) dashNames = [fm.dashboardDemo];
72
70
  if (fm?.recording) recordingNames = [fm.recording];
73
71
  }
74
72
 
75
73
  const detection = detectPublishPipeline({
76
74
  format,
77
75
  includeFigures: figures,
78
- includeTerminalDemo: demoNames.length > 0 && !sourceOnly,
79
- includeDashboardDemo: (dashNames.length > 0 || recordingNames.length > 0) && !sourceOnly,
76
+ includeTerminalDemo: explicitDemos && demoNames.length > 0 && !sourceOnly,
80
77
  cwd,
81
78
  repoRoot,
82
79
  env,
83
80
  });
84
81
 
85
- const ledger = { export: null, demos: [], dashboardDemos: [], recordings: [], detection, gate: null };
82
+ const ledger = { export: null, demos: [], recordings: [], detection, gate: null };
86
83
  const resolvedType = artifactType || inferArtifactTypeFromPath(resolvedIn, { rootDir: cwd });
87
84
 
88
85
  if (gate && !sourceOnly && resolvedType) {
@@ -131,40 +128,33 @@ export function runPublish({
131
128
  }
132
129
 
133
130
  for (const name of demoNames) {
131
+ const demoRequired = explicitDemos && strict && !sourceOnly;
134
132
  const demoResult = runDemoRecord(name, {
135
133
  cwd,
136
134
  format: 'mp4',
137
- sourceOnly: sourceOnly || (!strict && !detection.steps?.terminalDemo?.present),
138
- required: strict && !sourceOnly,
135
+ sourceOnly: sourceOnly || (!demoRequired && !detection.steps?.terminalDemo?.present),
136
+ required: demoRequired,
139
137
  surface: 'tape',
140
138
  });
141
139
  ledger.demos.push(demoResult);
142
- if (strict && !sourceOnly && demoResult.required && !demoResult.ok) {
140
+ if (demoRequired && !demoResult.ok) {
143
141
  return { ok: false, strict: true, ledger, message: demoResult.message };
144
142
  }
145
143
  }
146
144
 
147
- for (const name of dashNames) {
148
- const dashResult = recordDashboardDemo(name, { cwd, repoRoot, env });
149
- ledger.dashboardDemos.push(dashResult);
150
- if (strict && !sourceOnly && !dashResult.ok) {
151
- return { ok: false, strict: true, ledger, message: dashResult.message };
152
- }
153
- }
154
-
155
145
  for (const name of recordingNames) {
156
146
  const recording = loadDemoRecording(name, { cwd, repoRoot: repoRoot || cwd });
157
147
  if (!recording) {
158
148
  const miss = { ok: false, name, message: `Recording not found: ${name}` };
159
149
  ledger.recordings.push(miss);
160
- if (strict && !sourceOnly) {
150
+ if (explicitRecordings && strict && !sourceOnly) {
161
151
  return { ok: false, strict: true, ledger, message: miss.message };
162
152
  }
163
153
  continue;
164
154
  }
165
155
  const recResult = recordPlaywrightDemo(recording, { cwd, repoRoot: repoRoot || cwd, env });
166
156
  ledger.recordings.push(recResult);
167
- if (strict && !sourceOnly && !recResult.ok) {
157
+ if (explicitRecordings && strict && !sourceOnly && !recResult.ok) {
168
158
  return { ok: false, strict: true, ledger, message: recResult.message };
169
159
  }
170
160
  }
@@ -191,7 +181,6 @@ Options:
191
181
  --output=<path> Output path (default: .cx/publish/<name>.<format>)
192
182
  --type=<doc-type> Artifact type for release gate (inferred from path when omitted)
193
183
  --demo=<name> Terminal VHS tape name (repeatable)
194
- --dashboard-demo=<name> Playwright demo spec basename (repeatable)
195
184
  --recording=<name> Playwright recording manifest (repeatable)
196
185
  --no-figures Skip pandoc-ext/diagram filter
197
186
  --no-gate Skip artifact release gate (maintainer escape hatch)
@@ -203,7 +192,6 @@ Options:
203
192
  Frontmatter (optional):
204
193
  publish:
205
194
  demo: resource-guard-rails
206
- dashboardDemo: cockpit-tour
207
195
  recording: agentic-platforms-prd
208
196
 
209
197
  Examples:
@@ -218,7 +206,6 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
218
206
  output: null,
219
207
  type: null,
220
208
  demos: [],
221
- dashboardDemos: [],
222
209
  figures: true,
223
210
  strict: true,
224
211
  gate: true,
@@ -245,8 +232,6 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
245
232
  if (arg.startsWith('--type=')) { options.type = arg.slice(7); continue; }
246
233
  if (arg === '--demo') { options.demos.push(argv[++i]); continue; }
247
234
  if (arg.startsWith('--demo=')) { options.demos.push(arg.slice(7)); continue; }
248
- if (arg === '--dashboard-demo') { options.dashboardDemos.push(argv[++i]); continue; }
249
- if (arg.startsWith('--dashboard-demo=')) { options.dashboardDemos.push(arg.slice(17)); continue; }
250
235
  if (!arg.startsWith('--')) options.input = arg;
251
236
  }
252
237
 
@@ -260,7 +245,6 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
260
245
  format: options.format,
261
246
  includeFigures: options.figures,
262
247
  includeTerminalDemo: options.demos.length > 0,
263
- includeDashboardDemo: options.dashboardDemos.length > 0,
264
248
  cwd,
265
249
  repoRoot,
266
250
  });
@@ -278,7 +262,6 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
278
262
  format: options.format,
279
263
  outputPath: options.output,
280
264
  demos: options.demos,
281
- dashboardDemos: options.dashboardDemos,
282
265
  figures: options.figures,
283
266
  strict: options.strict,
284
267
  gate: options.gate,
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import fs from 'node:fs';
21
+ import { loadRegistry } from '../registry/loader.mjs';
21
22
  import path from 'node:path';
22
23
  import { fileURLToPath } from 'node:url';
23
24
 
@@ -34,17 +35,21 @@ function escapeRegExp(value) {
34
35
  }
35
36
 
36
37
  function registryMcpServers() {
37
- const installed = path.join(constructDir(), 'specialists', 'registry.json');
38
- const bundled = fileURLToPath(new URL('../../specialists/registry.json', import.meta.url));
39
- for (const src of [installed, bundled]) {
38
+ for (const rootDir of [constructDir(), fileURLToPath(new URL('../..', import.meta.url))]) {
40
39
  try {
41
- const parsed = JSON.parse(fs.readFileSync(src, 'utf8'));
42
- if (parsed && parsed.mcpServers) return parsed.mcpServers;
43
- } catch {
44
- continue;
45
- }
40
+ const registry = loadRegistry({ rootDir, skipValidation: true });
41
+ if (registry?.mcpServers && Object.keys(registry.mcpServers).length > 0) {
42
+ return registry.mcpServers;
43
+ }
44
+ } catch { /* try next root */ }
45
+ }
46
+ try {
47
+ const templatePath = path.join(constructDir(), 'platforms', 'claude', 'settings.template.json');
48
+ const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
49
+ return template.mcpServers || {};
50
+ } catch {
51
+ return {};
46
52
  }
47
- return {};
48
53
  }
49
54
 
50
55
  function hasMcpTable(text, id) {
package/lib/reflect.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lib/reflect.mjs — construct reflect command implementation.
3
3
  *
4
- * Captures improvement feedback from chat sessions and updates Construct core
4
+ * Captures improvement feedback from host sessions and updates Construct core
5
5
  * knowledge base with actionable insights for continuous self-improvement.
6
6
  */
7
7
 
@@ -259,4 +259,4 @@ if (import.meta.url === `file://${process.argv[1]}`) {
259
259
  console.error(`Error: ${err.message}`);
260
260
  process.exit(1);
261
261
  });
262
- }
262
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * lib/registry/agent-manifest.mjs — load, regenerate, and verify the agent execution manifest.
3
+ *
4
+ * The manifest (registry/agent-manifest.json) declares the MCP-first tool surface
5
+ * agents should reach for and the OpenCode-first human entry. The structural
6
+ * facts must stay honest against live sources rather than drifting into
7
+ * hand-maintained copy:
8
+ * - the CORE tool set, which is the CORE_TOOL_NAMES Set literal in
9
+ * lib/mcp/server.mjs (that constant is not exported, so it is parsed from the
10
+ * source text — the single authority for what the MCP ListTools front-loads);
11
+ * - the long-tail dispatcher name `construct_call`;
12
+ * - the OpenCode human conversation entry (the non-interactive CLI remains the admin/headless substrate);
13
+ * - the MCP gap list, which is exactly the capabilities declaring surfaces.mcp.supported=false
14
+ * in registry/capabilities.json, with the CLI fallback taken from surfaces.cli.command.
15
+ *
16
+ * generateAgentManifest reconciles those source-derived facts onto the curated prose
17
+ * (tool `use` strings, rationales, credential guidance, gap reasons) carried by the
18
+ * committed manifest and emits canonical JSON, so the human-owned copy round-trips while
19
+ * any drift in a live-source fact is caught by the --check path in registry:generate-docs.
20
+ * parseCoreToolNames reads the server source and returns the declared set so callers and
21
+ * tests verify the manifest against the server instead of re-stating the list.
22
+ * Read-only by default: generateAgentManifest writes only when asked.
23
+ */
24
+
25
+ import { readFileSync, writeFileSync } from 'node:fs';
26
+ import { dirname, resolve } from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+
29
+ import { loadCapabilityRegistry } from './validate.mjs';
30
+
31
+ const HERE = dirname(fileURLToPath(import.meta.url));
32
+ const REPO_ROOT = resolve(HERE, '..', '..');
33
+
34
+ export const MANIFEST_PATH = resolve(REPO_ROOT, 'registry', 'agent-manifest.json');
35
+ export const MCP_SERVER_PATH = resolve(REPO_ROOT, 'lib', 'mcp', 'server.mjs');
36
+
37
+ export const LONG_TAIL_DISPATCH_TOOL = 'call';
38
+
39
+ export function loadAgentManifest(path = MANIFEST_PATH) {
40
+ return JSON.parse(readFileSync(path, 'utf8'));
41
+ }
42
+
43
+ // CORE_TOOL_NAMES is a `new Set([...])` literal of single-quoted tool names.
44
+ // Capture the array body between the brackets, then pull each quoted token so the
45
+ // parser tolerates reordering and reflow but fails loudly if the constant is gone.
46
+
47
+ export function parseCoreToolNames(source) {
48
+ const block = source.match(/const\s+CORE_TOOL_NAMES\s*=\s*new\s+Set\(\[([\s\S]*?)\]\)/);
49
+ if (!block) {
50
+ throw new Error('CORE_TOOL_NAMES Set literal not found in MCP server source');
51
+ }
52
+ const names = [...block[1].matchAll(/'([^']+)'/g)].map((m) => m[1]);
53
+ if (names.length === 0) {
54
+ throw new Error('CORE_TOOL_NAMES literal parsed to an empty set');
55
+ }
56
+ return names;
57
+ }
58
+
59
+ export function readCoreToolNames(path = MCP_SERVER_PATH) {
60
+ return parseCoreToolNames(readFileSync(path, 'utf8'));
61
+ }
62
+
63
+ // Structural gate shared by the test and any future generator: the manifest must
64
+ // front-load exactly the live CORE set, expose construct_call as the long-tail
65
+ // door, name OpenCode as the primary human conversation entry, and never mention
66
+ // the removed local-loop subcommand.
67
+
68
+ export function verifyAgentManifest(manifest, { coreToolNames } = {}) {
69
+ const errors = [];
70
+ const core = coreToolNames ?? readCoreToolNames();
71
+
72
+ const tools = manifest?.toolSurface;
73
+ if (!tools || typeof tools !== 'object') {
74
+ errors.push('toolSurface object missing');
75
+ } else {
76
+ const declaredCore = Array.isArray(tools.core) ? tools.core.map((t) => t.name) : null;
77
+ if (!declaredCore) {
78
+ errors.push('toolSurface.core must be an array of tool entries');
79
+ } else {
80
+ const missing = core.filter((n) => !declaredCore.includes(n));
81
+ const extra = declaredCore.filter((n) => !core.includes(n));
82
+ if (missing.length) errors.push(`toolSurface.core missing CORE_TOOL_NAMES: ${missing.join(', ')}`);
83
+ if (extra.length) errors.push(`toolSurface.core lists non-core tools: ${extra.join(', ')}`);
84
+ }
85
+ if (tools.longTail?.tool !== LONG_TAIL_DISPATCH_TOOL) {
86
+ errors.push(`toolSurface.longTail.tool must be ${LONG_TAIL_DISPATCH_TOOL}`);
87
+ }
88
+ }
89
+
90
+ const entry = manifest?.humanEntry;
91
+ if (!entry || entry.surface !== 'opencode' || entry.command !== 'opencode') {
92
+ errors.push('humanEntry must name OpenCode as the primary human conversation surface');
93
+ }
94
+
95
+ const removedLocalLoopCommand = 'construct' + ' ' + 'c' + 'hat';
96
+ if (JSON.stringify(manifest).includes(removedLocalLoopCommand)) {
97
+ errors.push('manifest must not reference the removed local-loop subcommand');
98
+ }
99
+
100
+ return { valid: errors.length === 0, errors };
101
+ }
102
+
103
+ // The MCP gap list is not editorial: it is exactly the capabilities that declare
104
+ // surfaces.mcp.supported=false. Derive id/name/cliFallback from the registry so a
105
+ // capability flipping its MCP support shows up as drift; keep the human-written reason.
106
+
107
+ function deriveMcpGaps({ rootDir = REPO_ROOT, curatedGaps = [] } = {}) {
108
+ const { capabilities = [] } = loadCapabilityRegistry({ rootDir });
109
+ const reasonById = new Map(curatedGaps.map((g) => [g.id, g.reason]));
110
+ return capabilities
111
+ .filter((cap) => cap?.surfaces?.mcp?.supported === false)
112
+ .map((cap) => {
113
+ const reason = reasonById.get(cap.id);
114
+ if (!reason) {
115
+ throw new Error(`agent-manifest: MCP gap "${cap.id}" has no curated reason in registry/agent-manifest.json`);
116
+ }
117
+ return {
118
+ id: cap.id,
119
+ name: cap.name ?? cap.id,
120
+ mcp: 'unsupported',
121
+ cliFallback: cap.surfaces?.cli?.command ?? null,
122
+ reason,
123
+ };
124
+ })
125
+ .sort((a, b) => a.id.localeCompare(b.id));
126
+ }
127
+
128
+ // Reconcile source-of-truth structure onto the curated prose: pull the CORE set from
129
+ // the live server source (preserving each tool's curated `use` by name), pin the
130
+ // long-tail dispatcher and the OpenCode human entry, and derive the MCP gap list from the
131
+ // capability registry. The committed manifest supplies the prose; live sources supply
132
+ // the facts. Emit canonical JSON so a correct file round-trips byte-for-byte.
133
+
134
+ export function generateAgentManifest({ rootDir = REPO_ROOT, write = true } = {}) {
135
+ const manifestPath = resolve(rootDir, 'registry', 'agent-manifest.json');
136
+ const serverPath = resolve(rootDir, 'lib', 'mcp', 'server.mjs');
137
+ const current = loadAgentManifest(manifestPath);
138
+ const coreToolNames = readCoreToolNames(serverPath);
139
+
140
+ const useByName = new Map(
141
+ (current.toolSurface?.core ?? []).map((t) => [t.name, t.use]),
142
+ );
143
+ const core = coreToolNames.map((name) => {
144
+ const use = useByName.get(name);
145
+ if (!use) {
146
+ throw new Error(`agent-manifest: core tool "${name}" has no curated use string in registry/agent-manifest.json`);
147
+ }
148
+ return { name, use };
149
+ });
150
+
151
+ const next = {
152
+ $schema: current.$schema,
153
+ version: current.version,
154
+ kind: current.kind,
155
+ description: current.description,
156
+ sources: current.sources,
157
+ toolSurface: {
158
+ ...current.toolSurface,
159
+ core,
160
+ longTail: {
161
+ ...current.toolSurface?.longTail,
162
+ tool: LONG_TAIL_DISPATCH_TOOL,
163
+ },
164
+ },
165
+ humanEntry: {
166
+ ...current.humanEntry,
167
+ surface: 'opencode',
168
+ command: 'opencode',
169
+ subcommand: null,
170
+ },
171
+ credentials: current.credentials,
172
+ mcpGaps: {
173
+ ...current.mcpGaps,
174
+ gaps: deriveMcpGaps({ rootDir, curatedGaps: current.mcpGaps?.gaps ?? [] }),
175
+ },
176
+ };
177
+
178
+ const content = `${JSON.stringify(next, null, 2)}\n`;
179
+ if (!write) {
180
+ const existing = readFileSync(manifestPath, 'utf8');
181
+ return { path: manifestPath, content, drift: existing !== content };
182
+ }
183
+ writeFileSync(manifestPath, content);
184
+ return { path: manifestPath, content, drift: false };
185
+ }
186
+
187
+ export function checkAgentManifestDrift({ rootDir = REPO_ROOT } = {}) {
188
+ const { drift, content } = generateAgentManifest({ rootDir, write: false });
189
+ return { drift, contentLength: content.length };
190
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * lib/registry/assemble.mjs — Runtime merge of modular org files into registry v3.
3
+ *
4
+ * Walks specialists/org/{groups,teams,specialists,contracts,policies}, merges
5
+ * .cx/org overlay fragments, and returns the assembled object for validation.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ function readJsonDir(dir) {
12
+ const out = {};
13
+ if (!fs.existsSync(dir)) return out;
14
+ for (const name of fs.readdirSync(dir)) {
15
+ if (!name.endsWith('.json')) continue;
16
+ const id = name.replace(/\.json$/, '');
17
+ const raw = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8'));
18
+ const entityId = raw.id || id;
19
+ const { id: _drop, ...rest } = raw;
20
+ out[entityId] = { id: entityId, ...rest };
21
+ }
22
+ return out;
23
+ }
24
+
25
+ function mergeOverlaySection(base, overlaySection) {
26
+ if (!overlaySection) return;
27
+ for (const [id, value] of Object.entries(overlaySection)) {
28
+ base[id] = { ...base[id], ...value };
29
+ }
30
+ }
31
+
32
+ function mergeOrgOverlay(orgRoot, overlayRoot) {
33
+ if (!fs.existsSync(overlayRoot)) return;
34
+ const sections = ['groups', 'teams', 'specialists', 'contracts', 'policies'];
35
+ for (const section of sections) {
36
+ const overlayDir = path.join(overlayRoot, section);
37
+ if (!fs.existsSync(overlayDir)) continue;
38
+ const baseDir = path.join(orgRoot, section === 'groups' ? 'groups' : section);
39
+ const base = readJsonDir(baseDir);
40
+ for (const name of fs.readdirSync(overlayDir)) {
41
+ if (!name.endsWith('.json')) continue;
42
+ const raw = JSON.parse(fs.readFileSync(path.join(overlayDir, name), 'utf8'));
43
+ const entityId = raw.id || name.replace(/\.json$/, '');
44
+ const { id: _drop, ...rest } = raw;
45
+ base[entityId] = { ...(base[entityId] || {}), id: entityId, ...rest };
46
+ }
47
+ for (const [id, value] of Object.entries(base)) {
48
+ writeMerged(baseDir, id, value);
49
+ }
50
+ }
51
+ }
52
+
53
+ function writeMerged() {
54
+ // overlay merge is in-memory only; no disk write
55
+ }
56
+
57
+ export function orgDirMtime(rootDir) {
58
+ const orgRoot = path.join(rootDir, 'specialists', 'org');
59
+ if (!fs.existsSync(orgRoot)) return 0;
60
+ let max = 0;
61
+ const walk = (dir) => {
62
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
63
+ const p = path.join(dir, ent.name);
64
+ if (ent.isDirectory()) walk(p);
65
+ else if (ent.name.endsWith('.json')) max = Math.max(max, fs.statSync(p).mtimeMs);
66
+ }
67
+ };
68
+ walk(orgRoot);
69
+ const overlay = path.join(rootDir, '.cx', 'org');
70
+ if (fs.existsSync(overlay)) walk(overlay);
71
+ return max;
72
+ }
73
+
74
+ export function assembleRegistry(rootDir) {
75
+ const orgRoot = path.join(rootDir, 'specialists', 'org');
76
+ const overlayRoot = path.join(rootDir, '.cx', 'org');
77
+
78
+ if (!fs.existsSync(orgRoot)) {
79
+ throw new Error(`Modular org not found: ${orgRoot}. Run node scripts/migrate-org-modular.mjs`);
80
+ }
81
+
82
+ const groups = readJsonDir(path.join(orgRoot, 'groups'));
83
+ const squads = readJsonDir(path.join(orgRoot, 'teams'));
84
+ const specialists = readJsonDir(path.join(orgRoot, 'specialists'));
85
+ const contracts = readJsonDir(path.join(orgRoot, 'contracts'));
86
+ const policies = readJsonDir(path.join(orgRoot, 'policies'));
87
+
88
+ if (fs.existsSync(overlayRoot)) {
89
+ for (const [id, val] of Object.entries(readJsonDir(path.join(overlayRoot, 'groups')))) {
90
+ groups[id] = { ...groups[id], ...val };
91
+ }
92
+ for (const [id, val] of Object.entries(readJsonDir(path.join(overlayRoot, 'teams')))) {
93
+ squads[id] = { ...squads[id], ...val };
94
+ }
95
+ for (const [id, val] of Object.entries(readJsonDir(path.join(overlayRoot, 'specialists')))) {
96
+ specialists[id] = { ...specialists[id], ...val };
97
+ }
98
+ mergeOverlaySection(contracts, readJsonDir(path.join(overlayRoot, 'contracts')));
99
+ mergeOverlaySection(policies, readJsonDir(path.join(overlayRoot, 'policies')));
100
+ }
101
+
102
+ const teams = { ...groups, ...squads };
103
+
104
+ for (const [specId, spec] of Object.entries(specialists)) {
105
+ if (!spec.team && spec.teamId) spec.team = spec.teamId;
106
+ if (!spec.teamId && spec.team) spec.teamId = spec.team;
107
+ specialists[specId] = spec;
108
+ }
109
+
110
+ return {
111
+ version: 3,
112
+ teams,
113
+ specialists,
114
+ contracts,
115
+ policies,
116
+ };
117
+ }
118
+
119
+ export function listTeamsFromRegistry(registry, { kind } = {}) {
120
+ const entries = Object.entries(registry.teams || {}).map(([id, team]) => ({ id, ...team }));
121
+ if (kind === 'group') return entries.filter((t) => t.kind === 'group');
122
+ if (kind === 'squad') return entries.filter((t) => t.kind === 'squad');
123
+ return entries;
124
+ }
125
+
126
+ export function listGroupsHierarchical(registry) {
127
+ const groups = listTeamsFromRegistry(registry, { kind: 'group' });
128
+ const squads = listTeamsFromRegistry(registry, { kind: 'squad' });
129
+ return groups.map((group) => ({
130
+ ...group,
131
+ squads: squads.filter((s) => s.groupId === group.id),
132
+ }));
133
+ }