@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
@@ -1,10 +1,99 @@
1
1
  /**
2
- * lib/registry/cli.mjs — CLI handlers for registry validate, status, and doc generation.
2
+ * lib/registry/cli.mjs — CLI handlers for registry validate, status, doc generation, and team/specialist lifecycle.
3
+ *
4
+ * Exports handlers for:
5
+ * - runRegistryStatus: Show unified registry contents
6
+ * - runRegistryValidate: Validate unified registry schema and invariants
7
+ * - runRegistryGenerateDocs: Generate capabilities.md from registry
8
+ * - runRegistryDiff: Show changes to unified registry vs. last commit
9
+ * - runRegistryPrune: List orphaned specialist prompts/skills
10
+ * - runTeamAdd: Wizard to add a new team
11
+ * - runTeamRemove: Remove a team with dependency checking
12
+ * - runSpecialistAdd: Wizard to add a new specialist to a team
13
+ * - runSpecialistRemove: Remove a specialist with contract checking
3
14
  */
4
15
 
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { execSync } from 'node:child_process';
5
19
  import { validateCapabilityRegistry, loadCapabilityRegistry } from './validate.mjs';
6
20
  import { generateCapabilitiesDoc, checkCapabilitiesDocDrift } from './generate-docs.mjs';
21
+ import { generateAgentManifest, checkAgentManifestDrift } from './agent-manifest.mjs';
7
22
  import { triageBoundOrphans, formatConsolidationProposalMarkdown } from './consolidation.mjs';
23
+ import { loadRegistry } from './loader.mjs';
24
+ import { validate as validateUnifiedRegistry } from './validator.mjs';
25
+ import { validateRegistry } from '../validator.mjs';
26
+ import { assembleRegistryAtGitRef, findTeamFile, removeOrgEntityFile } from './org-io.mjs';
27
+
28
+ // The modular org under specialists/org/ is the SSOT. A project may shadow
29
+ // fields via a .cx/unified-registry.json overlay; the runtime loader merges
30
+ // it, so validation must merge it the same way (overlay wins) and run the
31
+ // same invariants the loader enforces on read.
32
+
33
+ function mergeUnifiedOverlay(base, overlay) {
34
+ for (const section of ['teams', 'specialists', 'contracts', 'policies']) {
35
+ if (!overlay[section]) continue;
36
+ base[section] = base[section] || {};
37
+ for (const [id, value] of Object.entries(overlay[section])) {
38
+ base[section][id] = { ...base[section][id], ...value };
39
+ }
40
+ }
41
+ return base;
42
+ }
43
+
44
+ export async function runUnifiedRegistryValidate(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
45
+ const jsonOutput = args.includes('--json');
46
+ const orgPath = path.join(rootDir, 'specialists', 'org');
47
+ const overlayPath = path.join(rootDir, '.cx', 'unified-registry.json');
48
+
49
+ let registry;
50
+ try {
51
+ const { clearCache } = await import('./loader.mjs');
52
+ clearCache();
53
+ registry = loadRegistry({ rootDir });
54
+ } catch (err) {
55
+ const message = `Cannot assemble registry from ${orgPath}: ${err.message}`;
56
+ if (jsonOutput) println(JSON.stringify({ ok: false, registryPath: orgPath, errors: [{ id: 'unreadable', message }], warnings: [] }, null, 2));
57
+ else errorln(`✗ ${message}`);
58
+ return 1;
59
+ }
60
+
61
+ const overlayApplied = fs.existsSync(overlayPath);
62
+
63
+ const result = validateUnifiedRegistry(registry);
64
+
65
+ if (jsonOutput) {
66
+ println(JSON.stringify({
67
+ ok: result.ok,
68
+ registryPath: orgPath,
69
+ overlayPath: overlayApplied ? overlayPath : null,
70
+ teams: Object.keys(registry.teams || {}).length,
71
+ specialists: Object.keys(registry.specialists || {}).length,
72
+ contracts: Object.keys(registry.contracts || {}).length,
73
+ policies: Object.keys(registry.policies || {}).length,
74
+ errors: result.errors,
75
+ warnings: result.warnings,
76
+ }, null, 2));
77
+ return result.ok ? 0 : 1;
78
+ }
79
+
80
+ println(`Unified registry: ${orgPath} (runtime merge)`);
81
+ if (overlayApplied) println(`Overlay: ${overlayPath}`);
82
+ println(`Teams: ${Object.keys(registry.teams || {}).length} Specialists: ${Object.keys(registry.specialists || {}).length} Contracts: ${Object.keys(registry.contracts || {}).length} Policies: ${Object.keys(registry.policies || {}).length}`);
83
+ if (result.errors.length) {
84
+ errorln(`Errors (${result.errors.length}):`);
85
+ for (const e of result.errors) errorln(` ✗ ${e.id}: ${e.message}`);
86
+ }
87
+ if (result.warnings.length) {
88
+ println(`Warnings (${result.warnings.length}):`);
89
+ for (const w of result.warnings) println(` ⚠ ${w.id}: ${w.message}`);
90
+ }
91
+ if (result.ok && !result.warnings.length) println('✓ Unified registry valid');
92
+ else if (result.ok) println('✓ Unified registry valid (with warnings)');
93
+ else errorln('✗ Unified registry invalid');
94
+
95
+ return result.ok ? 0 : 1;
96
+ }
8
97
 
