@geraldmaron/construct 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) 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/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/scopes/enrich.mjs +67 -0
  153. package/lib/scopes/hooks.mjs +12 -0
  154. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  155. package/lib/scopes/loader.mjs +104 -0
  156. package/lib/scopes/rebrand.mjs +32 -0
  157. package/lib/scopes/research-profile.mjs +16 -0
  158. package/lib/scopes/teams.mjs +98 -0
  159. package/lib/service-manager.mjs +1 -117
  160. package/lib/setup-prompts.mjs +1 -1
  161. package/lib/setup.mjs +6 -26
  162. package/lib/skills/composition-graph.mjs +98 -0
  163. package/lib/skills/router.mjs +80 -0
  164. package/lib/specialist-contracts.mjs +17 -18
  165. package/lib/specialists/postconditions.mjs +2 -2
  166. package/lib/specialists/prompt-schema.mjs +6 -5
  167. package/lib/specialists/roster.mjs +7 -12
  168. package/lib/specialists/schema.mjs +9 -6
  169. package/lib/status.mjs +25 -90
  170. package/lib/storage/file-lock.mjs +6 -3
  171. package/lib/telemetry/skill-calls.mjs +1 -1
  172. package/lib/templates/doc-presentation.mjs +63 -0
  173. package/lib/templates/visual-requirements.mjs +35 -2
  174. package/lib/term-format.mjs +107 -2
  175. package/lib/test-env-setup.mjs +21 -0
  176. package/lib/ui/components.mjs +42 -0
  177. package/lib/ui/glyphs.mjs +58 -0
  178. package/lib/ui/links.mjs +115 -0
  179. package/lib/ui/theme.mjs +108 -0
  180. package/lib/uninstall/uninstall.mjs +2 -1
  181. package/lib/validator.mjs +45 -8
  182. package/lib/validators/skill-effectiveness.mjs +174 -0
  183. package/lib/validators/skills.mjs +1 -1
  184. package/package.json +12 -8
  185. package/personas/construct.md +12 -3
  186. package/platforms/claude/CLAUDE.md +1 -1
  187. package/rules/common/beads-hygiene.md +52 -0
  188. package/rules/common/neurodivergent-output.md +4 -7
  189. package/rules/common/research.md +2 -0
  190. package/scripts/sync-specialists.mjs +260 -49
  191. package/skills/ai/prompt-optimizer.md +3 -3
  192. package/skills/brand/output-vibe.md +48 -0
  193. package/skills/docs/adr-workflow.md +3 -0
  194. package/skills/docs/backlog-proposal-workflow.md +3 -0
  195. package/skills/docs/codebase-research-workflow.md +5 -2
  196. package/skills/docs/customer-profile-workflow.md +3 -0
  197. package/skills/docs/document-ingest-workflow.md +3 -0
  198. package/skills/docs/evidence-ingest-workflow.md +4 -1
  199. package/skills/docs/init-project.md +1 -1
  200. package/skills/docs/prd-workflow.md +1 -1
  201. package/skills/docs/prfaq-workflow.md +3 -0
  202. package/skills/docs/product-intelligence-workflow.md +4 -1
  203. package/skills/docs/product-signal-workflow.md +3 -0
  204. package/skills/docs/research-workflow.md +13 -6
  205. package/skills/docs/runbook-workflow.md +3 -0
  206. package/skills/docs/strategy-workflow.md +3 -0
  207. package/skills/docs/user-research-workflow.md +6 -3
  208. package/skills/operating/orchestration-reference.md +1 -1
  209. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  210. package/skills/roles/architect.ai-systems.md +1 -1
  211. package/skills/roles/architect.data.md +1 -1
  212. package/skills/roles/architect.enterprise.md +1 -1
  213. package/skills/roles/architect.integration.md +1 -1
  214. package/skills/roles/architect.md +1 -1
  215. package/skills/roles/architect.platform.md +1 -1
  216. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  217. package/skills/roles/data-analyst.experiment.md +1 -1
  218. package/skills/roles/data-analyst.md +1 -1
  219. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  220. package/skills/roles/data-analyst.product.md +1 -1
  221. package/skills/roles/data-analyst.telemetry.md +1 -1
  222. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  223. package/skills/roles/data-engineer.pipeline.md +2 -2
  224. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  225. package/skills/roles/data-engineer.warehouse.md +2 -2
  226. package/skills/roles/debugger.md +1 -1
  227. package/skills/roles/designer.accessibility.md +1 -1
  228. package/skills/roles/designer.md +1 -1
  229. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  230. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  231. package/skills/roles/engineer.md +3 -7
  232. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  233. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  234. package/skills/roles/{operator.md → operations.md} +2 -5
  235. package/skills/roles/orchestrator.md +1 -1
  236. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  237. package/skills/roles/product-manager.ai-product.md +1 -1
  238. package/skills/roles/product-manager.enterprise.md +1 -1
  239. package/skills/roles/product-manager.growth.md +1 -2
  240. package/skills/roles/product-manager.md +1 -2
  241. package/skills/roles/product-manager.platform.md +1 -1
  242. package/skills/roles/product-manager.product.md +1 -1
  243. package/skills/roles/qa.ai-eval.md +1 -1
  244. package/skills/roles/qa.api-contract.md +1 -1
  245. package/skills/roles/qa.data-pipeline.md +1 -1
  246. package/skills/roles/qa.md +1 -1
  247. package/skills/roles/qa.web-ui.md +1 -1
  248. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  249. package/skills/roles/researcher.md +1 -1
  250. package/skills/roles/reviewer.md +1 -1
  251. package/skills/roles/security.ai.md +1 -1
  252. package/skills/roles/security.appsec.md +1 -1
  253. package/skills/roles/security.cloud.md +1 -1
  254. package/skills/roles/security.legal-compliance.md +1 -1
  255. package/skills/roles/security.md +1 -1
  256. package/skills/roles/security.privacy.md +1 -1
  257. package/skills/roles/security.supply-chain.md +1 -1
  258. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  259. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  260. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  261. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  262. package/skills/routing.json +18 -0
  263. package/skills/routing.md +1 -1
  264. package/specialists/artifact-manifest.json +2 -1
  265. package/specialists/audit-enrichments.json +14 -14
  266. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  267. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  268. package/specialists/org/contracts/any-to-debugger.json +38 -0
  269. package/specialists/org/contracts/any-to-designer.json +33 -0
  270. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  271. package/specialists/org/contracts/any-to-explorer.json +39 -0
  272. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  273. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  274. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  275. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  276. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  277. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  278. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  279. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  280. package/specialists/org/contracts/architect-to-operations.json +45 -0
  281. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  282. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  283. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  284. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  285. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  286. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  287. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  288. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  289. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  290. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  291. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  292. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  293. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  294. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  295. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  296. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  297. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  298. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  299. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  300. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  301. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  302. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  303. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  304. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  305. package/specialists/org/contracts/user-to-construct.json +30 -0
  306. package/specialists/org/groups/engineering-group.json +43 -0
  307. package/specialists/org/groups/governance-group.json +38 -0
  308. package/specialists/org/groups/operations-group.json +41 -0
  309. package/specialists/org/groups/product-group.json +46 -0
  310. package/specialists/org/groups/quality-group.json +42 -0
  311. package/specialists/org/groups/strategy-group.json +41 -0
  312. package/specialists/org/policies/action-approval.json +13 -0
  313. package/specialists/org/policies/agents-routing.json +13 -0
  314. package/specialists/org/policies/architecture.json +20 -0
  315. package/specialists/org/policies/bash-safety.json +13 -0
  316. package/specialists/org/policies/beads-hygiene.json +13 -0
  317. package/specialists/org/policies/bootstrap-state.json +13 -0
  318. package/specialists/org/policies/code-review.json +13 -0
  319. package/specialists/org/policies/coding-style.json +13 -0
  320. package/specialists/org/policies/comments.json +13 -0
  321. package/specialists/org/policies/commit-approval.json +13 -0
  322. package/specialists/org/policies/contract-preconditions.json +13 -0
  323. package/specialists/org/policies/deployment.json +20 -0
  324. package/specialists/org/policies/description.json +14 -0
  325. package/specialists/org/policies/design-approval.json +19 -0
  326. package/specialists/org/policies/doc-ownership.json +13 -0
  327. package/specialists/org/policies/file-path-fence.json +13 -0
  328. package/specialists/org/policies/framing.json +13 -0
  329. package/specialists/org/policies/git-workflow.json +13 -0
  330. package/specialists/org/policies/incident-response.json +15 -0
  331. package/specialists/org/policies/intake-triage.json +15 -0
  332. package/specialists/org/policies/neurodivergent-output.json +13 -0
  333. package/specialists/org/policies/no-fabrication.json +13 -0
  334. package/specialists/org/policies/patterns.json +13 -0
  335. package/specialists/org/policies/quality-gate-approval.json +17 -0
  336. package/specialists/org/policies/release-gates.json +13 -0
  337. package/specialists/org/policies/research-evidence.json +13 -0
  338. package/specialists/org/policies/review-before-change.json +13 -0
  339. package/specialists/org/policies/rollback.json +15 -0
  340. package/specialists/org/policies/scope-change.json +20 -0
  341. package/specialists/org/policies/secret-scan.json +13 -0
  342. package/specialists/org/policies/security-approval.json +17 -0
  343. package/specialists/org/policies/security.json +13 -0
  344. package/specialists/org/policies/session-efficiency.json +13 -0
  345. package/specialists/org/policies/skill-routing.json +13 -0
  346. package/specialists/org/policies/strategic-prioritization.json +17 -0
  347. package/specialists/org/policies/testing.json +13 -0
  348. package/specialists/org/policies/tool-invisibility.json +14 -0
  349. package/specialists/org/scopes/creative.json +54 -0
  350. package/specialists/org/scopes/operations.json +51 -0
  351. package/specialists/org/scopes/research.json +60 -0
  352. package/specialists/org/scopes/rnd.json +81 -0
  353. package/specialists/org/specialists/cx-accessibility.json +69 -0
  354. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  355. package/specialists/org/specialists/cx-architect.json +89 -0
  356. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  357. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  358. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  359. package/specialists/org/specialists/cx-debugger.json +75 -0
  360. package/specialists/org/specialists/cx-designer.json +85 -0
  361. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  362. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  363. package/specialists/org/specialists/cx-engineer.json +106 -0
  364. package/specialists/org/specialists/cx-evaluator.json +69 -0
  365. package/specialists/org/specialists/cx-explorer.json +69 -0
  366. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  367. package/specialists/org/specialists/cx-operations.json +72 -0
  368. package/specialists/org/specialists/cx-oracle.json +46 -0
  369. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  370. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  371. package/specialists/org/specialists/cx-product-manager.json +102 -0
  372. package/specialists/org/specialists/cx-qa.json +79 -0
  373. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  374. package/specialists/org/specialists/cx-release-manager.json +77 -0
  375. package/specialists/org/specialists/cx-researcher.json +82 -0
  376. package/specialists/org/specialists/cx-reviewer.json +71 -0
  377. package/specialists/org/specialists/cx-security.json +91 -0
  378. package/specialists/org/specialists/cx-sre.json +94 -0
  379. package/specialists/org/specialists/cx-test-automation.json +69 -0
  380. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  381. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  382. package/specialists/org/teams/accessibility-team.json +39 -0
  383. package/specialists/org/teams/design-team.json +40 -0
  384. package/specialists/org/teams/engineering-team.json +50 -0
  385. package/specialists/org/teams/governance-team.json +41 -0
  386. package/specialists/org/teams/operations-team.json +46 -0
  387. package/specialists/org/teams/product-management-team.json +45 -0
  388. package/specialists/org/teams/quality-team.json +49 -0
  389. package/specialists/org/teams/research-team.json +39 -0
  390. package/specialists/org/teams/strategy-team.json +48 -0
  391. package/specialists/org/teams/ux-research-team.json +39 -0
  392. package/specialists/prompts/_shared/validation-contract.md +1 -1
  393. package/specialists/prompts/_team-template.md +10 -0
  394. package/specialists/prompts/cx-accessibility.md +1 -0
  395. package/specialists/prompts/cx-ai-engineer.md +1 -1
  396. package/specialists/prompts/cx-business-strategist.md +1 -1
  397. package/specialists/prompts/cx-data-engineer.md +1 -1
  398. package/specialists/prompts/cx-designer.md +1 -0
  399. package/specialists/prompts/cx-devil-advocate.md +1 -1
  400. package/specialists/prompts/cx-docs-keeper.md +1 -1
  401. package/specialists/prompts/cx-evaluator.md +1 -1
  402. package/specialists/prompts/cx-explorer.md +1 -1
  403. package/specialists/prompts/cx-operations.md +1 -1
  404. package/specialists/prompts/cx-oracle.md +7 -3
  405. package/specialists/prompts/cx-orchestrator.md +17 -1
  406. package/specialists/prompts/cx-platform-engineer.md +1 -1
  407. package/specialists/prompts/cx-product-manager.md +2 -0
  408. package/specialists/prompts/cx-release-manager.md +1 -1
  409. package/specialists/prompts/cx-researcher.md +7 -4
  410. package/specialists/prompts/cx-sre.md +1 -1
  411. package/specialists/prompts/cx-test-automation.md +2 -2
  412. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  413. package/specialists/prompts/cx-ux-researcher.md +2 -1
  414. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  415. package/templates/demos/scripts/capability-contract.json +25 -0
  416. package/templates/demos/scripts/intake-triage.json +25 -0
  417. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  418. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  419. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  420. package/templates/demos/tapes/capability-contract.tape +39 -0
  421. package/templates/demos/tapes/intake-triage.tape +39 -0
  422. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  423. package/templates/distribution/construct-brand.typ +23 -18
  424. package/templates/distribution/construct-decision.typ +1 -1
  425. package/templates/distribution/construct-pdf.typ +1 -1
  426. package/templates/distribution/construct-prd.typ +1 -1
  427. package/templates/distribution/construct-research.typ +1 -1
  428. package/templates/distribution/mermaid-puppeteer.json +8 -0
  429. package/templates/docs/persona-artifact.md +1 -1
  430. package/templates/docs/prd.md +6 -0
  431. package/apps/chat/engine/ai-sdk-agent.mjs +0 -183
  432. package/apps/chat/engine/loop-driver.mjs +0 -211
  433. package/apps/chat/engine/models.mjs +0 -122
  434. package/apps/chat/engine/provider-adapters.mjs +0 -171
  435. package/apps/chat/engine/tools/permission.mjs +0 -54
  436. package/apps/chat/engine/tools/primitives.mjs +0 -180
  437. package/apps/chat/engine/tools/registry.mjs +0 -122
  438. package/apps/chat/engine/turn-controls.mjs +0 -70
  439. package/lib/boundary.mjs +0 -127
  440. package/lib/certification/dashboard-api.mjs +0 -71
  441. package/lib/chat/cli.mjs +0 -333
  442. package/lib/chat/command-suggest.mjs +0 -161
  443. package/lib/chat/commands.mjs +0 -215
  444. package/lib/chat/config.mjs +0 -142
  445. package/lib/chat/context-compactor.mjs +0 -250
  446. package/lib/chat/context-continuation.mjs +0 -253
  447. package/lib/chat/continuation-source.mjs +0 -58
  448. package/lib/chat/demo-guide.mjs +0 -61
  449. package/lib/chat/design-tokens.mjs +0 -91
  450. package/lib/chat/desktop-binary.mjs +0 -81
  451. package/lib/chat/desktop-build.mjs +0 -130
  452. package/lib/chat/desktop-launcher.mjs +0 -133
  453. package/lib/chat/evidence.mjs +0 -145
  454. package/lib/chat/export.mjs +0 -74
  455. package/lib/chat/harness/driver.mjs +0 -91
  456. package/lib/chat/list-picker.mjs +0 -112
  457. package/lib/chat/model-picker.mjs +0 -356
  458. package/lib/chat/openrouter-fallback.mjs +0 -151
  459. package/lib/chat/permission-prompt.mjs +0 -33
  460. package/lib/chat/picker-catalog.mjs +0 -45
  461. package/lib/chat/policy-telemetry.mjs +0 -34
  462. package/lib/chat/present.mjs +0 -246
  463. package/lib/chat/session-context.mjs +0 -39
  464. package/lib/chat/session-persist.mjs +0 -73
  465. package/lib/chat/session-restore.mjs +0 -71
  466. package/lib/chat/session-settings.mjs +0 -53
  467. package/lib/chat/system-prompt.mjs +0 -52
  468. package/lib/chat/transparency.mjs +0 -93
  469. package/lib/chat/tui/color-scheme.mjs +0 -42
  470. package/lib/chat/tui/markdown.mjs +0 -123
  471. package/lib/chat/tui/presentation.mjs +0 -100
  472. package/lib/chat/tui/render.mjs +0 -500
  473. package/lib/chat/tui/turn-block.mjs +0 -284
  474. package/lib/chat/tui/turn-present.mjs +0 -18
  475. package/lib/chat/tui/turn-state.mjs +0 -88
  476. package/lib/chat/tui/usage.mjs +0 -122
  477. package/lib/chat/web-commands.mjs +0 -146
  478. package/lib/chat/web-launcher.mjs +0 -63
  479. package/lib/chat/web-picker-keys.mjs +0 -46
  480. package/lib/chat/web-session.mjs +0 -159
  481. package/lib/config/alias.mjs +0 -57
  482. package/lib/dashboard-demo.mjs +0 -71
  483. package/lib/dashboard-static.mjs +0 -175
  484. package/lib/install/desktop-binary-download.mjs +0 -88
  485. package/lib/profiles/loader.mjs +0 -123
  486. package/lib/profiles/rebrand.mjs +0 -46
  487. package/lib/server/auth.mjs +0 -169
  488. package/lib/server/chat-loop.mjs +0 -622
  489. package/lib/server/chat.mjs +0 -336
  490. package/lib/server/cors.mjs +0 -77
  491. package/lib/server/csrf.mjs +0 -103
  492. package/lib/server/demo-preview.mjs +0 -63
  493. package/lib/server/index.mjs +0 -2641
  494. package/lib/server/insights.mjs +0 -780
  495. package/lib/server/langfuse-login.mjs +0 -58
  496. package/lib/server/rate-limit.mjs +0 -93
  497. package/lib/server/telemetry-login.mjs +0 -100
  498. package/lib/server/webhook.mjs +0 -512
  499. package/specialists/contracts.json +0 -1032
  500. package/specialists/contracts.schema.json +0 -83
  501. package/specialists/policy-inventory.json +0 -188
  502. package/specialists/registry.json +0 -1412
  503. package/specialists/role-manifests.json +0 -217
  504. 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
- }