@geraldmaron/construct 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (505) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +235 -209
  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 +55 -54
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +90 -24
  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/cli.mjs +12 -2
  44. package/lib/doctor/source-checkout.mjs +3 -4
  45. package/lib/doctor/watchers/consistency.mjs +148 -43
  46. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  47. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  48. package/lib/doctor/watchers/service-health.mjs +4 -32
  49. package/lib/document-export.mjs +73 -9
  50. package/lib/document-extract.mjs +10 -198
  51. package/lib/embed/artifact.mjs +2 -3
  52. package/lib/embed/cli.mjs +1 -1
  53. package/lib/embed/config.mjs +1 -2
  54. package/lib/embed/customer-profiles.mjs +1 -1
  55. package/lib/embed/daemon.mjs +5 -6
  56. package/lib/embed/demand-fetch.mjs +114 -2
  57. package/lib/embed/docs-lifecycle.mjs +9 -5
  58. package/lib/embed/intake-metrics.mjs +2 -2
  59. package/lib/embed/notifications.mjs +2 -3
  60. package/lib/embed/recommendation-store.mjs +25 -22
  61. package/lib/embed/role-framing.mjs +4 -9
  62. package/lib/embedded-contract/capability.mjs +4 -2
  63. package/lib/embedded-contract/contract-version.mjs +1 -1
  64. package/lib/env-config.mjs +2 -2
  65. package/lib/flavors/loader.mjs +21 -19
  66. package/lib/graph/build-from-registry.mjs +31 -7
  67. package/lib/graph/staleness.mjs +1 -1
  68. package/lib/headhunt.mjs +82 -35
  69. package/lib/hooks/agent-tracker.mjs +2 -2
  70. package/lib/hooks/config-protection.mjs +1 -1
  71. package/lib/hooks/registry-sync.mjs +3 -3
  72. package/lib/host-capabilities.mjs +5 -5
  73. package/lib/improvement/controller.mjs +2 -2
  74. package/lib/init-unified.mjs +19 -21
  75. package/lib/intake/classify.mjs +26 -0
  76. package/lib/intake/daemon.mjs +2 -3
  77. package/lib/intake/prepare.mjs +10 -3
  78. package/lib/intake/session-prelude.mjs +1 -1
  79. package/lib/intake/tables/creative.mjs +9 -9
  80. package/lib/intake/tables/operations.mjs +4 -4
  81. package/lib/intake/tables/research.mjs +2 -2
  82. package/lib/integrations/intake-integrations.mjs +9 -10
  83. package/lib/knowledge/research-store.mjs +2 -2
  84. package/lib/mcp/server.mjs +104 -61
  85. package/lib/mcp/tool-budget.mjs +1 -46
  86. package/lib/mcp/tool-recovery.mjs +44 -0
  87. package/lib/mcp/tools/artifact-author.mjs +36 -0
  88. package/lib/mcp/tools/find-tool.mjs +86 -0
  89. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  90. package/lib/mcp/tools/project.mjs +4 -3
  91. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  92. package/lib/mcp/tools/skills.mjs +39 -22
  93. package/lib/mcp/tools/telemetry.mjs +2 -1
  94. package/lib/mcp/tools/workflow.mjs +1 -1
  95. package/lib/mcp-platform-config.mjs +7 -5
  96. package/lib/migrations/index.mjs +4 -2
  97. package/lib/migrations/v2-unified-registry.mjs +35 -0
  98. package/lib/model-router.mjs +203 -36
  99. package/lib/models/catalog.mjs +25 -9
  100. package/lib/models/execution-capability-profile.mjs +3 -3
  101. package/lib/models/execution-policy.mjs +7 -6
  102. package/lib/models/provider-poll.mjs +18 -4
  103. package/lib/opencode-config.mjs +56 -1
  104. package/lib/opencode-runtime-plugin.mjs +13 -10
  105. package/lib/oracle/artifact-gate.mjs +14 -4
  106. package/lib/oracle/cli.mjs +2 -0
  107. package/lib/oracle/dispatch.mjs +18 -1
  108. package/lib/oracle/execute.mjs +39 -7
  109. package/lib/oracle/index.mjs +1 -1
  110. package/lib/oracle/org-graph.mjs +10 -5
  111. package/lib/oracle/policy.mjs +6 -0
  112. package/lib/oracle/read-model.mjs +103 -6
  113. package/lib/oracle/reconcile.mjs +31 -4
  114. package/lib/oracle/remediation-dispatch.mjs +53 -0
  115. package/lib/oracle/routing.mjs +5 -0
  116. package/lib/oracle/synthesize.mjs +80 -3
  117. package/lib/orchestration/routing-tables.mjs +44 -10
  118. package/lib/orchestration/runtime.mjs +2 -0
  119. package/lib/orchestration/worker.mjs +1 -1
  120. package/lib/orchestration-policy.mjs +351 -62
  121. package/lib/overrides/resolver.mjs +1 -12
  122. package/lib/parity.mjs +37 -11
  123. package/lib/policy/engine.mjs +80 -15
  124. package/lib/prompt-composer.js +51 -15
  125. package/lib/prompt-metadata.mjs +3 -2
  126. package/lib/providers/connection-probe.mjs +58 -0
  127. package/lib/providers/copilot-auth.mjs +1 -1
  128. package/lib/providers/credential-bootstrap.mjs +1 -1
  129. package/lib/providers/op-run.mjs +3 -4
  130. package/lib/providers/secret-resolver.mjs +31 -0
  131. package/lib/publish-tooling.mjs +3 -11
  132. package/lib/publish.mjs +12 -29
  133. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  134. package/lib/reflect.mjs +2 -2
  135. package/lib/registry/agent-manifest.mjs +190 -0
  136. package/lib/registry/assemble.mjs +133 -0
  137. package/lib/registry/catalog.mjs +171 -0
  138. package/lib/registry/cli.mjs +590 -6
  139. package/lib/registry/consolidation.mjs +5 -4
  140. package/lib/registry/docs-sync.mjs +67 -0
  141. package/lib/registry/loader.mjs +147 -0
  142. package/lib/registry/org-io.mjs +76 -0
  143. package/lib/registry/retired-paths.mjs +71 -0
  144. package/lib/registry/surface-map.mjs +5 -7
  145. package/lib/registry/validator.mjs +438 -0
  146. package/lib/research-execution-policy.mjs +173 -0
  147. package/lib/roles/catalog.mjs +4 -9
  148. package/lib/roles/fence.mjs +107 -1
  149. package/lib/roles/flavor-bindings.mjs +68 -0
  150. package/lib/roles/gateway.mjs +140 -29
  151. package/lib/roles/manifest.mjs +22 -13
  152. package/lib/roles/router.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 +6 -26
  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 -8
  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 +269 -51
  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/apps/chat/engine/ai-sdk-agent.mjs +0 -183
  433. package/apps/chat/engine/loop-driver.mjs +0 -211
  434. package/apps/chat/engine/models.mjs +0 -122
  435. package/apps/chat/engine/provider-adapters.mjs +0 -171
  436. package/apps/chat/engine/tools/permission.mjs +0 -54
  437. package/apps/chat/engine/tools/primitives.mjs +0 -180
  438. package/apps/chat/engine/tools/registry.mjs +0 -122
  439. package/apps/chat/engine/turn-controls.mjs +0 -70
  440. package/lib/boundary.mjs +0 -127
  441. package/lib/certification/dashboard-api.mjs +0 -71
  442. package/lib/chat/cli.mjs +0 -333
  443. package/lib/chat/command-suggest.mjs +0 -161
  444. package/lib/chat/commands.mjs +0 -215
  445. package/lib/chat/config.mjs +0 -142
  446. package/lib/chat/context-compactor.mjs +0 -250
  447. package/lib/chat/context-continuation.mjs +0 -253
  448. package/lib/chat/continuation-source.mjs +0 -58
  449. package/lib/chat/demo-guide.mjs +0 -61
  450. package/lib/chat/design-tokens.mjs +0 -91
  451. package/lib/chat/desktop-binary.mjs +0 -81
  452. package/lib/chat/desktop-build.mjs +0 -130
  453. package/lib/chat/desktop-launcher.mjs +0 -133
  454. package/lib/chat/evidence.mjs +0 -145
  455. package/lib/chat/export.mjs +0 -74
  456. package/lib/chat/harness/driver.mjs +0 -91
  457. package/lib/chat/list-picker.mjs +0 -112
  458. package/lib/chat/model-picker.mjs +0 -356
  459. package/lib/chat/openrouter-fallback.mjs +0 -151
  460. package/lib/chat/permission-prompt.mjs +0 -33
  461. package/lib/chat/picker-catalog.mjs +0 -45
  462. package/lib/chat/policy-telemetry.mjs +0 -34
  463. package/lib/chat/present.mjs +0 -246
  464. package/lib/chat/session-context.mjs +0 -39
  465. package/lib/chat/session-persist.mjs +0 -73
  466. package/lib/chat/session-restore.mjs +0 -71
  467. package/lib/chat/session-settings.mjs +0 -53
  468. package/lib/chat/system-prompt.mjs +0 -52
  469. package/lib/chat/transparency.mjs +0 -93
  470. package/lib/chat/tui/color-scheme.mjs +0 -42
  471. package/lib/chat/tui/markdown.mjs +0 -123
  472. package/lib/chat/tui/presentation.mjs +0 -100
  473. package/lib/chat/tui/render.mjs +0 -500
  474. package/lib/chat/tui/turn-block.mjs +0 -284
  475. package/lib/chat/tui/turn-present.mjs +0 -18
  476. package/lib/chat/tui/turn-state.mjs +0 -88
  477. package/lib/chat/tui/usage.mjs +0 -122
  478. package/lib/chat/web-commands.mjs +0 -146
  479. package/lib/chat/web-launcher.mjs +0 -63
  480. package/lib/chat/web-picker-keys.mjs +0 -46
  481. package/lib/chat/web-session.mjs +0 -159
  482. package/lib/config/alias.mjs +0 -57
  483. package/lib/dashboard-demo.mjs +0 -71
  484. package/lib/dashboard-static.mjs +0 -175
  485. package/lib/install/desktop-binary-download.mjs +0 -88
  486. package/lib/profiles/loader.mjs +0 -123
  487. package/lib/profiles/rebrand.mjs +0 -46
  488. package/lib/server/auth.mjs +0 -169
  489. package/lib/server/chat-loop.mjs +0 -622
  490. package/lib/server/chat.mjs +0 -336
  491. package/lib/server/cors.mjs +0 -77
  492. package/lib/server/csrf.mjs +0 -103
  493. package/lib/server/demo-preview.mjs +0 -63
  494. package/lib/server/index.mjs +0 -2641
  495. package/lib/server/insights.mjs +0 -780
  496. package/lib/server/langfuse-login.mjs +0 -58
  497. package/lib/server/rate-limit.mjs +0 -93
  498. package/lib/server/telemetry-login.mjs +0 -100
  499. package/lib/server/webhook.mjs +0 -512
  500. package/specialists/contracts.json +0 -1032
  501. package/specialists/contracts.schema.json +0 -83
  502. package/specialists/policy-inventory.json +0 -188
  503. package/specialists/registry.json +0 -1412
  504. package/specialists/role-manifests.json +0 -217
  505. package/specialists/teams.json +0 -94
