@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/demo.mjs CHANGED
@@ -2,19 +2,23 @@
2
2
  * lib/demo.mjs — `construct demo` terminal recordings via VHS `.tape` files.
3
3
  *
4
4
  * Shipped tapes live in templates/demos/tapes/; project overrides in
5
- * .cx/demos/tapes/. Built-in scaffolds via `construct demo init --from=`.
6
- * Default run mode launches construct chat with a demo script; use `record` or
7
- * --surface=tape for VHS. Dashboard demos delegate to lib/dashboard-demo.mjs.
5
+ * .cx/demos/tapes/. Built-in scaffolds via `construct demo init --from=`;
6
+ * `--from-project` scaffolds a project demo plug-in (manifest + script + tape)
7
+ * under .cx/demos/ seeded from real project signals (see lib/demo-project.mjs).
8
+ * Default run mode writes or records a VHS tape; use `tour` for a linear
9
+ * guided walkthrough and `--surface=playwright` for recording manifests.
8
10
  */
9
11
 
10
12
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
11
13
  import { spawnSync } from 'node:child_process';
12
14
  import { join, resolve, dirname, basename, relative } from 'node:path';
13
15
  import { fileURLToPath } from 'node:url';
14
- import { listDemoScripts } from './demo-script.mjs';
16
+ import { listDemoScripts, loadDemoScript } from './demo-script.mjs';
15
17
  import { DEMO_SURFACES } from './demo-surface.mjs';
18
+ import { renderTour } from './demo-tour-renderer.mjs';
16
19
  import { listDemoRecordings, loadDemoRecording } from './demo-recording.mjs';
17
20
  import { recordPlaywrightDemo } from './playwright-demo.mjs';
21
+ import { scaffoldProjectDemo } from './demo-project.mjs';
18
22
 
19
23
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
20
24
 