9
98
  export async function runRegistryStatus(args = [], { rootDir, println = console.log } = {}) {
10
99
  const jsonOutput = args.includes('--json');
@@ -35,6 +124,10 @@ export async function runRegistryStatus(args = [], { rootDir, println = console.
35
124
  }
36
125
 
37
126
  export async function runRegistryValidate(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
127
+ if (args.includes('--unified')) {
128
+ return runUnifiedRegistryValidate(args, { rootDir, println, errorln });
129
+ }
130
+
38
131
  const jsonOutput = args.includes('--json');
39
132
  const report = validateCapabilityRegistry({ rootDir });
40
133
 
@@ -62,16 +155,63 @@ export async function runRegistryValidate(args = [], { rootDir, println = consol
62
155
 
63
156
  export async function runRegistryGenerateDocs(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
64
157
  if (args.includes('--check')) {
65
- const { drift } = checkCapabilitiesDocDrift({ rootDir });
66
- if (drift) {
158
+ let failed = false;
159
+
160
+ const docsDrift = checkCapabilitiesDocDrift({ rootDir }).drift;
161
+ if (docsDrift) {
67
162
  errorln('capabilities.md drift — run `construct registry:generate-docs`');
68
- return 1;
163
+ failed = true;
164
+ } else {
165
+ println('capabilities.md is up to date');
69
166
  }
70
- println('capabilities.md is up to date');
71
- return 0;
167
+
168
+ const manifestDrift = checkAgentManifestDrift({ rootDir }).drift;
169
+ if (manifestDrift) {
170
+ errorln('agent-manifest.json drift — run `construct registry:generate-docs`');
171
+ failed = true;
172
+ } else {
173
+ println('agent-manifest.json is up to date');
174
+ }
175
+
176
+ return failed ? 1 : 0;
72
177
  }
73
178
  const out = generateCapabilitiesDoc({ rootDir });
74
179
  println(`Generated ${out}`);
180
+ const { path: manifestPath } = generateAgentManifest({ rootDir });
181
+ println(`Generated ${manifestPath}`);
182
+ return 0;
183
+ }
184
+
185
+ export async function runTeamList(args = [], { rootDir, println = console.log } = {}) {
186
+ const { loadRegistry, listTeams } = await import('./loader.mjs');
187
+ const kindFlag = args.indexOf('--kind');
188
+ const kind = kindFlag !== -1 ? args[kindFlag + 1] : null;
189
+ loadRegistry({ rootDir });
190
+ const teams = listTeams({ rootDir, kind });
191
+ if (args.includes('--json')) {
192
+ println(JSON.stringify(teams, null, 2));
193
+ return 0;
194
+ }
195
+ for (const t of teams) {
196
+ println(`${t.id.padEnd(28)} ${t.kind || 'squad'} ${t.name}`);
197
+ }
198
+ return 0;
199
+ }
200
+
201
+ export async function runTeamShow(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
202
+ const id = args.find((a) => !a.startsWith('--'));
203
+ if (!id) {
204
+ errorln('Usage: construct team show <team-id>');
205
+ return 1;
206
+ }
207
+ const { getTeam } = await import('./loader.mjs');
208
+ const team = getTeam(id, { rootDir });
209
+ if (!team) {
210
+ errorln(`Team not found: ${id}`);
211
+ return 1;
212
+ }
213
+ if (args.includes('--json')) println(JSON.stringify({ id, ...team }, null, 2));
214
+ else println(JSON.stringify({ id, ...team }, null, 2));
75
215
  return 0;
76
216
  }
77
217
 
@@ -80,3 +220,447 @@ export async function runRegistryConsolidationProposal(args = [], { rootDir, pri
80
220
  println(formatConsolidationProposalMarkdown(triage));
81
221
  return 0;
82
222
  }
223
+
224
+ export async function runRegistryDiff(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
225
+ const jsonOutput = args.includes('--json');
226
+
227
+ let currentRegistry;
228
+ let headRegistry;
229
+ try {
230
+ currentRegistry = loadRegistry({ rootDir });
231
+ } catch (err) {
232
+ errorln(`Error reading current registry: ${err.message}`);
233
+ return 1;
234
+ }
235
+
236
+ try {
237
+ headRegistry = assembleRegistryAtGitRef(rootDir, 'HEAD');
238
+ } catch (err) {
239
+ errorln(`No previous org snapshot at HEAD: ${err.message}`);
240
+ return 1;
241
+ }
242
+
243
+ const diffs = {
244
+ teamChanges: { added: [], removed: [], modified: [] },
245
+ specialistChanges: { added: [], removed: [], modified: [] },
246
+ contractChanges: { added: [], removed: [], modified: [] },
247
+ };
248
+
249
+ // Compare teams
250
+ const currentTeamIds = new Set(Object.keys(currentRegistry.teams || {}));
251
+ const headTeamIds = new Set(Object.keys(headRegistry.teams || {}));
252
+
253
+ for (const id of currentTeamIds) {
254
+ if (!headTeamIds.has(id)) {
255
+ diffs.teamChanges.added.push(id);
256
+ } else if (JSON.stringify(currentRegistry.teams[id]) !== JSON.stringify(headRegistry.teams[id])) {
257
+ diffs.teamChanges.modified.push(id);
258
+ }
259
+ }
260
+
261
+ for (const id of headTeamIds) {
262
+ if (!currentTeamIds.has(id)) {
263
+ diffs.teamChanges.removed.push(id);
264
+ }
265
+ }
266
+
267
+ // Compare specialists
268
+ const currentSpecIds = new Set(Object.keys(currentRegistry.specialists || {}));
269
+ const headSpecIds = new Set(Object.keys(headRegistry.specialists || {}));
270
+
271
+ for (const id of currentSpecIds) {
272
+ if (!headSpecIds.has(id)) {
273
+ diffs.specialistChanges.added.push(id);
274
+ } else if (JSON.stringify(currentRegistry.specialists[id]) !== JSON.stringify(headRegistry.specialists[id])) {
275
+ diffs.specialistChanges.modified.push(id);
276
+ }
277
+ }
278
+
279
+ for (const id of headSpecIds) {
280
+ if (!currentSpecIds.has(id)) {
281
+ diffs.specialistChanges.removed.push(id);
282
+ }
283
+ }
284
+
285
+ // Compare contracts
286
+ const currentContractIds = new Set(Object.keys(currentRegistry.contracts || {}));
287
+ const headContractIds = new Set(Object.keys(headRegistry.contracts || {}));
288
+
289
+ for (const id of currentContractIds) {
290
+ if (!headContractIds.has(id)) {
291
+ diffs.contractChanges.added.push(id);
292
+ } else if (JSON.stringify(currentRegistry.contracts[id]) !== JSON.stringify(headRegistry.contracts[id])) {
293
+ diffs.contractChanges.modified.push(id);
294
+ }
295
+ }
296
+
297
+ for (const id of headContractIds) {
298
+ if (!currentContractIds.has(id)) {
299
+ diffs.contractChanges.removed.push(id);
300
+ }
301
+ }
302
+
303
+ if (jsonOutput) {
304
+ println(JSON.stringify(diffs, null, 2));
305
+ return 0;
306
+ }
307
+
308
+ // Human-readable output
309
+ let hasChanges = false;
310
+
311
+ if (diffs.teamChanges.added.length || diffs.teamChanges.removed.length || diffs.teamChanges.modified.length) {
312
+ hasChanges = true;
313
+ println('Teams:');
314
+ for (const id of diffs.teamChanges.added) println(` + ${id}`);
315
+ for (const id of diffs.teamChanges.removed) println(` - ${id}`);
316
+ for (const id of diffs.teamChanges.modified) println(` ~ ${id}`);
317
+ }
318
+
319
+ if (diffs.specialistChanges.added.length || diffs.specialistChanges.removed.length || diffs.specialistChanges.modified.length) {
320
+ if (hasChanges) println('');
321
+ hasChanges = true;
322
+ println('Specialists:');
323
+ for (const id of diffs.specialistChanges.added) println(` + ${id}`);
324
+ for (const id of diffs.specialistChanges.removed) println(` - ${id}`);
325
+ for (const id of diffs.specialistChanges.modified) println(` ~ ${id}`);
326
+ }
327
+
328
+ if (diffs.contractChanges.added.length || diffs.contractChanges.removed.length || diffs.contractChanges.modified.length) {
329
+ if (hasChanges) println('');
330
+ hasChanges = true;
331
+ println('Contracts:');
332
+ for (const id of diffs.contractChanges.added) println(` + ${id}`);
333
+ for (const id of diffs.contractChanges.removed) println(` - ${id}`);
334
+ for (const id of diffs.contractChanges.modified) println(` ~ ${id}`);
335
+ }
336
+
337
+ if (!hasChanges) {
338
+ println('No changes to unified registry since last commit.');
339
+ }
340
+
341
+ return 0;
342
+ }
343
+
344
+ export async function runRegistryPrune(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
345
+ const jsonOutput = args.includes('--json');
346
+ const registry = loadRegistry({ rootDir });
347
+
348
+ const registeredSpecialists = new Set(Object.keys(registry.specialists || {}));
349
+ const registeredSkills = new Set();
350
+
351
+ // Collect all registered skill file paths from registry
352
+ for (const spec of Object.values(registry.specialists || {})) {
353
+ if (Array.isArray(spec.skills)) {
354
+ for (const skillPath of spec.skills) {
355
+ // Skills are stored as paths like "docs/prd-workflow", resolve to file path
356
+ registeredSkills.add(path.join(rootDir, 'skills', `${skillPath.split('/').pop()}.md`));
357
+ }
358
+ }
359
+ }
360
+
361
+ const orphaned = { prompts: [], skills: [] };
362
+
363
+ // Specialist prompts not registered appear in orphaned list.
364
+ const promptsDir = path.join(rootDir, 'specialists', 'prompts');
365
+ if (fs.existsSync(promptsDir)) {
366
+ const promptFiles = fs.readdirSync(promptsDir).filter(f => f.endsWith('.md'));
367
+ for (const file of promptFiles) {
368
+ const match = file.match(/^cx-(.+)\.md$/);
369
+ if (match) {
370
+ const id = `cx-${match[1]}`;
371
+ if (!registeredSpecialists.has(id)) {
372
+ orphaned.prompts.push(path.join('specialists', 'prompts', file));
373
+ }
374
+ }
375
+ }
376
+ }
377
+
378
+ // Skill files not referenced by any specialist are orphaned.
379
+ const skillsDir = path.join(rootDir, 'skills');
380
+ if (fs.existsSync(skillsDir)) {
381
+ const walkSkills = (dir, relPath = '') => {
382
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
383
+ for (const entry of entries) {
384
+ const fullPath = path.join(dir, entry.name);
385
+ const relFullPath = path.join(relPath, entry.name);
386
+
387
+ if (entry.isDirectory()) {
388
+ walkSkills(fullPath, relFullPath);
389
+ } else if (entry.name.endsWith('.md')) {
390
+ // A skill is registered if any specialist lists it in their skills array.
391
+ let found = false;
392
+ for (const spec of Object.values(registry.specialists || {})) {
393
+ if (Array.isArray(spec.skills)) {
394
+ for (const skillPath of spec.skills) {
395
+ if (skillPath.endsWith(relFullPath.replace(/\.md$/, '').replace(/\\/g, '/'))) {
396
+ found = true;
397
+ break;
398
+ }
399
+ }
400
+ }
401
+ if (found) break;
402
+ }
403
+ if (!found) {
404
+ orphaned.skills.push(path.join('skills', relFullPath));
405
+ }
406
+ }
407
+ }
408
+ };
409
+ walkSkills(skillsDir);
410
+ }
411
+
412
+ if (jsonOutput) {
413
+ println(JSON.stringify(orphaned, null, 2));
414
+ return 0;
415
+ }
416
+
417
+ if (orphaned.prompts.length === 0 && orphaned.skills.length === 0) {
418
+ println('No orphaned prompt or skill files found.');
419
+ return 0;
420
+ }
421
+
422
+ let hasOrphans = false;
423
+
424
+ if (orphaned.prompts.length) {
425
+ hasOrphans = true;
426
+ println('Orphaned specialist prompts:');
427
+ for (const file of orphaned.prompts) {
428
+ println(` rm ${file}`);
429
+ }
430
+ }
431
+
432
+ if (orphaned.skills.length) {
433
+ if (hasOrphans) println('');
434
+ hasOrphans = true;
435
+ println('Orphaned skills:');
436
+ for (const file of orphaned.skills) {
437
+ println(` rm ${file}`);
438
+ }
439
+ }
440
+
441
+ return 0;
442
+ }
443
+
444
+ export async function runTeamAdd(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
445
+ // Interactive wizard not available; requires terminal TTY and structured I/O.
446
+ // Fallback: guide user to manual JSON editing.
447
+ errorln('construct team add requires interactive terminal access.');
448
+ errorln('Edit specialists/org directly and run:');
449
+ errorln(' construct registry validate');
450
+ errorln('');
451
+ errorln('Expected format in teams object:');
452
+ errorln(JSON.stringify({
453
+ 'example-group': {
454
+ name: 'Example Group',
455
+ owner: 'example-owner',
456
+ roles: ['example-owner', 'example-member'],
457
+ decisionRights: ['decision-1', 'decision-2'],
458
+ forbiddenDecisions: ['forbidden-1'],
459
+ escalationPath: ['example-owner', 'rd-lead', 'orchestrator'],
460
+ charter: 'Team charter describing scope and responsibility.',
461
+ contact: { slack: '#example', email: 'example@company.com' },
462
+ },
463
+ }, null, 2));
464
+ return 1;
465
+ }
466
+
467
+ export async function runTeamRemove(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
468
+ if (args.length === 0) {
469
+ errorln('Usage: construct team remove <team-id> [--force]');
470
+ return 1;
471
+ }
472
+
473
+ const teamId = args[0];
474
+ const forceRemove = args.includes('--force');
475
+
476
+ let registry;
477
+ try {
478
+ registry = loadRegistry({ rootDir });
479
+ } catch (err) {
480
+ errorln(`Error loading registry: ${err.message}`);
481
+ return 1;
482
+ }
483
+
484
+ const team = registry.teams?.[teamId];
485
+ if (!team) {
486
+ errorln(`Team '${teamId}' not found in registry.`);
487
+ return 1;
488
+ }
489
+
490
+ // Check for policy references
491
+ const policyReferences = [];
492
+ for (const [policyId, policy] of Object.entries(registry.policies || {})) {
493
+ if (policy.teamOwner === teamId || (policy.requiresApprovalFrom || []).includes(teamId)) {
494
+ policyReferences.push(policyId);
495
+ }
496
+ }
497
+
498
+ // Check for contract team boundaries
499
+ const contractReferences = [];
500
+ for (const [contractId, contract] of Object.entries(registry.contracts || {})) {
501
+ if (contract.teamBoundary?.producerTeam === teamId || contract.teamBoundary?.consumerTeam === teamId) {
502
+ contractReferences.push(contractId);
503
+ }
504
+ }
505
+
506
+ if ((policyReferences.length || contractReferences.length) && !forceRemove) {
507
+ errorln(`Cannot remove team '${teamId}' — it is referenced by:`);
508
+ if (policyReferences.length) {
509
+ errorln(` Policies: ${policyReferences.join(', ')}`);
510
+ }
511
+ if (contractReferences.length) {
512
+ errorln(` Contracts: ${contractReferences.join(', ')}`);
513
+ }
514
+ errorln('');
515
+ errorln('Either reassign these policies/contracts first, or use --force to orphan them.');
516
+ return 1;
517
+ }
518
+
519
+ if (forceRemove) {
520
+ if (policyReferences.length) {
521
+ println(`WARNING: Orphaning ${policyReferences.length} policy reference(s): ${policyReferences.join(', ')}`);
522
+ }
523
+ if (contractReferences.length) {
524
+ println(`WARNING: Orphaning ${contractReferences.length} contract reference(s): ${contractReferences.join(', ')}`);
525
+ }
526
+ }
527
+
528
+ const specialistsOnTeam = Object.entries(registry.specialists || {})
529
+ .filter(([, spec]) => spec.team === teamId || spec.teamId === teamId)
530
+ .map(([id]) => id);
531
+
532
+ for (const specId of specialistsOnTeam) {
533
+ if (!removeOrgEntityFile(rootDir, 'specialists', specId)) {
534
+ errorln(`Could not remove specialist file for ${specId}`);
535
+ return 1;
536
+ }
537
+ println(`Removed specialist ${specId}`);
538
+ }
539
+
540
+ const teamFile = findTeamFile(rootDir, teamId);
541
+ if (!teamFile) {
542
+ errorln(`Could not locate org file for team '${teamId}'`);
543
+ return 1;
544
+ }
545
+ fs.unlinkSync(teamFile);
546
+
547
+ const { clearCache } = await import('./loader.mjs');
548
+ clearCache();
549
+ const validation = validateUnifiedRegistry(loadRegistry({ rootDir }));
550
+ if (!validation.ok) {
551
+ errorln('Validation failed after removal:');
552
+ for (const error of validation.errors) {
553
+ errorln(` ✗ ${error.id}: ${error.message}`);
554
+ }
555
+ return 1;
556
+ }
557
+
558
+ println(`✓ Team '${teamId}' and ${specialistsOnTeam.length} specialist(s) removed.`);
559
+ return 0;
560
+ }
561
+
562
+ export async function runSpecialistAdd(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
563
+ // Interactive wizard not available; requires terminal TTY and structured I/O.
564
+ // Fallback: guide user to manual JSON editing.
565
+ errorln('construct specialist add requires interactive terminal access.');
566
+ errorln('Edit specialists/org directly and run:');
567
+ errorln(' construct registry validate');
568
+ errorln('');
569
+ errorln('Expected format in specialists object:');
570
+ errorln(JSON.stringify({
571
+ 'cx-new-specialist': {
572
+ name: 'new-specialist',
573
+ displayName: 'New Specialist',
574
+ team: 'team-id',
575
+ role: 'specialist-role',
576
+ skills: ['docs/skill-1', 'docs/skill-2'],
577
+ modelTier: 'reasoning',
578
+ events: ['event.type'],
579
+ fence: {
580
+ allowedPaths: ['lib/**'],
581
+ allowedCommands: ['bd create'],
582
+ approvalRequired: ['commit'],
583
+ },
584
+ },
585
+ }, null, 2));
586
+ return 1;
587
+ }
588
+
589
+ export async function runSpecialistRemove(args = [], { rootDir, println = console.log, errorln = console.error } = {}) {
590
+ if (args.length === 0) {
591
+ errorln('Usage: construct specialist remove <specialist-id> [--force]');
592
+ return 1;
593
+ }
594
+
595
+ const specialistId = args[0];
596
+ const forceRemove = args.includes('--force');
597
+
598
+ let registry;
599
+ try {
600
+ registry = loadRegistry({ rootDir });
601
+ } catch (err) {
602
+ errorln(`Error loading registry: ${err.message}`);
603
+ return 1;
604
+ }
605
+
606
+ const specialist = registry.specialists?.[specialistId];
607
+ if (!specialist) {
608
+ errorln(`Specialist '${specialistId}' not found in registry.`);
609
+ return 1;
610
+ }
611
+
612
+ // Check for contract references
613
+ const contractReferences = [];
614
+ for (const [contractId, contract] of Object.entries(registry.contracts || {})) {
615
+ if (contract.producer === specialistId || contract.consumer === specialistId) {
616
+ contractReferences.push(contractId);
617
+ }
618
+ }
619
+
620
+ if (contractReferences.length && !forceRemove) {
621
+ errorln(`Cannot remove specialist '${specialistId}' — it is referenced by contracts:`);
622
+ errorln(` ${contractReferences.join(', ')}`);
623
+ errorln('');
624
+ errorln('Either reassign these contracts first, or use --force.');
625
+ return 1;
626
+ }
627
+
628
+ const teamId = specialist.team || specialist.teamId;
629
+ const teamSpecialists = Object.entries(registry.specialists || {})
630
+ .filter(([, spec]) => (spec.team || spec.teamId) === teamId)
631
+ .map(([id]) => id);
632
+
633
+ if (teamSpecialists.length === 1) {
634
+ println(`WARNING: This is the last specialist on team '${teamId}'. Team will be understaffed.`);
635
+ }
636
+
637
+ const team = registry.teams?.[teamId];
638
+ if (team?.owner === specialist.role) {
639
+ println(`WARNING: This specialist has the owner role for team '${teamId}'. Team will have no owner.`);
640
+ }
641
+
642
+ if (!removeOrgEntityFile(rootDir, 'specialists', specialistId)) {
643
+ errorln(`Could not locate org file for specialist '${specialistId}'`);
644
+ return 1;
645
+ }
646
+
647
+ const { clearCache } = await import('./loader.mjs');
648
+ clearCache();
649
+ const validation = validateUnifiedRegistry(loadRegistry({ rootDir }));
650
+ if (!validation.ok) {
651
+ errorln('Validation failed after removal:');
652
+ for (const error of validation.errors) {
653
+ errorln(` ✗ ${error.id}: ${error.message}`);
654
+ }
655
+ return 1;
656
+ }
657
+
658
+ println(`✓ Specialist '${specialistId}' removed.`);
659
+
660
+ const promptFile = path.join(rootDir, specialist.promptFile || `specialists/prompts/${specialistId}.md`);
661
+ if (fs.existsSync(promptFile)) {
662
+ println(` (orphaned prompt: rm ${path.relative(rootDir, promptFile)})`);
663
+ }
664
+
665
+ return 0;
666
+ }
@@ -8,6 +8,7 @@
8
8
  import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
+ import { loadRegistry } from '../registry/loader.mjs';
11
12
 
12
13
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
13
14
  const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..');
@@ -56,11 +57,11 @@ function collectProfileRoles(root) {
56
57
  * Classify bound-orphans for maintainer approval (bind / merge / composer / review).
57
58
  */
58
59
  export function triageBoundOrphans({ rootDir = REPO_ROOT } = {}) {
59
- const registry = JSON.parse(fs.readFileSync(path.join(rootDir, 'specialists', 'registry.json'), 'utf8'));
60
+ const registry = loadRegistry({ rootDir });
60
61
  const declared = new Set();
61
- for (const s of registry.specialists ?? []) for (const sk of s.skills ?? []) declared.add(sk);
62
+ for (const s of Object.values(registry.specialists ?? {})) for (const sk of s.skills ?? []) declared.add(sk);
62
63
 
63
- const specialistNames = new Set((registry.specialists ?? []).map((s) => s.name));
64
+ const specialistNames = new Set(Object.values(registry.specialists ?? {}).map((s) => s.name));
64
65
  const profileRoles = collectProfileRoles(rootDir);
65
66
  const all = collectSkillFiles(path.join(rootDir, 'skills'));
66
67
 
@@ -117,7 +118,7 @@ export function formatConsolidationProposalMarkdown(triage, { date = new Date().
117
118
  '## Summary',
118
119
  '',
119
120
  `- **${triage.fileCount}** skill files on disk`,
120
- `- **${triage.declaredCount}** declared in specialists/registry.json`,
121
+ `- **${triage.declaredCount}** declared in specialists/org`,
121
122
  `- **${triage.boundOrphanCount}** registry bound-orphans (not declared by any specialist)`,
122
123
  `- **${triage.composerReachableCount}** composer-reachable (B-composer — intentional via prompt composer)`,
123
124
  `- **${triage.trueOrphanCount}** true orphans (C-merge + D-review — need maintainer action)`,