@@ -1,336 +0,0 @@
1
- /**
2
- * lib/server/chat.mjs — Dashboard chat endpoint handler.
3
- *
4
- * Handles POST /api/chat (non-streaming JSON) and GET /api/chat/stream (SSE).
5
- * Shells out to the `claude --print` CLI and streams the response back as
6
- * Server-Sent Events. Returns a structured fallback if the CLI is unavailable.
7
- *
8
- * Message history is kept per conversation ID in memory (cleared on restart).
9
- * The client sends { id?, message } — omitting id starts a new conversation.
10
- */
11
-
12
- import { spawn, execSync } from 'child_process';
13
- import { randomBytes } from 'crypto';
14
- import { existsSync, readFileSync } from 'fs';
15
- import { join } from 'path';
16
- import { homedir } from 'os';
17
- import { fileURLToPath } from 'url';
18
- import { dirname, resolve } from 'path';
19
-
20
- const HOME = homedir();
21
- const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
22
-
23
- // ── Session-end optimize hook ──────────────────────────────────────────────
24
- // After a chat session closes, check whether enough new quality scores have
25
- // accumulated in the performance review to warrant running optimize.
26
- // Fires asynchronously — never blocks the response.
27
-
28
- let _sessionsSinceOptimize = 0;
29
- const OPTIMIZE_EVERY_N_SESSIONS = parseInt(process.env.CX_OPTIMIZE_INTERVAL ?? '5', 10);
30
-
31
- function maybeRunOptimize() {
32
- _sessionsSinceOptimize++;
33
- if (_sessionsSinceOptimize < OPTIMIZE_EVERY_N_SESSIONS) return;
34
- _sessionsSinceOptimize = 0;
35
-
36
- // Spawn optimize --list to see if any agents are below threshold
37
- const script = join(ROOT_DIR, 'scripts', 'optimize.mjs');
38
- if (!existsSync(script)) return;
39
-
40
- const proc = spawn(process.execPath, [script, '--list', '--threshold=0.7'], {
41
- cwd: ROOT_DIR,
42
- env: { ...process.env },
43
- stdio: ['ignore', 'pipe', 'pipe'],
44
- });
45
-
46
- let out = '';
47
- proc.stdout.on('data', d => { out += d; });
48
- proc.on('close', () => {
49
- // Find agents flagged below threshold and run optimize on each
50
- const flagged = out.split('\n')
51
- .filter(l => l.includes('← below threshold'))
52
- .map(l => l.trim().split(/\s+/)[0])
53
- .filter(Boolean);
54
-
55
- for (const agent of flagged) {
56
- const opt = spawn(process.execPath, [script, agent, `--threshold=0.7`], {
57
- cwd: ROOT_DIR,
58
- env: { ...process.env },
59
- stdio: 'ignore',
60
- detached: true,
61
- });
62
- opt.unref();
63
- }
64
- });
65
- proc.unref();
66
- }
67
-
68
- // ── Conversation store ─────────────────────────────────────────────────────
69
- const conversations = new Map();
70
- const CONV_TTL_MS = 2 * 60 * 60 * 1000;
71
-
72
- function pruneConversations() {
73
- const now = Date.now();
74
- for (const [id, conv] of conversations) {
75
- if (now - conv.lastAt > CONV_TTL_MS) conversations.delete(id);
76
- }
77
- }
78
-
79
- export function getOrCreateConversation(id) {
80
- pruneConversations();
81
- if (id && conversations.has(id)) {
82
- const conv = conversations.get(id);
83
- conv.lastAt = Date.now();
84
- return conv;
85
- }
86
- const newId = randomBytes(12).toString('hex');
87
- const conv = { id: newId, messages: [], createdAt: Date.now(), lastAt: Date.now() };
88
- conversations.set(newId, conv);
89
- return conv;
90
- }
91
-
92
- // ── CLI detection ──────────────────────────────────────────────────────────
93
-
94
- let _cliCmd = undefined; // undefined = not yet checked, false = not found, string = found
95
-
96
- function detectCli() {
97
- if (_cliCmd !== undefined) return _cliCmd;
98
- for (const cmd of ['claude', 'anthropic']) {
99
- try {
100
- execSync(`which ${cmd}`, { stdio: 'ignore' });
101
- _cliCmd = cmd;
102
- return _cliCmd;
103
- } catch { /* not found */ }
104
- }
105
- _cliCmd = false;
106
- return _cliCmd;
107
- }
108
-
109
- // Read-only Construct MCP tools that are pre-authorized in dashboard chat.
110
- // Destructive tools (storage_reset, delete_ingested_artifacts, ingest_document)
111
- // are deliberately omitted and still require explicit approval.
112
- const ALLOWED_CONSTRUCT_TOOLS = [
113
- 'mcp__construct-mcp__provider_fetch',
114
- 'mcp__construct-mcp__knowledge_search',
115
- 'mcp__construct-mcp__memory_search',
116
- 'mcp__construct-mcp__memory_recent',
117
- 'mcp__construct-mcp__project_context',
118
- 'mcp__construct-mcp__session_list',
119
- 'mcp__construct-mcp__session_load',
120
- 'mcp__construct-mcp__session_search',
121
- 'mcp__construct-mcp__agent_health',
122
- 'mcp__construct-mcp__storage_status',
123
- 'mcp__construct-mcp__list_skills',
124
- 'mcp__construct-mcp__get_skill',
125
- 'mcp__construct-mcp__list_templates',
126
- 'mcp__construct-mcp__get_template',
127
- 'mcp__construct-mcp__list_teams',
128
- 'mcp__construct-mcp__get_team',
129
- 'mcp__construct-mcp__agent_contract',
130
- 'mcp__construct-mcp__orchestration_policy',
131
- 'mcp__construct-mcp__search_skills',
132
- 'mcp__construct-mcp__efficiency_snapshot',
133
- 'mcp__construct-mcp__session_usage',
134
- 'mcp__construct-mcp__list_schema_artifacts',
135
- 'mcp__construct-mcp__extract_document_text',
136
- 'mcp__construct-mcp__summarize_diff',
137
- 'mcp__construct-mcp__scan_file',
138
- 'Read', 'Glob', 'Grep',
139
- ];
140
-
141
- function buildCliArgs() {
142
- return [
143
- '--print',
144
- '--permission-mode', 'default',
145
- '--allowedTools', ...ALLOWED_CONSTRUCT_TOOLS,
146
- ];
147
- }
148
-
149
- // ── Prompt builder ─────────────────────────────────────────────────────────
150
-
151
- function buildPrompt(messages, newMessage, rootDir) {
152
- const lines = [];
153
-
154
- // Attach project context prefix on first message
155
- if (messages.length === 0) {
156
- lines.push(
157
- '[System]',
158
- 'You are Construct, the operator-facing chat for this project. The MCP tools',
159
- '`provider_fetch`, `knowledge_search`, `memory_search`, `memory_recent`,',
160
- '`project_context`, and `session_*` are pre-authorized read-only lookups.',
161
- 'Call them directly without asking for approval. They route to the configured',
162
- 'GitHub/Jira/Slack/local sources via authority-guarded providers.',
163
- 'For "what is X" / "tell me about X" queries about a project name, repo, or',
164
- 'concept, your first action is `provider_fetch({ query: "<user query>" })`.',
165
- '',
166
- );
167
- const contextFile = join(rootDir || process.cwd(), '.cx', 'context.md');
168
- if (existsSync(contextFile)) {
169
- try {
170
- const ctx = readFileSync(contextFile, 'utf8').slice(0, 1500);
171
- lines.push(`[Project context]\n${ctx}\n`);
172
- } catch { /* ignore */ }
173
- }
174
- }
175
-
176
- // Include last 6 turns of history
177
- for (const m of messages.slice(-6)) {
178
- lines.push(`${m.role === 'user' ? 'Human' : 'Assistant'}: ${m.content}`);
179
- }
180
- lines.push(`Human: ${newMessage}`, '', 'Assistant:');
181
- return lines.join('\n');
182
- }
183
-
184
- // ── SSE helpers ────────────────────────────────────────────────────────────
185
-
186
- function sseWrite(res, type, text, id) {
187
- res.write(`data: ${JSON.stringify({ type, text, id })}\n\n`);
188
- }
189
-
190
- function cliMissingResponse(res, conv) {
191
- sseWrite(res, 'chunk',
192
- '**Construct chat** requires the `claude` CLI.\n\n' +
193
- 'Install: `npm install -g @anthropic-ai/claude-code`\n\n' +
194
- 'Then authenticate once with `claude` before using the dashboard chat.',
195
- conv.id);
196
- sseWrite(res, 'done', '', conv.id);
197
- conv.messages.push({ role: 'assistant', content: '[claude CLI not available]' });
198
- res.end();
199
- }
200
-
201
- // ── Handlers ───────────────────────────────────────────────────────────────
202
-
203
- /**
204
- * GET /api/chat/stream?message=<text>[&id=<convId>]
205
- * Streams response as SSE: data: { type, text, id }
206
- * type = 'chunk' | 'done' | 'error'
207
- */
208
- export function handleChatStream(req, res, { rootDir } = {}) {
209
- const url = new URL(req.url, 'http://localhost');
210
- const message = url.searchParams.get('message');
211
- const convId = url.searchParams.get('id') || undefined;
212
-
213
- if (!message) {
214
- res.writeHead(400, { 'Content-Type': 'application/json' });
215
- res.end(JSON.stringify({ error: 'message query param required' }));
216
- return;
217
- }
218
-
219
- res.writeHead(200, {
220
- 'Content-Type': 'text/event-stream',
221
- 'Cache-Control': 'no-cache',
222
- 'Connection': 'keep-alive',
223
- });
224
-
225
- const conv = getOrCreateConversation(convId);
226
- conv.messages.push({ role: 'user', content: message });
227
-
228
- const cli = detectCli();
229
- if (!cli) { cliMissingResponse(res, conv); return; }
230
-
231
- const prompt = buildPrompt(conv.messages.slice(0, -1), message, rootDir);
232
- const proc = spawn(cli, buildCliArgs(), {
233
- cwd: rootDir || process.cwd(),
234
- env: { ...process.env },
235
- stdio: ['pipe', 'pipe', 'pipe'],
236
- });
237
-
238
- proc.stdin.write(prompt);
239
- proc.stdin.end();
240
-
241
- let fullResponse = '';
242
-
243
- proc.stdout.on('data', chunk => {
244
- const text = chunk.toString();
245
- fullResponse += text;
246
- sseWrite(res, 'chunk', text, conv.id);
247
- });
248
-
249
- proc.on('close', code => {
250
- if (code !== 0 && !fullResponse) {
251
- sseWrite(res, 'error', `Chat exited with code ${code}. Check that \`claude\` is authenticated.`, conv.id);
252
- } else {
253
- sseWrite(res, 'done', '', conv.id);
254
- }
255
- conv.messages.push({ role: 'assistant', content: fullResponse || '[no response]' });
256
- res.end();
257
- maybeRunOptimize();
258
- });
259
-
260
- proc.on('error', err => {
261
- sseWrite(res, 'error', 'Failed to start chat: ' + err.message, conv.id);
262
- res.end();
263
- });
264
-
265
- req.on('close', () => { try { proc.kill(); } catch { /* ignore */ } });
266
- }
267
-
268
- /**
269
- * POST /api/chat — non-streaming, full response as JSON.
270
- * Body: { id?, message } → { id, reply }
271
- */
272
- export function handleChat(req, res, { rootDir } = {}) {
273
- let body = '';
274
- req.on('data', chunk => { body += chunk; });
275
- req.on('end', () => {
276
- try {
277
- const data = JSON.parse(body || '{}');
278
- if (!data.message) throw new Error('message required');
279
-
280
- const conv = getOrCreateConversation(data.id);
281
- conv.messages.push({ role: 'user', content: data.message });
282
-
283
- const cli = detectCli();
284
- if (!cli) {
285
- const reply = 'The `claude` CLI is not installed. Run: npm install -g @anthropic-ai/claude-code';
286
- conv.messages.push({ role: 'assistant', content: reply });
287
- res.writeHead(200, { 'Content-Type': 'application/json' });
288
- res.end(JSON.stringify({ id: conv.id, reply, cliMissing: true }));
289
- return;
290
- }
291
-
292
- const prompt = buildPrompt(conv.messages.slice(0, -1), data.message, rootDir);
293
- const proc = spawn(cli, buildCliArgs(), {
294
- cwd: rootDir || process.cwd(),
295
- env: { ...process.env },
296
- stdio: ['pipe', 'pipe', 'pipe'],
297
- });
298
-
299
- proc.stdin.write(prompt);
300
- proc.stdin.end();
301
-
302
- let reply = '';
303
- proc.stdout.on('data', c => { reply += c.toString(); });
304
- proc.on('close', code => {
305
- const text = reply.trim() || (code !== 0 ? `[exit ${code}]` : '[empty response]');
306
- conv.messages.push({ role: 'assistant', content: text });
307
- res.writeHead(200, { 'Content-Type': 'application/json' });
308
- res.end(JSON.stringify({ id: conv.id, reply: text }));
309
- maybeRunOptimize();
310
- });
311
- proc.on('error', err => {
312
- res.writeHead(500, { 'Content-Type': 'application/json' });
313
- res.end(JSON.stringify({ error: err.message }));
314
- });
315
- } catch (err) {
316
- res.writeHead(400, { 'Content-Type': 'application/json' });
317
- res.end(JSON.stringify({ error: err.message }));
318
- }
319
- });
320
- }
321
-
322
- /**
323
- * GET /api/chat/history?id=<convId>
324
- */
325
- export function handleChatHistory(req, res) {
326
- const url = new URL(req.url, 'http://localhost');
327
- const id = url.searchParams.get('id');
328
- if (!id || !conversations.has(id)) {
329
- res.writeHead(200, { 'Content-Type': 'application/json' });
330
- res.end(JSON.stringify({ id: null, messages: [] }));
331
- return;
332
- }
333
- const conv = conversations.get(id);
334
- res.writeHead(200, { 'Content-Type': 'application/json' });
335
- res.end(JSON.stringify({ id: conv.id, messages: conv.messages }));
336
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * lib/server/cors.mjs — origin allowlist for the dashboard server.
3
- *
4
- * The default is exact-match against the dashboard's own origin (single-host
5
- * self-hosted install). Operators can pass extra origins via the env var
6
- * `CONSTRUCT_DASHBOARD_ORIGINS` as a comma-separated list.
7
- *
8
- * CONSTRUCT_DASHBOARD_ORIGINS="https://my-laptop.tail-net.ts.net,https://construct.example.com"
9
- *
10
- * Wildcard origins are NOT supported. `Access-Control-Allow-Origin: *` is
11
- * never set — paid deployments behind a private network or behind an ALB
12
- * with a CloudFront distribution declare exactly which origins may issue
13
- * requests.
14
- *
15
- * Exports:
16
- * - applyCors(req, res, opts) : sets the right Access-Control-* headers
17
- * on a request. Returns true when the
18
- * request was a preflight (OPTIONS) and
19
- * the caller should respond with 204.
20
- * - originAllowed(req, opts) : true/false. Useful when middleware wants
21
- * to enforce same-origin without setting
22
- * any headers (e.g. on SSE streams).
23
- */
24
-
25
- const HEADER_NAME = 'access-control-allow-origin';
26
-
27
- function parseOrigins(env) {
28
- const list = (env.CONSTRUCT_DASHBOARD_ORIGINS || '')
29
- .split(',')
30
- .map((s) => s.trim())
31
- .filter(Boolean);
32
- return new Set(list);
33
- }
34
-
35
- function selfOrigin(req) {
36
- const proto = req.headers['x-forwarded-proto'] || (req.socket?.encrypted ? 'https' : 'http');
37
- const host = req.headers['x-forwarded-host'] || req.headers.host;
38
- if (!host) return null;
39
- return `${proto}://${host}`;
40
- }
41
-
42
- export function originAllowed(req, { env = process.env } = {}) {
43
- const origin = req.headers.origin;
44
- if (!origin) return true;
45
- const allowed = parseOrigins(env);
46
- allowed.add(selfOrigin(req));
47
- return allowed.has(origin);
48
- }
49
-
50
- /**
51
- * Set CORS headers on a response. Returns `true` for preflight requests
52
- * that the caller should answer with 204 immediately.
53
- */
54
- export function applyCors(req, res, { env = process.env } = {}) {
55
- const origin = req.headers.origin;
56
- if (origin && originAllowed(req, { env })) {
57
- res.setHeader(HEADER_NAME, origin);
58
- res.setHeader('Vary', 'Origin');
59
- res.setHeader('Access-Control-Allow-Credentials', 'true');
60
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
61
- res.setHeader(
62
- 'Access-Control-Allow-Headers',
63
- 'Content-Type, Authorization, X-Construct-Csrf, X-Construct-Token'
64
- );
65
- }
66
- if (req.method === 'OPTIONS') {
67
- if (origin && !originAllowed(req, { env })) {
68
- res.statusCode = 403;
69
- res.end('origin not allowed');
70
- return true;
71
- }
72
- res.statusCode = 204;
73
- res.end();
74
- return true;
75
- }
76
- return false;
77
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * lib/server/csrf.mjs — double-submit CSRF protection for state-mutating routes.
3
- *
4
- * Every non-GET request must include a `X-Construct-Csrf` header whose value
5
- * matches the `cx_csrf` cookie. The dashboard mints the cookie on first
6
- * authenticated load (the cookie value is a 256-bit random hex string,
7
- * SameSite=Lax, HttpOnly=false so the SPA can read it). Issuing the header
8
- * proves the request originated from a page that received the cookie, which
9
- * a cross-origin attacker cannot forge.
10
- *
11
- * Routes that legitimately accept unauthenticated requests (webhooks,
12
- * /api/auth/login) opt out by passing `{ skipMethods: ['POST'] }` or by
13
- * checking `csrfRequired(url)` themselves.
14
- */
15
-
16
- import { randomBytes, timingSafeEqual } from 'node:crypto';
17
-
18
- const COOKIE_NAME = 'cx_csrf';
19
- const HEADER_NAME = 'x-construct-csrf';
20
- const TOKEN_BYTES = 32;
21
- const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
22
-
23
- function parseCookies(req) {
24
- const out = {};
25
- const header = req.headers.cookie;
26
- if (!header) return out;
27
- for (const part of header.split(';')) {
28
- const [k, ...rest] = part.split('=');
29
- if (!k) continue;
30
- out[k.trim()] = rest.join('=').trim();
31
- }
32
- return out;
33
- }
34
-
35
- function bufFromHex(s) {
36
- if (typeof s !== 'string' || s.length % 2 !== 0) return null;
37
- try { return Buffer.from(s, 'hex'); } catch { return null; }
38
- }
39
-
40
- export function mintCsrfToken() {
41
- return randomBytes(TOKEN_BYTES).toString('hex');
42
- }
43
-
44
- /**
45
- * Ensure the response has the `cx_csrf` cookie. If the request already had
46
- * one, leave it alone. Otherwise mint and set it.
47
- *
48
- * @returns {string} the token now in play
49
- */
50
- export function ensureCsrfCookie(req, res) {
51
- const existing = parseCookies(req)[COOKIE_NAME];
52
- if (existing) return existing;
53
- const token = mintCsrfToken();
54
- const sec = req.socket?.encrypted ? '; Secure' : '';
55
- res.setHeader(
56
- 'Set-Cookie',
57
- `${COOKIE_NAME}=${token}; Path=/; SameSite=Lax; Max-Age=43200${sec}`
58
- );
59
- return token;
60
- }
61
-
62
- /**
63
- * Verify the `X-Construct-Csrf` header matches the `cx_csrf` cookie. Returns
64
- * true on safe methods (GET/HEAD/OPTIONS) without checking. Returns true on
65
- * URLs that opt out via `skip(url)`.
66
- */
67
- export function verifyCsrf(req, { skip } = {}) {
68
- if (SAFE_METHODS.has(req.method)) return true;
69
- // A request authenticated by an Authorization header (bearer token) is not
70
- // cookie-driven, so it cannot be forged cross-origin — CSRF defends ambient
71
- // cookie auth, not header tokens. This is what lets programmatic API clients
72
- // (editor adapters, CLI, CI) POST without the browser CSRF dance.
73
- if (req.headers && req.headers.authorization) return true;
74
- if (typeof skip === 'function' && skip(req.url || '')) return true;
75
- const headerVal = req.headers[HEADER_NAME];
76
- const cookieVal = parseCookies(req)[COOKIE_NAME];
77
- if (!headerVal || !cookieVal) return false;
78
- if (headerVal.length !== cookieVal.length) return false;
79
- const a = bufFromHex(headerVal);
80
- const b = bufFromHex(cookieVal);
81
- if (!a || !b || a.length !== b.length) return false;
82
- try { return timingSafeEqual(a, b); } catch { return false; }
83
- }
84
-
85
- /**
86
- * Default skip predicate — webhooks, auth/login, and the orchestration API
87
- * legitimately accept POSTs without the CSRF dance. CSRF defends a browser
88
- * cookie session; the `/api/orchestration/*` endpoints are consumed only by
89
- * programmatic clients (the `--remote` CLI, the MCP tool, editor adapters) that
90
- * carry a bearer token (or nothing in no-token mode), never the dashboard
91
- * cookie — so CSRF is the wrong control there, and the auth gate still protects
92
- * them in token mode. Without this, a no-token daemon rejects every orchestration
93
- * run with a 403.
94
- */
95
- export function defaultSkip(url) {
96
- if (!url) return false;
97
- return (
98
- url.startsWith('/api/webhooks/') ||
99
- url.startsWith('/api/orchestration/') ||
100
- url === '/api/auth/login' ||
101
- url === '/api/slack/commands'
102
- );
103
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * lib/server/demo-preview.mjs — gated static serve for Playwright demo artifacts.
3
- *
4
- * When CONSTRUCT_DEMO_ARTIFACT_DIR is set, GET /demo-preview/<filename> serves
5
- * PDF/HTML (and sibling export assets) from that directory with path traversal
6
- * guards. Inactive when the env var is unset.
7
- */
8
-
9
- import fs from 'node:fs';
10
- import path from 'node:path';
11
-
12
- const ALLOWED_EXT = new Set(['.pdf', '.html', '.png', '.svg', '.webm', '.mp4', '.css', '.js']);
13
-
14
- const MIME = {
15
- '.pdf': 'application/pdf',
16
- '.html': 'text/html; charset=utf-8',
17
- '.png': 'image/png',
18
- '.svg': 'image/svg+xml',
19
- '.webm': 'video/webm',
20
- '.mp4': 'video/mp4',
21
- '.css': 'text/css; charset=utf-8',
22
- '.js': 'text/javascript; charset=utf-8',
23
- };
24
-
25
- export function demoPreviewRootFromEnv(env = process.env) {
26
- const raw = env.CONSTRUCT_DEMO_ARTIFACT_DIR;
27
- if (!raw || !String(raw).trim()) return null;
28
- return path.resolve(String(raw));
29
- }
30
-
31
- export function resolveDemoPreviewPath(pathname, env = process.env) {
32
- const root = demoPreviewRootFromEnv(env);
33
- if (!root) return { ok: false, status: 404, message: 'Not found' };
34
-
35
- const prefix = '/demo-preview/';
36
- if (!pathname.startsWith(prefix)) return { ok: false, status: 404, message: 'Not found' };
37
-
38
- const name = decodeURIComponent(pathname.slice(prefix.length));
39
- if (!name || name.includes('/') || name.includes('\\') || name.includes('..')) {
40
- return { ok: false, status: 403, message: 'Forbidden' };
41
- }
42
-
43
- const ext = path.extname(name).toLowerCase();
44
- if (!ALLOWED_EXT.has(ext)) {
45
- return { ok: false, status: 403, message: 'Forbidden' };
46
- }
47
-
48
- const fullPath = path.resolve(path.join(root, name));
49
- const rootWithSep = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
50
- if (!fullPath.startsWith(rootWithSep) && fullPath !== root) {
51
- return { ok: false, status: 403, message: 'Forbidden' };
52
- }
53
-
54
- if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
55
- return { ok: false, status: 404, message: 'Not found' };
56
- }
57
-
58
- return { ok: true, filePath: fullPath, ext };
59
- }
60
-
61
- export function demoPreviewMime(ext) {
62
- return MIME[ext] ?? 'application/octet-stream';
63
- }