@@ -219,7 +223,7 @@ export function initConstructCockpitRecording(name, { cwd, repoRoot = REPO_ROOT
219
223
  ...reference,
220
224
  name,
221
225
  title: reference.title,
222
- output: { format: 'mp4', path: `.cx/demos/dashboard/${name}.mp4` },
226
+ output: { format: 'mp4', path: `.cx/demos/${name}.mp4` },
223
227
  _reference: 'agentic-platforms-prd',
224
228
  };
225
229
  delete recording.sourcePath;
@@ -228,7 +232,7 @@ export function initConstructCockpitRecording(name, { cwd, repoRoot = REPO_ROOT
228
232
  return {
229
233
  ok: true,
230
234
  recordingPath: recPath,
231
- message: `Scaffolded Construct cockpit recording ${recPath} (references apps/dashboard spec)`,
235
+ message: `Scaffolded Construct cockpit recording ${recPath}`,
232
236
  };
233
237
  }
234
238
 
@@ -306,10 +310,6 @@ export function listProjectTapes({ cwd = process.cwd(), repoRoot = REPO_ROOT } =
306
310
  }
307
311
 
308
312
  export function resolveTapeSource(name, { cwd, repoRoot = REPO_ROOT, artifactPath } = {}) {
309
- if (name.startsWith('dashboard:')) {
310
- return { kind: 'dashboard', name: name.slice('dashboard:'.length) };
311
- }
312
-
313
313
  const tapePath = resolveTapeFile(name, { cwd, repoRoot });
314
314
  if (tapePath) {
315
315
  let source = readFileSync(tapePath, 'utf8');
@@ -402,10 +402,6 @@ export function runDemoRecord(name, {
402
402
  required = false,
403
403
  surface = null,
404
404
  } = {}) {
405
- if (name.startsWith('dashboard:')) {
406
- return { ok: false, name, message: 'Use recordDashboardDemo for dashboard: demos' };
407
- }
408
-
409
405
  const recording = loadDemoRecording(name, { cwd, repoRoot });
410
406
  const usePlaywright = surface !== 'tape'
411
407
  && (surface === 'playwright' || (recording && recording.engine === 'playwright'));
@@ -506,28 +502,28 @@ export function printHelp() {
506
502
  Commands:
507
503
  list List tapes, recordings, and demo scripts
508
504
  init <name> [--from=T] Scaffold tape or Playwright recording
505
+ init <name> --from-project Scaffold a project demo plug-in under .cx/demos/
509
506
  record <name> Record VHS tape or Playwright demo manifest
510
- <name> Run guided demo (default: construct chat)
507
+ tour [name] Linear, screen-reader-friendly guided tour
508
+ <name> Record or print the demo (default: tape)
511
509
 
512
- Default run uses construct chat with a demo script (templates/demos/scripts/).
513
- Falls back to web chat, VHS tape, Playwright, dashboard, or printed steps.
510
+ Default run uses VHS tape templates and falls back to printed script steps.
514
511
 
515
512
  Options:
516
513
  --surface=<s> ${surfaces}
517
- --model <id> Pin model for chat demo
518
- --web Open web chat (/chat/) instead of terminal
519
- --plain Linear chat renderer
520
- --free OpenRouter free-router mode
514
+ --accessible Screen-reader-friendly linear renderer (tour)
515
+ --skip-input Tour: auto-advance without waiting for Enter (headless/CI)
521
516
  --format <f> gif (default) | mp4 | webm
522
517
  --out <path> Output path (recording)
518
+ --from-project init: scaffold a project demo plug-in under .cx/demos/
523
519
  --source-only Tape: write .tape only; skip recording
524
520
  -h, --help Show this message
525
521
 
526
522
  Examples:
527
523
  construct demo agentic-platforms-prd
524
+ construct demo tour agentic-platforms-prd --accessible
528
525
  construct demo record agentic-platforms-prd --format mp4
529
526
  construct demo init marketing --from=nextjs-static
530
- construct demo dashboard:cockpit-tour
531
527
  `);
532
528
  }
533
529
 
@@ -539,22 +535,19 @@ function parseArgs(argv) {
539
535
  from: 'quickstart',
540
536
  command: null,
541
537
  name: null,
542
- surface: 'chat',
543
- model: null,
544
- plain: false,
538
+ surface: 'tape',
545
539
  accessible: false,
546
- web: false,
547
- free: false,
540
+ skipInput: false,
541
+ fromProject: false,
548
542
  };
549
543
  const positional = [];
550
544
  for (let i = 0; i < argv.length; i += 1) {
551
545
  const arg = argv[i];
552
546
  if (arg === '--help' || arg === '-h') { options.help = true; continue; }
547
+ if (arg === '--from-project') { options.fromProject = true; continue; }
553
548
  if (arg === '--source-only') { options.sourceOnly = true; continue; }
554
- if (arg === '--plain') { options.plain = true; continue; }
555
549
  if (arg === '--accessible') { options.accessible = true; continue; }
556
- if (arg === '--web') { options.web = true; continue; }
557
- if (arg === '--free') { options.free = true; continue; }
550
+ if (arg === '--skip-input') { options.skipInput = true; continue; }
558
551
  if (arg === '--format') { options.format = argv[++i]; continue; }
559
552
  if (arg.startsWith('--format=')) { options.format = arg.slice(9); continue; }
560
553
  if (arg === '--out') { options.out = argv[++i]; continue; }
@@ -563,13 +556,12 @@ function parseArgs(argv) {
563
556
  if (arg.startsWith('--from=')) { options.from = arg.slice(7); continue; }
564
557
  if (arg === '--surface') { options.surface = argv[++i]; continue; }
565
558
  if (arg.startsWith('--surface=')) { options.surface = arg.slice(10); continue; }
566
- if (arg === '--model') { options.model = argv[++i]; continue; }
567
- if (arg.startsWith('--model=')) { options.model = arg.slice(8); continue; }
568
559
  positional.push(arg);
569
560
  }
570
561
  if (positional[0] === 'list') options.command = 'list';
571
562
  else if (positional[0] === 'init') { options.command = 'init'; options.name = positional[1]; }
572
563
  else if (positional[0] === 'record') { options.command = 'record'; options.name = positional[1]; }
564
+ else if (positional[0] === 'tour') { options.command = 'tour'; options.name = positional[1] || null; }
573
565
  else options.name = positional[0] || null;
574
566
  return options;
575
567
  }
@@ -591,7 +583,7 @@ export async function runDemoCli(argv = [], { cwd = process.cwd(), repoRoot } =
591
583
  console.log('\nPlaywright recordings (.cx/demos/recordings/ overrides templates/demos/recordings/):');
592
584
  if (!recordings.length) console.log(' (none — run `construct demo init <name> --from=nextjs-static`)');
593
585
  else for (const r of recordings) console.log(` ${r}`);
594
- console.log('\nDemo scripts (chat-guided; templates/demos/scripts/):');
586
+ console.log('\nDemo scripts (tour steps; templates/demos/scripts/):');
595
587
  if (!scripts.length) console.log(' (none)');
596
588
  else for (const s of scripts) console.log(` ${s}`);
597
589
  console.log(`\nScaffold templates: ${RECORDING_INIT_TEMPLATES.join(', ')}`);
@@ -599,8 +591,21 @@ export async function runDemoCli(argv = [], { cwd = process.cwd(), repoRoot } =
599
591
  }
600
592
 
601
593
  if (options.command === 'init') {
594
+ if (options.fromProject) {
595
+ if (!options.name) {
596
+ console.error('Usage: construct demo init <name> --from-project');
597
+ process.exit(1);
598
+ }
599
+ const result = scaffoldProjectDemo(options.name, { cwd, repoRoot });
600
+ if (!result.ok) {
601
+ console.error(result.message);
602
+ process.exit(1);
603
+ }
604
+ console.log(result.message);
605
+ return;
606
+ }
602
607
  if (!options.name) {
603
- console.error(`Usage: construct demo init <name> [--from=${RECORDING_INIT_TEMPLATES.join('|')}]`);
608
+ console.error(`Usage: construct demo init <name> [--from=${RECORDING_INIT_TEMPLATES.join('|')}] | --from-project`);
604
609
  process.exit(1);
605
610
  }
606
611
  const result = initProjectRecording(options.name, { cwd, from: options.from, repoRoot });
@@ -612,28 +617,45 @@ export async function runDemoCli(argv = [], { cwd = process.cwd(), repoRoot } =
612
617
  return;
613
618
  }
614
619
 
615
- const demoName = options.command === 'record' ? options.name : options.name;
616
- if (!demoName) {
617
- printHelp();
618
- process.exit(1);
619
- }
620
-
621
- if (demoName.startsWith('dashboard:')) {
622
- const { recordDashboardDemo } = await import('./dashboard-demo.mjs');
623
- const result = recordDashboardDemo(demoName.slice('dashboard:'.length), { cwd, repoRoot });
620
+ if (options.command === 'tour') {
621
+ const scripts = listDemoScripts({ cwd, repoRoot });
622
+ const tourName = options.name || scripts[0] || null;
623
+ if (!tourName) {
624
+ console.error('No demo scripts found. Add one under templates/demos/scripts/ or .cx/demos/scripts/.');
625
+ process.exit(1);
626
+ }
627
+ const script = loadDemoScript(tourName, { cwd, repoRoot });
628
+ if (!script) {
629
+ console.error(`Unknown demo: ${tourName}. Run \`construct demo list\` to see available scripts.`);
630
+ process.exit(1);
631
+ }
632
+ // A non-TTY stdout (pipe, file, CI) advances on its own even without --skip-input.
633
+ const skipInput = options.skipInput || !process.stdout.isTTY;
634
+ const result = await renderTour({
635
+ script,
636
+ output: process.stdout,
637
+ input: process.stdin,
638
+ env: process.env,
639
+ accessible: options.accessible,
640
+ skipInput,
641
+ });
624
642
  if (!result.ok) {
625
643
  console.error(result.message);
626
- process.exit(result.missing?.length ? 2 : 1);
644
+ process.exit(1);
627
645
  }
628
- console.log(result.message);
629
- for (const v of result.videos || []) console.log(` ${v}`);
630
646
  return;
631
647
  }
632
648
 
649
+ const demoName = options.command === 'record' ? options.name : options.name;
650
+ if (!demoName) {
651
+ printHelp();
652
+ process.exit(1);
653
+ }
654
+
633
655
  const preferPlaywright = options.surface === 'playwright'
634
656
  || (options.command === 'record' && loadDemoRecording(demoName, { cwd, repoRoot })?.engine === 'playwright');
635
657
  const useTape = options.surface === 'tape'
636
- || (options.command === 'record' && !preferPlaywright && options.surface !== 'dashboard');
658
+ || (options.command === 'record' && !preferPlaywright);
637
659
  if (useTape || (options.command === 'record' && preferPlaywright)) {
638
660
  const result = runDemoRecord(demoName, {
639
661
  cwd,
@@ -669,11 +691,7 @@ export async function runDemoCli(argv = [], { cwd = process.cwd(), repoRoot } =
669
691
  format: options.format,
670
692
  out: options.out,
671
693
  sourceOnly: options.sourceOnly,
672
- model: options.model,
673
- plain: options.plain,
674
694
  accessible: options.accessible,
675
- web: options.web,
676
- free: options.free,
677
695
  });
678
696
 
679
697
  if (!result.ok) {
@@ -684,9 +702,6 @@ export async function runDemoCli(argv = [], { cwd = process.cwd(), repoRoot } =
684
702
  if (result.notices?.length) {
685
703
  for (const n of result.notices) console.error(`note: ${n}`);
686
704
  }
687
- if (result.surface === 'chat' || result.surface === 'web') {
688
- return;
689
- }
690
705
  if (result.artifactPath) {
691
706
  console.log(`Demo (${demoName}) [${result.surface}] recorded to:`);
692
707
  console.log(` ${result.artifactPath}`);
@@ -14,6 +14,7 @@ import { fileURLToPath } from 'node:url';
14
14
 
15
15
  const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
16
16
  const ICON_DIR = path.join(REPO_ROOT, 'templates', 'distribution', 'icons');
17
+ const MERMAID_PPTR_CONFIG = path.join(REPO_ROOT, 'templates', 'distribution', 'mermaid-puppeteer.json');
17
18
 
18
19
  // Monochrome diagram palette, mirroring the ink ramp in construct-brand.typ so
19
20
  // embedded diagrams and surrounding body share one design language.
@@ -36,9 +37,9 @@ export const DISTRIBUTION_D2_SCALE = '0.72';
36
37
  export const DISTRIBUTION_D2_SKETCH = '1';
37
38
  export const DISTRIBUTION_D2_FONT_SIZE = '14';
38
39
  export const DISTRIBUTION_MERMAID_MIME = 'image/png';
39
- export const DISTRIBUTION_MERMAID_WIDTH = '480';
40
- export const DISTRIBUTION_MERMAID_SCALE = '1';
41
- export const DISTRIBUTION_FIGURE_MAX_WIDTH = '58%';
40
+ export const DISTRIBUTION_MERMAID_WIDTH = '2400';
41
+ export const DISTRIBUTION_MERMAID_SCALE = '2';
42
+ export const DISTRIBUTION_FIGURE_MAX_WIDTH = '92%';
42
43
 
43
44
  // Handwritten font for Mermaid labels so its hand-drawn look matches D2 sketch
44
45
  // mode. Caveat ships bundled (templates/distribution/fonts/handwritten) and is
@@ -51,6 +52,9 @@ const MERMAID_INIT = `%%{init: {
51
52
  'theme': 'base',
52
53
  'look': 'handDrawn',
53
54
  'fontFamily': '${MERMAID_FONT}',
55
+ 'flowchart': {
56
+ 'htmlLabels': false
57
+ },
54
58
  'themeVariables': {
55
59
  'fontFamily': '${MERMAID_FONT}',
56
60
  'fontSize': '19px',
@@ -86,17 +90,8 @@ const MERMAID_INIT = `%%{init: {
86
90
  }
87
91
  }}%%`;
88
92
 
89
- const CHROME_CANDIDATES = [
90
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
91
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
92
- '/usr/bin/google-chrome',
93
- '/usr/bin/chromium',
94
- '/usr/bin/chromium-browser',
95
- ];
96
-
97
- export function resolvePuppeteerExecutable(env = process.env) {
98
- if (env.PUPPETEER_EXECUTABLE_PATH) return env.PUPPETEER_EXECUTABLE_PATH;
99
- for (const candidate of CHROME_CANDIDATES) {
93
+ function firstExistingPath(paths = []) {
94
+ for (const candidate of paths) {
100
95
  try {
101
96
  if (fs.existsSync(candidate)) return candidate;
102
97
  } catch {
@@ -106,6 +101,59 @@ export function resolvePuppeteerExecutable(env = process.env) {
106
101
  return null;
107
102
  }
108
103
 
104
+ function playwrightChromeCandidates(home = process.env.HOME || '') {
105
+ if (!home) return [];
106
+ const cacheRoot = path.join(home, 'Library', 'Caches', 'ms-playwright');
107
+ let entries = [];
108
+ try {
109
+ entries = fs.readdirSync(cacheRoot, { withFileTypes: true });
110
+ } catch {
111
+ return [];
112
+ }
113
+ entries = entries
114
+ .filter((entry) => entry.isDirectory())
115
+ .sort((a, b) => {
116
+ const aHeadless = a.name.startsWith('chromium_headless_shell-') ? 0 : 1;
117
+ const bHeadless = b.name.startsWith('chromium_headless_shell-') ? 0 : 1;
118
+ if (aHeadless !== bHeadless) return aHeadless - bHeadless;
119
+ const aVer = Number((a.name.match(/-(\d+)/) || [])[1] || 0);
120
+ const bVer = Number((b.name.match(/-(\d+)/) || [])[1] || 0);
121
+ return bVer - aVer;
122
+ });
123
+ const candidates = [];
124
+ for (const entry of entries) {
125
+ const base = path.join(cacheRoot, entry.name);
126
+ if (entry.name.startsWith('chromium_headless_shell-')) {
127
+ candidates.push(path.join(base, 'chrome-headless-shell-mac-arm64', 'chrome-headless-shell'));
128
+ candidates.push(path.join(base, 'chrome-headless-shell-mac', 'chrome-headless-shell'));
129
+ }
130
+ if (entry.name.startsWith('chromium-')) {
131
+ candidates.push(path.join(base, 'chrome-mac-arm64', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'));
132
+ candidates.push(path.join(base, 'chrome-mac-arm64', 'chrome'));
133
+ candidates.push(path.join(base, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'));
134
+ candidates.push(path.join(base, 'chrome-mac', 'chrome'));
135
+ }
136
+ }
137
+ return candidates;
138
+ }
139
+
140
+ function chromeCandidates(env = process.env) {
141
+ return [
142
+ ...playwrightChromeCandidates(env.HOME),
143
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
144
+ '/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing',
145
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
146
+ '/usr/bin/google-chrome',
147
+ '/usr/bin/chromium',
148
+ '/usr/bin/chromium-browser',
149
+ ];
150
+ }
151
+
152
+ export function resolvePuppeteerExecutable(env = process.env) {
153
+ if (env.PUPPETEER_EXECUTABLE_PATH) return env.PUPPETEER_EXECUTABLE_PATH;
154
+ return firstExistingPath(chromeCandidates(env));
155
+ }
156
+
109
157
  export function injectMermaidBrandTheme(code) {
110
158
  const body = String(code || '').trim();
111
159
  if (!body) return body;
@@ -170,6 +218,7 @@ export function buildDistributionDiagramEnv(baseEnv = process.env) {
170
218
  CONSTRUCT_MERMAID_WIDTH: DISTRIBUTION_MERMAID_WIDTH,
171
219
  CONSTRUCT_MERMAID_SCALE: DISTRIBUTION_MERMAID_SCALE,
172
220
  };
221
+ if (fs.existsSync(MERMAID_PPTR_CONFIG)) env.CONSTRUCT_MERMAID_PPTR_CONFIG = MERMAID_PPTR_CONFIG;
173
222
  const chrome = resolvePuppeteerExecutable(baseEnv);
174
223
  if (chrome) env.PUPPETEER_EXECUTABLE_PATH = chrome;
175
224
  return env;
@@ -2,8 +2,8 @@
2
2
  * lib/doctor/source-checkout.mjs — distinguish Construct source tree vs npm install.
3
3
  *
4
4
  * `isConstructPackageRepo` is true for both checkouts and consumer installs.
5
- * Source-only doctor checks (dashboard static export, certification role cards)
6
- * should run only when this returns true.
5
+ * Source-only doctor checks (certification role cards) should run only when this
6
+ * returns true.
7
7
  */
8
8
 
9
9
  import fs from 'node:fs';
@@ -11,6 +11,5 @@ import path from 'node:path';
11
11
 
12
12
  export function isConstructSourceCheckout(rootDir) {
13
13
  const root = path.resolve(rootDir);
14
- return fs.existsSync(path.join(root, 'apps', 'dashboard'))
15
- || fs.existsSync(path.join(root, 'tests', 'certification', 'specialists'));
14
+ return fs.existsSync(path.join(root, 'tests', 'certification', 'specialists'));
16
15
  }
@@ -7,11 +7,11 @@
7
7
  * 2. MCP registration ↔ impl: every `name === '<tool>'` branch in the MCP
8
8
  * dispatch resolves to an imported function, and every exported tool
9
9
  * function in lib/mcp/tools/*.mjs is wired into the dispatch.
10
- * 3. Roles ↔ registry: every persona key in specialists/role-manifests.json
11
- * resolves to a persona or agent name in specialists/registry.json.
10
+ * 3. Roles ↔ registry: every persona key in specialists/org
11
+ * resolves to a persona or agent name in specialists/org.
12
12
  * 4. Persona promptFiles: every registry.personas[].promptFile and
13
13
  * registry.agents[].promptFile exists on disk.
14
- * 5. Contracts ↔ schemas: specialists/contracts.json passes validateContractsFile
14
+ * 5. Contracts ↔ schemas: specialists/org passes validateContractsFile
15
15
  * (cross-file refs, producer/consumer names, schema paths).
16
16
  *
17
17
  * Each violation produces an audit record. Blocking severity escalates to a
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
23
+ import { loadRegistry } from '../../registry/loader.mjs';
23
24
  import { join, dirname, resolve } from 'node:path';
24
25
  import os from 'node:os';
25
26
  import { fileURLToPath } from 'node:url';
@@ -89,6 +90,10 @@ export async function runAllChecks({ repoRoot = REPO_ROOT } = {}) {
89
90
  const hosts = checkHostSurfaces({ repoRoot });
90
91
  collect(hosts, findings, passed, 'host-surfaces');
91
92
 
93
+ const { runCredentialParityChecks } = await import('./credential-parity.mjs');
94
+ const credentials = await runCredentialParityChecks({ rootDir: repoRoot });
95
+ collect(credentials, findings, passed, 'credential-parity');
96
+
92
97
  return { findings, passed };
93
98
  }
94
99
 
@@ -219,22 +224,75 @@ function camelToSnake(s) {
219
224
  return s.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
220
225
  }
221
226
 
227
+ function readRegistrySnapshot(repoRoot) {
228
+ const registryPath = join(repoRoot, 'specialists', 'org');
229
+ if (!existsSync(registryPath)) return null;
230
+ const stat = statSync(registryPath);
231
+ if (stat.isDirectory()) return loadRegistry({ rootDir: repoRoot });
232
+ return JSON.parse(readFileSync(registryPath, 'utf8'));
233
+ }
234
+
235
+ function personaKeysFromRegistry(registry) {
236
+ const keys = new Set();
237
+
238
+ for (const [specId, spec] of Object.entries(registry?.specialists || {})) {
239
+ if (specId) keys.add(specId.replace(/^cx-/, ''));
240
+ if (spec?.name) keys.add(String(spec.name).replace(/^cx-/, ''));
241
+ }
242
+
243
+ if (Array.isArray(registry?.specialists)) {
244
+ for (const spec of registry.specialists) {
245
+ if (spec?.id) keys.add(String(spec.id).replace(/^cx-/, ''));
246
+ if (spec?.name) keys.add(String(spec.name).replace(/^cx-/, ''));
247
+ }
248
+ }
249
+
250
+ if (registry?.orchestrator?.id) keys.add(String(registry.orchestrator.id).replace(/^cx-/, ''));
251
+ if (registry?.orchestrator?.name) keys.add(String(registry.orchestrator.name).replace(/^cx-/, ''));
252
+
253
+ return [...keys].sort();
254
+ }
255
+
256
+ function personaOwnersFromRegistry(registry) {
257
+ const owners = new Map();
258
+
259
+ function addOwner(rawId, owner) {
260
+ if (!rawId) return;
261
+ const personaId = String(rawId).replace(/^cx-/, '');
262
+ if (!owners.has(personaId)) owners.set(personaId, new Set());
263
+ owners.get(personaId).add(owner);
264
+ }
265
+
266
+ for (const [specId, spec] of Object.entries(registry?.specialists || {})) {
267
+ addOwner(specId, `specialist:${specId}`);
268
+ addOwner(spec?.name, `specialist:${specId}:name`);
269
+ }
270
+
271
+ if (Array.isArray(registry?.specialists)) {
272
+ for (const spec of registry.specialists) {
273
+ const owner = `specialist:${spec?.id || spec?.name || 'unknown'}`;
274
+ addOwner(spec?.id, owner);
275
+ addOwner(spec?.name, `${owner}:name`);
276
+ }
277
+ }
278
+
279
+ addOwner(registry?.orchestrator?.id, 'orchestrator:id');
280
+ addOwner(registry?.orchestrator?.name, 'orchestrator:name');
281
+ return owners;
282
+ }
283
+
222
284
  // ── Check 3: roles ↔ registry ──────────────────────────────────────────────
223
285
 
224
286
  function checkRolesDrift({ repoRoot }) {
225
287
  const violations = [];
226
- const manifestsPath = join(repoRoot, 'specialists', 'role-manifests.json');
227
- const registryPath = join(repoRoot, 'specialists', 'registry.json');
228
-
229
- if (!existsSync(manifestsPath) || !existsSync(registryPath)) {
230
- return { summary: 'role manifests or registry missing', violations: [] };
288
+ if (!existsSync(join(repoRoot, 'specialists', 'org'))) {
289
+ return { summary: 'registry missing', violations: [] };
231
290
  }
232
291
 
233
- const manifests = JSON.parse(readFileSync(manifestsPath, 'utf8'));
234
- const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
292
+ const registry = readRegistrySnapshot(repoRoot);
235
293
 
236
294
  const known = new Set();
237
- for (const s of registry.specialists || []) {
295
+ for (const s of Object.values(registry.specialists || {})) {
238
296
  if (s?.name) { known.add(s.name); known.add(`cx-${s.name}`); }
239
297
  }
240
298
  if (registry.orchestrator?.name) {
@@ -242,35 +300,43 @@ function checkRolesDrift({ repoRoot }) {
242
300
  known.add(`cx-${registry.orchestrator.name}`);
243
301
  }
244
302
 
245
- const personaKeys = Object.keys(manifests.personas || manifests || {});
303
+ const personaKeys = personaKeysFromRegistry(registry);
304
+ const personaOwners = personaOwnersFromRegistry(registry);
246
305
  for (const key of personaKeys) {
247
- if (key === 'version' || key === 'description' || key === 'schemaVersion') continue;
248
306
  if (!known.has(key) && !known.has(`cx-${key}`)) {
249
307
  violations.push({
250
308
  category: 'roles-drift',
251
309
  severity: 'warning',
252
310
  target: key,
253
- summary: `role-manifests persona '${key}' does not resolve to a registry persona/agent`,
311
+ summary: `role manifest '${key}' does not resolve to a registry persona/agent`,
254
312
  });
255
313
  }
256
314
  }
257
315
 
258
- return { summary: `roles: ${personaKeys.length} manifest keys, ${violations.length} drift`, violations };
316
+ for (const [personaId, owners] of personaOwners.entries()) {
317
+ if (owners.size <= 1) continue;
318
+ violations.push({
319
+ category: 'roles-drift',
320
+ severity: 'warning',
321
+ target: personaId,
322
+ summary: `role manifest '${personaId}' is ambiguous after normalization: ${[...owners].join(', ')}`,
323
+ });
324
+ }
325
+
326
+ return { summary: `roles: ${personaKeys.length} personas, ${violations.length} drift`, violations };
259
327
  }
260
328
 
261
329
  // ── Check 4: persona promptFile existence ──────────────────────────────────
262
330
 
263
331
  function checkPersonaPrompts({ repoRoot }) {
264
332
  const violations = [];
265
- const registryPath = join(repoRoot, 'specialists', 'registry.json');
266
- if (!existsSync(registryPath)) return { summary: 'registry missing', violations: [] };
267
-
268
- const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
333
+ const registry = readRegistrySnapshot(repoRoot);
334
+ if (!registry) return { summary: 'registry missing', violations: [] };
269
335
  let checked = 0;
270
336
 
271
337
  for (const list of [
272
338
  registry.orchestrator ? [registry.orchestrator] : [],
273
- registry.specialists || [],
339
+ Object.values(registry.specialists || {}),
274
340
  ]) {
275
341
  for (const entry of list) {
276
342
  const ref = entry?.promptFile;
@@ -297,7 +363,9 @@ async function checkContractsDrift({ repoRoot }) {
297
363
  const violations = [];
298
364
  try {
299
365
  const { validateContractsFile } = await import('../../contracts/validate.mjs');
300
- const result = validateContractsFile({ repoRoot });
366
+ const legacyContractsPath = join(repoRoot, 'specialists', 'contracts.json');
367
+ const opts = existsSync(legacyContractsPath) ? { contractsPath: legacyContractsPath, repoRoot } : { repoRoot };
368
+ const result = validateContractsFile(opts);
301
369
  if (!result.ok) {
302
370
  for (const err of result.errors) {
303
371
  violations.push({
@@ -327,7 +395,7 @@ function getVSCodeSettingsDir(homeDir) {
327
395
  return join(appData, 'Code', 'User');
328
396
  }
329
397
 
330
- function checkHostSurfaces({ repoRoot }) {
398
+ function checkHostSurfaces({ repoRoot: _repoRoot }) {
331
399
  const violations = [];
332
400
  const homeDir = os.homedir();
333
401
  const surfaces = [