@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,63 +0,0 @@
1
- /**
2
- * lib/chat/web-launcher.mjs — start dashboard server and open /chat for construct chat --web.
3
- *
4
- * Reuses lib/service-manager.mjs startDashboard so the browser surface shares auth,
5
- * CSRF, and static hosting with the operations dashboard.
6
- */
7
-
8
- import { spawn } from 'node:child_process';
9
- import { startDashboard, readDashboardState } from '../service-manager.mjs';
10
-
11
- function openBrowser(url) {
12
- const platform = process.platform;
13
- let cmd;
14
- let args;
15
- if (platform === 'darwin') {
16
- cmd = 'open';
17
- args = [url];
18
- } else if (platform === 'win32') {
19
- cmd = 'cmd';
20
- args = ['/c', 'start', '', url];
21
- } else {
22
- cmd = 'xdg-open';
23
- args = [url];
24
- }
25
- try {
26
- spawn(cmd, args, { detached: true, stdio: 'ignore' }).unref();
27
- } catch {
28
- /* browser open is best-effort */
29
- }
30
- }
31
-
32
- export async function runWebChat({
33
- cwd = process.cwd(),
34
- env = process.env,
35
- output = process.stdout,
36
- errorOutput = process.stderr,
37
- surface = null,
38
- } = {}) {
39
- let dash = readDashboardState();
40
- if (!dash?.port) {
41
- const result = await startDashboard({ rootDir: cwd, preferredPort: parseInt(env.PORT || env.DASHBOARD_PORT || '4242', 10) });
42
- if (result.started || result.reused) {
43
- dash = { port: result.port, url: result.url || `http://127.0.0.1:${result.port}` };
44
- } else {
45
- errorOutput.write(`Failed to start dashboard: ${result.reason || 'unknown error'}\n`);
46
- return 1;
47
- }
48
- }
49
-
50
- const base = (dash.url || `http://127.0.0.1:${dash.port}`).replace(/\/$/, '');
51
- const chatUrl = surface === 'desktop'
52
- ? `${base}/chat/?surface=desktop`
53
- : `${base}/chat/`;
54
- output.write(`Construct web chat: ${chatUrl}\n`);
55
- output.write('Press Ctrl-C to stop the dashboard server.\n');
56
- openBrowser(chatUrl);
57
-
58
- await new Promise((resolve) => {
59
- process.once('SIGINT', resolve);
60
- process.once('SIGTERM', resolve);
61
- });
62
- return 0;
63
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * lib/chat/web-picker-keys.mjs — keyboard routing for the web list picker.
3
- *
4
- * Arrow/enter/escape handling shared with Ink via list-picker.mjs; this module
5
- * adapts DOM KeyboardEvent shapes for tests and the React overlay.
6
- */
7
-
8
- import {
9
- getPickerSelectedItem,
10
- getPickerVisibleItems,
11
- movePickerIndex,
12
- reducePickerKey,
13
- } from './list-picker.mjs';
14
-
15
- export { getPickerSelectedItem, getPickerVisibleItems, movePickerIndex };
16
-
17
- export function domKeyToPickerKey(event) {
18
- const key = event?.key || '';
19
- return {
20
- char: key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey ? key : undefined,
21
- key: {
22
- escape: key === 'Escape',
23
- return: key === 'Enter',
24
- upArrow: key === 'ArrowUp',
25
- downArrow: key === 'ArrowDown',
26
- backspace: key === 'Backspace',
27
- delete: key === 'Delete',
28
- ctrl: event.ctrlKey,
29
- meta: event.metaKey,
30
- },
31
- };
32
- }
33
-
34
- export function isPickerNavigationKey(key) {
35
- return key === 'ArrowUp' || key === 'ArrowDown' || key === 'Enter' || key === 'Escape';
36
- }
37
-
38
- export function reduceWebPickerKey(state, event) {
39
- return reducePickerKey(state, domKeyToPickerKey(event));
40
- }
41
-
42
- export function commitWebPickerSelection(state) {
43
- const item = getPickerSelectedItem(state);
44
- if (!item || item.disabled) return { ok: false, item: null };
45
- return { ok: true, item };
46
- }
@@ -1,159 +0,0 @@
1
- /**
2
- * lib/chat/web-session.mjs — owned-loop runtime factory for browser chat.
3
- *
4
- * Builds the same driver + session shape as lib/chat/cli.mjs so dashboard SSE
5
- * and construct chat --web share one configuration path. One runtime instance
6
- * is kept per conversation id for the lifetime of the server process.
7
- */
8
- import { randomBytes } from 'crypto';
9
- import { createOwnedLoopDriver } from '../../apps/chat/engine/loop-driver.mjs';
10
- import { resolveChatModelSelectionAsync, resolveSessionModel } from '../../apps/chat/engine/models.mjs';
11
- import { loadChatConfig } from './config.mjs';
12
- import { loadConstructEnv } from '../env-config.mjs';
13
- import { resolveLayers } from './transparency.mjs';
14
- import { buildSystemPrompt } from './system-prompt.mjs';
15
- import { createSessionUsage } from './tui/usage.mjs';
16
- import { resolveExecutionCapabilityProfile } from '../models/execution-capability-profile.mjs';
17
- import { compileExecutionPolicy } from '../models/execution-policy.mjs';
18
- import { resolveDemoGuideForChat } from '../demo-surface.mjs';
19
-
20
- const runtimes = new Map();
21
- const pendingPermissions = new Map();
22
-
23
- function createPermissionHandler({ session, onPermission = null }) {
24
- return async (req) => {
25
- const mode = session.permissionMode || 'allow_once';
26
- if (mode === 'reject') return 'reject';
27
- if (mode === 'allow_always') return 'allow_always';
28
- if (mode === 'allow_once') return 'allow';
29
- if (mode === 'ask') {
30
- const requestId = req.requestId || `perm-${randomBytes(8).toString('hex')}`;
31
- onPermission?.({
32
- type: 'permission',
33
- requestId,
34
- toolCall: { title: req.tool || req.toolCall?.title || 'tool', input: req.input ?? null },
35
- options: ['allow', 'allow_always', 'reject'],
36
- });
37
- return new Promise((resolve) => {
38
- pendingPermissions.set(requestId, { resolve, req, convId: session.convId });
39
- setTimeout(() => {
40
- if (pendingPermissions.has(requestId)) {
41
- pendingPermissions.delete(requestId);
42
- resolve('reject');
43
- }
44
- }, 120_000);
45
- });
46
- }
47
- return 'allow';
48
- };
49
- }
50
-
51
- export function resolveWebPermission(requestId, decision) {
52
- const pending = pendingPermissions.get(requestId);
53
- if (!pending) return false;
54
- pendingPermissions.delete(requestId);
55
- pending.resolve(decision);
56
- return true;
57
- }
58
-
59
- export function listPendingPermissions(convId) {
60
- return [...pendingPermissions.entries()]
61
- .filter(([, p]) => p.convId === convId)
62
- .map(([requestId, p]) => ({
63
- requestId,
64
- title: p.req?.toolCall?.title || 'tool',
65
- options: p.req?.options || ['allow', 'reject'],
66
- }));
67
- }
68
-
69
- export function getWebChatRuntime(convId) {
70
- return runtimes.get(convId) || null;
71
- }
72
-
73
- export async function ensureWebChatRuntime({
74
- convId,
75
- cwd = process.cwd(),
76
- env = process.env,
77
- onPermission = null,
78
- } = {}) {
79
- if (runtimes.has(convId)) return runtimes.get(convId);
80
-
81
- const effectiveEnv = loadConstructEnv({ rootDir: cwd, env, warn: false });
82
- const { config } = loadChatConfig({ cwd });
83
- const layers = resolveLayers({ flags: {}, env: effectiveEnv });
84
- for (const key of Object.keys(config.layers || {})) {
85
- if (config.layers[key] === false) layers[key] = false;
86
- }
87
-
88
- let modelMode = config.modelMode || 'pinned';
89
- let resolvedModel = config.model || null;
90
- if (modelMode === 'free-router') {
91
- const sessionStub = { modelMode: 'free-router', failedModels: new Set() };
92
- resolvedModel = await resolveSessionModel(sessionStub, { env: effectiveEnv, tier: 'standard' });
93
- }
94
-
95
- const modelResolution = modelMode === 'free-router' && resolvedModel
96
- ? { id: resolvedModel, notice: null }
97
- : await resolveChatModelSelectionAsync({ env: effectiveEnv, requested: resolvedModel });
98
-
99
- const demoName = effectiveEnv.CONSTRUCT_DEMO || null;
100
- const demoPack = demoName ? resolveDemoGuideForChat(demoName, { cwd, repoRoot: cwd }) : null;
101
-
102
- const session = {
103
- convId,
104
- model: modelResolution.id || resolvedModel,
105
- modelMode,
106
- permissionMode: config.permissionMode || 'allow_once',
107
- sandbox: config.sandbox || 'workspace-write',
108
- layers,
109
- usage: createSessionUsage(),
110
- demoGuide: demoPack?.guide || null,
111
- demoTitle: demoPack?.script?.title || null,
112
- };
113
-
114
- const handlers = {
115
- getSandbox: () => session.sandbox,
116
- getPermissionMode: () => session.permissionMode,
117
- requestPermission: createPermissionHandler({ session, onPermission }),
118
- };
119
-
120
- const { createAiSdkAgent } = await import('../../apps/chat/engine/ai-sdk-agent.mjs');
121
- const driver = createOwnedLoopDriver({
122
- env: effectiveEnv,
123
- cwd,
124
- model: session.model,
125
- handlers,
126
- systemPrompt: buildSystemPrompt({
127
- capabilityTier: compileExecutionPolicy({
128
- profile: resolveExecutionCapabilityProfile({ model: session.model }),
129
- }).prompt.systemPromptTier,
130
- }),
131
- createAgent: (opts) => createAiSdkAgent(opts),
132
- });
133
-
134
- const runtime = { convId, driver, session, layers, cwd, env: effectiveEnv, _started: false };
135
- runtimes.set(convId, runtime);
136
-
137
- if (!demoPack?.guide) {
138
- await driver.start();
139
- runtime._started = true;
140
- }
141
-
142
- return runtime;
143
- }
144
-
145
- export async function ensureDriverStarted(runtime) {
146
- if (!runtime || runtime._started) return runtime;
147
- await runtime.driver.start();
148
- runtime._started = true;
149
- return runtime;
150
- }
151
-
152
- export const createWebChatRuntime = ensureWebChatRuntime;
153
-
154
- export function dropWebChatRuntime(convId) {
155
- const runtime = runtimes.get(convId);
156
- if (!runtime) return;
157
- try { runtime.driver.stop?.(); } catch { /* already stopped */ }
158
- runtimes.delete(convId);
159
- }
@@ -1,57 +0,0 @@
1
- /**
2
- * lib/config/alias.mjs — resolves the user-facing Construct alias.
3
- *
4
- * Lookup precedence:
5
- * 1. CONSTRUCT_ALIAS env var (per-session override)
6
- * 2. config.json.aliasOverride in the XDG config dir (user-level, lets each
7
- * user on the same project see their own branding without
8
- * committing it)
9
- * 3. construct.config.json.alias (project-level, committed)
10
- * 4. "Construct" (the default)
11
- *
12
- * Pure resolution — no fs writes. The alias flows to the dashboard
13
- * wordmark (via /api/alias), persona prompts ({{alias}} template at
14
- * sync time), and CLI banner output.
15
- */
16
- import fs from 'node:fs';
17
- import os from 'node:os';
18
- import path from 'node:path';
19
- import { loadProjectConfig } from './project-config.mjs';
20
- import { configDir } from './xdg.mjs';
21
-
22
- export const DEFAULT_ALIAS = 'Construct';
23
- export const ALIAS_ENV_KEY = 'CONSTRUCT_ALIAS';
24
- export const ALIAS_TEMPLATE_TOKEN = /\{\{alias\}\}/g;
25
-
26
- export function resolveAlias({ cwd = process.cwd(), env = process.env, homeDir = os.homedir() } = {}) {
27
- const envAlias = env?.[ALIAS_ENV_KEY];
28
- if (typeof envAlias === 'string' && envAlias.trim()) {
29
- return { value: envAlias.trim(), source: 'env' };
30
- }
31
-
32
- try {
33
- const userCfgPath = path.join(configDir(homeDir), 'config.json');
34
- if (fs.existsSync(userCfgPath)) {
35
- const userCfg = JSON.parse(fs.readFileSync(userCfgPath, 'utf8'));
36
- const userAlias = userCfg?.aliasOverride;
37
- if (typeof userAlias === 'string' && userAlias.trim()) {
38
- return { value: userAlias.trim(), source: 'user' };
39
- }
40
- }
41
- } catch { /* user file is best-effort */ }
42
-
43
- try {
44
- const { config, source } = loadProjectConfig(cwd, env);
45
- if (source === 'file' && typeof config?.alias === 'string' && config.alias.trim()) {
46
- return { value: config.alias.trim(), source: 'project' };
47
- }
48
- } catch { /* project config is best-effort */ }
49
-
50
- return { value: DEFAULT_ALIAS, source: 'default' };
51
- }
52
-
53
- export function applyAliasToTemplate(text, alias) {
54
- if (typeof text !== 'string') return text;
55
- const safe = String(alias ?? DEFAULT_ALIAS).trim() || DEFAULT_ALIAS;
56
- return text.replace(ALIAS_TEMPLATE_TOKEN, safe);
57
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * lib/dashboard-demo.mjs — Construct cockpit Playwright demo adapter.
3
- *
4
- * Resolves dashboard recording manifests (or legacy script bridge) and delegates
5
- * to lib/playwright-demo.mjs with dashboard workspace defaults.
6
- */
7
-
8
- import path from 'node:path';
9
- import { loadDemoRecording } from './demo-recording.mjs';
10
- import {
11
- detectPlaywrightDemo,
12
- recordPlaywrightDemo,
13
- collectVideoFiles,
14
- newestVideo,
15
- finalizeDemoVideo,
16
- locateFfmpeg,
17
- transcodeWebmToMp4,
18
- resolvePlaywrightPackage,
19
- } from './playwright-demo.mjs';
20
-
21
- export {
22
- collectVideoFiles,
23
- newestVideo,
24
- finalizeDemoVideo,
25
- locateFfmpeg,
26
- transcodeWebmToMp4,
27
- resolvePlaywrightPackage,
28
- };
29
-
30
- export function dashboardWorkspaceRoot(repoRoot) {
31
- return path.join(repoRoot, 'apps', 'dashboard');
32
- }
33
-
34
- export function detectDashboardDemo({ cwd = process.cwd(), repoRoot } = {}) {
35
- const root = repoRoot || cwd;
36
- return detectPlaywrightDemo({ workspace: dashboardWorkspaceRoot(root), repoRoot: root, cwd });
37
- }
38
-
39
- export function recordDashboardDemo(name, {
40
- cwd = process.cwd(),
41
- repoRoot,
42
- outputDir,
43
- outputPath = null,
44
- format = 'mp4',
45
- artifactDir = null,
46
- artifactFile = null,
47
- env = process.env,
48
- } = {}) {
49
- const root = repoRoot || cwd;
50
- const recording = loadDemoRecording(name, { cwd, repoRoot: root });
51
-
52
- if (!recording) {
53
- const detection = detectDashboardDemo({ cwd, repoRoot: root });
54
- if (!detection.present) {
55
- return { ok: false, name, missing: detection.missing, message: detection.message };
56
- }
57
- return { ok: false, name, message: `No recording manifest or dashboard demo script for ${name}` };
58
- }
59
-
60
- const outDir = outputDir || path.join(cwd, '.cx', 'demos', 'dashboard');
61
- return recordPlaywrightDemo(recording, {
62
- cwd,
63
- repoRoot: root,
64
- outputDir: outDir,
65
- outputPath,
66
- format,
67
- artifactDir,
68
- artifactFile,
69
- env,
70
- });
71
- }
@@ -1,175 +0,0 @@
1
- /**
2
- * lib/dashboard-static.mjs — build and sync the dashboard static bundle.
3
- *
4
- * Keeps the Next.js static export at `apps/dashboard/out/` and the served
5
- * tree at `lib/server/static/` aligned so the HTTP server picks up the
6
- * current dashboard build without code changes. Pre-2.0 the dashboard
7
- * lived at `dashboard/dist/`; the rebuild to Next.js (commit 84cd721)
8
- * moved the source of truth into `apps/dashboard/out/`.
9
- */
10
-
11
- import fs from 'node:fs';
12
- import path from 'node:path';
13
- import { spawnSync } from 'node:child_process';
14
- import { sanitizeNpmSpawnEnv } from './npm-spawn-env.mjs';
15
-
16
- function walkFiles(rootDir, currentDir = rootDir) {
17
- if (!fs.existsSync(currentDir)) return [];
18
- const entries = [];
19
- for (const name of fs.readdirSync(currentDir)) {
20
- const fullPath = path.join(currentDir, name);
21
- const stat = fs.statSync(fullPath);
22
- if (stat.isDirectory()) {
23
- entries.push(...walkFiles(rootDir, fullPath));
24
- } else {
25
- entries.push(path.relative(rootDir, fullPath));
26
- }
27
- }
28
- return entries.sort();
29
- }
30
-
31
- function readBufferIfExists(filePath) {
32
- if (!fs.existsSync(filePath)) return null;
33
- return fs.readFileSync(filePath);
34
- }
35
-
36
- function compareTrees(sourceDir, targetDir) {
37
- const sourceFiles = walkFiles(sourceDir);
38
- const targetFiles = walkFiles(targetDir);
39
- const sourceSet = new Set(sourceFiles);
40
- const targetSet = new Set(targetFiles);
41
- const copied = [];
42
- const removed = [];
43
-
44
- for (const relPath of sourceFiles) {
45
- const source = readBufferIfExists(path.join(sourceDir, relPath));
46
- const target = readBufferIfExists(path.join(targetDir, relPath));
47
- if (!target || !source.equals(target)) copied.push(relPath);
48
- }
49
-
50
- for (const relPath of targetFiles) {
51
- if (!sourceSet.has(relPath)) removed.push(relPath);
52
- }
53
-
54
- return {
55
- copied,
56
- removed,
57
- sourceFiles,
58
- targetFiles,
59
- changed: copied.length > 0 || removed.length > 0,
60
- inSync: copied.length === 0 && removed.length === 0,
61
- sourceExists: fs.existsSync(sourceDir),
62
- };
63
- }
64
-
65
- function ensureParentDir(filePath) {
66
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
67
- }
68
-
69
- function removeEmptyDirs(dirPath, stopAt) {
70
- let current = dirPath;
71
- while (current.startsWith(stopAt) && current !== stopAt) {
72
- if (!fs.existsSync(current)) {
73
- current = path.dirname(current);
74
- continue;
75
- }
76
- if (fs.readdirSync(current).length > 0) break;
77
- fs.rmdirSync(current);
78
- current = path.dirname(current);
79
- }
80
- }
81
-
82
- export function getDashboardPaths({ rootDir = process.cwd() } = {}) {
83
- return {
84
- rootDir,
85
- dashboardDir: path.join(rootDir, 'apps', 'dashboard'),
86
- sourceDir: path.join(rootDir, 'apps', 'dashboard', 'out'),
87
- targetDir: path.join(rootDir, 'lib', 'server', 'static'),
88
- };
89
- }
90
-
91
- export function getDashboardStaticStatus({ rootDir = process.cwd() } = {}) {
92
- const { sourceDir, targetDir } = getDashboardPaths({ rootDir });
93
- return compareTrees(sourceDir, targetDir);
94
- }
95
-
96
- export function syncDashboardStatic({ rootDir = process.cwd(), check = false } = {}) {
97
- const { sourceDir, targetDir } = getDashboardPaths({ rootDir });
98
- if (!fs.existsSync(sourceDir)) {
99
- throw new Error(`Dashboard build output missing: ${sourceDir}`);
100
- }
101
-
102
- const status = compareTrees(sourceDir, targetDir);
103
- if (check || !status.changed) return status;
104
-
105
- for (const relPath of status.copied) {
106
- const sourcePath = path.join(sourceDir, relPath);
107
- const targetPath = path.join(targetDir, relPath);
108
- ensureParentDir(targetPath);
109
- fs.copyFileSync(sourcePath, targetPath);
110
- }
111
-
112
- for (const relPath of status.removed) {
113
- const targetPath = path.join(targetDir, relPath);
114
- fs.rmSync(targetPath, { force: true });
115
- removeEmptyDirs(path.dirname(targetPath), targetDir);
116
- }
117
-
118
- return {
119
- ...status,
120
- inSync: true,
121
- };
122
- }
123
-
124
- export function buildDashboardStatic({
125
- rootDir = process.cwd(),
126
- spawnSyncFn = spawnSync,
127
- } = {}) {
128
- const { dashboardDir } = getDashboardPaths({ rootDir });
129
- const nextCache = path.join(dashboardDir, '.next');
130
- fs.rmSync(nextCache, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
131
-
132
- const env = sanitizeNpmSpawnEnv(process.env);
133
- let result;
134
- for (let attempt = 1; attempt <= 2; attempt += 1) {
135
- result = spawnSyncFn('npm', ['--prefix', dashboardDir, 'run', 'build'], {
136
- cwd: rootDir,
137
- encoding: 'utf8',
138
- stdio: ['ignore', 'pipe', 'pipe'],
139
- env,
140
- });
141
- if (result.status === 0) break;
142
- const output = `${result.stdout || ''}\n${result.stderr || ''}`;
143
- const transientRename = /ENOENT.*rename.*\.next\/(export|server\/pages)\/500\.html/.test(output);
144
- if (!transientRename) break;
145
- fs.rmSync(nextCache, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
146
- }
147
- if (result.status !== 0) {
148
- throw new Error((result.stderr || result.stdout || 'dashboard build failed').trim());
149
- }
150
- return result;
151
- }
152
-
153
- export async function runDashboardStaticCli(args = [], { rootDir = process.cwd() } = {}) {
154
- const build = args.includes('--build');
155
- const check = args.includes('--check');
156
-
157
- if (build) buildDashboardStatic({ rootDir });
158
- const status = syncDashboardStatic({ rootDir, check });
159
-
160
- if (check) {
161
- if (status.changed) {
162
- console.error('Dashboard static assets are out of date. Run `construct dashboard:sync --build`.');
163
- return 1;
164
- }
165
- console.log('Dashboard static assets are up to date.');
166
- return 0;
167
- }
168
-
169
- if (status.changed) {
170
- console.log(`Dashboard static synced: ${status.copied.length} updated, ${status.removed.length} removed.`);
171
- } else {
172
- console.log('Dashboard static already up to date.');
173
- }
174
- return 0;
175
- }
@@ -1,88 +0,0 @@
1
- /**
2
- * lib/install/desktop-binary-download.mjs — downloads the Construct chat desktop binary.
3
- *
4
- * Fetches the platform-matching construct-chat binary from the latest GitHub
5
- * release and writes it to the bin/ subdir of the XDG state dir. Called by
6
- * `construct install --scope=user`.
7
- * Network failures are non-fatal: returns { status: 'skipped', reason } so the
8
- * rest of setup can proceed without the desktop binary.
9
- */
10
-
11
- import fs from 'node:fs';
12
- import path from 'node:path';
13
-
14
- import { stateDir } from '../config/xdg.mjs';
15
-
16
- const GITHUB_REPO = 'geraldmaron/construct';
17
-
18
- function assetName() {
19
- const plat = process.platform;
20
- const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
21
- if (plat === 'win32') return 'construct-chat-windows-x64.exe';
22
- if (plat === 'darwin') return `construct-chat-darwin-${arch}`;
23
- return `construct-chat-linux-${arch}`;
24
- }
25
-
26
- function destBinaryName() {
27
- return process.platform === 'win32' ? 'construct-chat.exe' : 'construct-chat';
28
- }
29
-
30
- async function fetchRelease(env) {
31
- const headers = { 'User-Agent': 'construct-installer/1.0' };
32
- if (env.GITHUB_TOKEN) headers['Authorization'] = `Bearer ${env.GITHUB_TOKEN}`;
33
- const res = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/releases/latest`, { headers });
34
- if (!res.ok) throw new Error(`GitHub API ${res.status}: ${await res.text().then((t) => t.slice(0, 120))}`);
35
- return res.json();
36
- }
37
-
38
- async function downloadAsset(url, dest, env) {
39
- const headers = {
40
- 'User-Agent': 'construct-installer/1.0',
41
- 'Accept': 'application/octet-stream',
42
- };
43
- if (env.GITHUB_TOKEN) headers['Authorization'] = `Bearer ${env.GITHUB_TOKEN}`;
44
- const res = await fetch(url, { headers, redirect: 'follow' });
45
- if (!res.ok) throw new Error(`Download ${res.status}: ${url}`);
46
- const buf = await res.arrayBuffer();
47
- fs.writeFileSync(dest, Buffer.from(buf));
48
- }
49
-
50
- // Returns { status: 'downloaded'|'skipped'|'failed' }; 'skipped' and 'failed' are both non-fatal.
51
- export async function downloadDesktopBinary({ homeDir, env = process.env } = {}) {
52
- const binDir = path.join(stateDir(homeDir), 'bin');
53
- const dest = path.join(binDir, destBinaryName());
54
- const name = assetName();
55
-
56
- let release;
57
- try {
58
- release = await fetchRelease(env);
59
- } catch (err) {
60
- return { status: 'skipped', reason: `GitHub API unreachable: ${err.message}` };
61
- }
62
-
63
- const asset = release.assets?.find((a) => a.name === name);
64
- if (!asset) {
65
- return {
66
- status: 'skipped',
67
- reason: `No desktop binary in ${release.tag_name} for this platform (looked for ${name})`,
68
- };
69
- }
70
-
71
- fs.mkdirSync(binDir, { recursive: true });
72
-
73
- try {
74
- await downloadAsset(asset.browser_download_url, dest, env);
75
- } catch (err) {
76
- return { status: 'failed', reason: err.message };
77
- }
78
-
79
- if (process.platform !== 'win32') {
80
- try {
81
- fs.chmodSync(dest, 0o755);
82
- } catch {
83
- /* best effort */
84
- }
85
- }
86
-
87
- return { status: 'downloaded', dest, version: release.tag_name };
88
- }