@geraldmaron/construct 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
@@ -1,512 +0,0 @@
1
- /**
2
- * lib/server/webhook.mjs — Webhook ingestion + Slack slash command handler.
3
- *
4
- * Receives inbound webhook payloads from external providers (GitHub, Jira,
5
- * Slack, Confluence, etc.), normalises them through the provider's
6
- * `normalizeWebhook` capability, and routes the resulting event to:
7
- *
8
- * 1. The approval queue — if the event matches a configured approval rule.
9
- * 2. A snapshot trigger — if the event is tagged as a significant change.
10
- * 3. The SSE notification stream — so the dashboard updates in real-time.
11
- *
12
- * Route: POST /api/webhooks/:provider
13
- *
14
- * Security:
15
- * Each provider validates its own signature. The handler calls
16
- * provider.verifyWebhookSignature(payload, headers) before processing.
17
- * Requests that fail signature verification are rejected with 401.
18
- *
19
- * Provider registration:
20
- * Providers are loaded lazily from providers/<name>/index.mjs. A provider
21
- * is eligible for webhook ingestion if it declares 'webhook' in its
22
- * capabilities array and exports normalizeWebhook.
23
- *
24
- * Slack slash commands:
25
- * Route: POST /api/slack/commands
26
- * Requires SLACK_SIGNING_SECRET in config.env.
27
- * Set the Slack app's slash command Request URL to:
28
- * https://<your-domain>/api/slack/commands
29
- *
30
- * Supported commands:
31
- * /construct snapshot — run a live embed snapshot and post summary
32
- * /construct risks — surface escalating risks from the knowledge base
33
- * /construct plan — post the current plan.md workflow summary
34
- */
35
-
36
- import { createHmac, timingSafeEqual } from 'crypto';
37
- import { existsSync, readFileSync } from 'fs';
38
- import { join } from 'path';
39
- import { fileURLToPath } from 'url';
40
- import { homedir as osHomedir } from 'os';
41
- import { configDir } from '../config/xdg.mjs';
42
-
43
- const __dirname = fileURLToPath(new URL('.', import.meta.url));
44
- const ROOT_DIR = join(__dirname, '..', '..');
45
- const HOME = osHomedir();
46
-
47
- // ── Provider loader ────────────────────────────────────────────────────────
48
-
49
- const _providerCache = new Map();
50
-
51
- async function loadProvider(name) {
52
- if (_providerCache.has(name)) return _providerCache.get(name);
53
- const providerPath = join(ROOT_DIR, 'providers', name, 'index.mjs');
54
- if (!existsSync(providerPath)) return null;
55
- try {
56
- const mod = await import(providerPath);
57
- _providerCache.set(name, mod);
58
- return mod;
59
- } catch {
60
- return null;
61
- }
62
- }
63
-
64
- // ── Signature verification helpers ────────────────────────────────────────
65
-
66
- /**
67
- * GitHub: X-Hub-Signature-256: sha256=<hex>
68
- */
69
- function verifyGitHubSignature(body, headers, secret) {
70
- const sig = headers['x-hub-signature-256'];
71
- if (!sig || !secret) return !secret; // open if no secret configured
72
- const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex');
73
- try {
74
- return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
75
- } catch {
76
- return false;
77
- }
78
- }
79
-
80
- /**
81
- * Jira: no standard HMAC — verify via shared token in X-Atlassian-Token header.
82
- */
83
- function verifyJiraSignature(body, headers, secret) {
84
- if (!secret) return true;
85
- const token = headers['x-atlassian-token'] || headers['x-webhook-token'];
86
- if (!token) return false;
87
- try {
88
- return timingSafeEqual(Buffer.from(token), Buffer.from(secret));
89
- } catch {
90
- return false;
91
- }
92
- }
93
-
94
- /**
95
- * Slack: X-Slack-Signature: v0=<hex> with X-Slack-Request-Timestamp
96
- */
97
- function verifySlackSignature(body, headers, secret) {
98
- const sig = headers['x-slack-signature'];
99
- const ts = headers['x-slack-request-timestamp'];
100
- if (!sig || !ts || !secret) return !secret;
101
- // Reject requests older than 5 minutes
102
- if (Math.abs(Date.now() / 1000 - parseInt(ts, 10)) > 300) return false;
103
- const baseString = `v0:${ts}:${body}`;
104
- const expected = 'v0=' + createHmac('sha256', secret).update(baseString).digest('hex');
105
- try {
106
- return timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
107
- } catch {
108
- return false;
109
- }
110
- }
111
-
112
- const SIGNATURE_VERIFIERS = {
113
- github: verifyGitHubSignature,
114
- jira: verifyJiraSignature,
115
- slack: verifySlackSignature,
116
- confluence: verifyJiraSignature, // same Atlassian token scheme
117
- };
118
-
119
- // ── Event classification ────────────────────────────────────────────────────
120
-
121
- /**
122
- * Returns { significant: boolean, reason: string } for a normalised event.
123
- * Significant events trigger an immediate snapshot; others are logged only.
124
- */
125
- function classifyEvent(providerName, event) {
126
- const type = (event?.type || '').toLowerCase();
127
- const SIGNIFICANT = [
128
- 'pr.merged', 'pr.closed',
129
- 'issue.created', 'issue.closed', 'issue.transitioned',
130
- 'push',
131
- 'message.posted',
132
- 'page.created', 'page.updated',
133
- ];
134
- const significant = SIGNIFICANT.some(t => type === t || type.startsWith(t));
135
- return { significant, reason: significant ? `${providerName}:${type} is a tracked event` : 'informational' };
136
- }
137
-
138
- // ── Main handler ────────────────────────────────────────────────────────────
139
-
140
- /**
141
- * @param {object} opts
142
- * @param {ApprovalQueue} opts.approvalQueue
143
- * @param {function} opts.notifyClients — triggers SSE broadcast
144
- * @param {object} opts.webhookSecrets — { github: '...', slack: '...', ... }
145
- */
146
- export function createWebhookHandler({ approvalQueue, notifyClients, webhookSecrets = {} }) {
147
- return async function handleWebhook(req, res) {
148
- // Extract provider name from path: /api/webhooks/:provider
149
- const match = req.url.match(/\/api\/webhooks\/([a-z0-9_-]+)/i);
150
- if (!match) {
151
- res.writeHead(400, { 'Content-Type': 'application/json' });
152
- res.end(JSON.stringify({ error: 'Missing provider name in path' }));
153
- return;
154
- }
155
-
156
- const providerName = match[1].toLowerCase();
157
-
158
- // Collect body
159
- const chunks = [];
160
- await new Promise((resolve, reject) => {
161
- req.on('data', c => chunks.push(c));
162
- req.on('end', resolve);
163
- req.on('error', reject);
164
- });
165
- const rawBody = Buffer.concat(chunks).toString('utf8');
166
-
167
- // Signature verification
168
- const verifier = SIGNATURE_VERIFIERS[providerName];
169
- const secret = webhookSecrets[providerName] || process.env[`WEBHOOK_SECRET_${providerName.toUpperCase()}`];
170
- if (verifier && !verifier(rawBody, req.headers, secret)) {
171
- res.writeHead(401, { 'Content-Type': 'application/json' });
172
- res.end(JSON.stringify({ error: 'Webhook signature verification failed' }));
173
- return;
174
- }
175
-
176
- // Parse payload
177
- let payload;
178
- try {
179
- payload = JSON.parse(rawBody || '{}');
180
- } catch {
181
- res.writeHead(400, { 'Content-Type': 'application/json' });
182
- res.end(JSON.stringify({ error: 'Invalid JSON payload' }));
183
- return;
184
- }
185
-
186
- // Normalise via provider if available
187
- let event = { type: 'unknown', raw: payload, provider: providerName, receivedAt: new Date().toISOString() };
188
- const provider = await loadProvider(providerName);
189
- if (provider?.normalizeWebhook) {
190
- try {
191
- event = { ...event, ...provider.normalizeWebhook(payload, req.headers) };
192
- } catch {
193
- // Normalisation failure is non-fatal — log and continue with raw event
194
- }
195
- }
196
-
197
- // Classify and route
198
- const { significant } = classifyEvent(providerName, event);
199
-
200
- if (significant && approvalQueue) {
201
- // Queue for embed awareness (not necessarily requiring human approval)
202
- approvalQueue.enqueue({
203
- action: `webhook.${providerName}.${event.type || 'event'}`,
204
- payload: event,
205
- source: `webhook:${providerName}`,
206
- autoApprove: true, // informational enqueue; hybrid model decides actual approval need
207
- });
208
- }
209
-
210
- // Notify dashboard clients
211
- if (notifyClients) notifyClients();
212
-
213
- res.writeHead(200, { 'Content-Type': 'application/json' });
214
- res.end(JSON.stringify({ ok: true, provider: providerName, type: event.type, significant }));
215
- };
216
- }
217
-
218
- // ── Slack slash command handler ────────────────────────────────────────────
219
-
220
- /**
221
- * Parse application/x-www-form-urlencoded body into an object.
222
- */
223
- function parseFormBody(raw) {
224
- const out = {};
225
- for (const pair of raw.split('&')) {
226
- const eq = pair.indexOf('=');
227
- if (eq === -1) continue;
228
- const k = decodeURIComponent(pair.slice(0, eq).replace(/\+/g, ' '));
229
- const v = decodeURIComponent(pair.slice(eq + 1).replace(/\+/g, ' '));
230
- out[k] = v;
231
- }
232
- return out;
233
- }
234
-
235
- /**
236
- * Post a message back to Slack using response_url (no bot token required for
237
- * slash command responses). Falls back to bot token + chat.postMessage if the
238
- * response_url is absent (e.g. in tests).
239
- */
240
- async function postSlackMessage(responseUrl, text, botToken) {
241
- const body = JSON.stringify({ response_type: 'in_channel', text });
242
-
243
- if (responseUrl) {
244
- const { fetch: nodeFetch } = await import('node:http');
245
- // Use built-in fetch (Node 18+) or a simple https post
246
- const url = new URL(responseUrl);
247
- const isHttps = url.protocol === 'https:';
248
- const mod = isHttps ? await import('node:https') : await import('node:http');
249
- return new Promise((resolve, reject) => {
250
- const req = mod.request(responseUrl, {
251
- method: 'POST',
252
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
253
- }, (res) => {
254
- res.resume();
255
- res.on('end', resolve);
256
- });
257
- req.on('error', reject);
258
- req.write(body);
259
- req.end();
260
- });
261
- }
262
-
263
- if (botToken) {
264
- const https = await import('node:https');
265
- const postBody = JSON.stringify({ text });
266
- return new Promise((resolve, reject) => {
267
- const req = https.request('https://slack.com/api/chat.postMessage', {
268
- method: 'POST',
269
- headers: {
270
- 'Content-Type': 'application/json',
271
- 'Authorization': `Bearer ${botToken}`,
272
- 'Content-Length': Buffer.byteLength(postBody),
273
- },
274
- }, (res) => { res.resume(); res.on('end', resolve); });
275
- req.on('error', reject);
276
- req.write(postBody);
277
- req.end();
278
- });
279
- }
280
- }
281
-
282
- /**
283
- * Load config.env values as an object.
284
- */
285
- function loadConfigEnv() {
286
- const envFile = join(configDir(HOME), 'config.env');
287
- if (!existsSync(envFile)) return {};
288
- const lines = readFileSync(envFile, 'utf8').split('\n');
289
- const out = {};
290
- for (const line of lines) {
291
- const trimmed = line.trim();
292
- if (!trimmed || trimmed.startsWith('#')) continue;
293
- const eq = trimmed.indexOf('=');
294
- if (eq === -1) continue;
295
- out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim().replace(/^['"]|['"]$/g, '');
296
- }
297
- return out;
298
- }
299
-
300
- /**
301
- * Run an embed snapshot and return a formatted Slack message string.
302
- */
303
- async function runSnapshotForSlack() {
304
- try {
305
- const env = { ...process.env, ...loadConfigEnv() };
306
- const { ProviderRegistry } = await import('../embed/providers/registry.mjs');
307
- const { SnapshotEngine, renderMarkdown } = await import('../embed/snapshot.mjs');
308
- const { EMPTY_CONFIG } = await import('../embed/config.mjs');
309
-
310
- const registry = await ProviderRegistry.fromEnv(env);
311
- const { resolveAutoEmbedSources } = await import('../embed/auto-sources.mjs');
312
- const sources = resolveAutoEmbedSources({ cwd: process.cwd(), env, registry });
313
- if (!sources.length) return '⚠️ No embed sources configured. Add credentials to config.env.';
314
-
315
- const config = { ...EMPTY_CONFIG, sources, snapshot: { maxItems: 10 } };
316
- const engine = new SnapshotEngine(registry, config);
317
- const snapshot = await engine.generate();
318
-
319
- const totalItems = snapshot.sections.reduce((n, s) => n + s.items.length, 0);
320
- const errorCount = snapshot.errors.length;
321
-
322
- const lines = [`*Construct Snapshot* — ${new Date(snapshot.generatedAt).toLocaleString()}`];
323
- lines.push(`${totalItems} items across ${snapshot.sections.length} sources${errorCount ? ` (${errorCount} errors)` : ''}`);
324
- lines.push('');
325
-
326
- for (const section of snapshot.sections) {
327
- if (!section.items.length) continue;
328
- lines.push(`*${section.provider}* (${section.items.length})`);
329
- for (const item of section.items.slice(0, 5)) {
330
- const title = item.title || item.summary || item.text || '(untitled)';
331
- const url = item.url || item.html_url || '';
332
- lines.push(url ? `• <${url}|${title}>` : `• ${title}`);
333
- }
334
- if (section.items.length > 5) lines.push(` _…and ${section.items.length - 5} more_`);
335
- }
336
-
337
- if (errorCount) {
338
- lines.push('');
339
- lines.push(`⚠️ Errors: ${snapshot.errors.map(e => `${e.source}: ${e.error}`).join(', ')}`);
340
- }
341
-
342
- return lines.join('\n');
343
- } catch (err) {
344
- return `❌ Snapshot failed: ${err.message}`;
345
- }
346
- }
347
-
348
- /**
349
- * Surface escalating risks from the knowledge base.
350
- */
351
- async function runRisksForSlack() {
352
- try {
353
- const { buildTrendReport } = await import('../knowledge/trends.mjs');
354
- const report = buildTrendReport(ROOT_DIR);
355
-
356
- if (!report.escalatingRisks?.length && !report.decisionDrift?.length) {
357
- return '✅ No escalating risks detected in the current knowledge base.';
358
- }
359
-
360
- const lines = ['*Construct Risks*', ''];
361
-
362
- if (report.escalatingRisks?.length) {
363
- lines.push('*Escalating Risks*');
364
- for (const r of report.escalatingRisks) {
365
- lines.push(`• ${r.summary} _(↑${r.escalationScore}× — ${r.recentCount} recent)_`);
366
- }
367
- }
368
-
369
- if (report.decisionDrift?.length) {
370
- lines.push('');
371
- lines.push('*Decision Drift*');
372
- for (const d of report.decisionDrift) {
373
- lines.push(`• ${d.decision.summary} _(drift score ${d.driftScore})_`);
374
- }
375
- }
376
-
377
- return lines.join('\n');
378
- } catch (err) {
379
- return `❌ Risk analysis failed: ${err.message}`;
380
- }
381
- }
382
-
383
- /**
384
- * Summarise plan.md as a Slack message.
385
- */
386
- function runPlanForSlack() {
387
- try {
388
- const planPath = join(ROOT_DIR, 'plan.md');
389
- if (!existsSync(planPath)) return '⚠️ No plan.md found in the project root.';
390
-
391
- const content = readFileSync(planPath, 'utf8');
392
- const lines = content.split('\n');
393
-
394
- // Extract phase headers and their done/in-progress items
395
- const output = ['*Construct Plan*', ''];
396
- let currentPhase = null;
397
- let phaseItems = [];
398
- let inTable = false;
399
-
400
- for (const line of lines) {
401
- if (line.startsWith('## Phase') || line.startsWith('## Goal') || line.startsWith('## Next')) {
402
- if (currentPhase && phaseItems.length) {
403
- output.push(`*${currentPhase}*`);
404
- output.push(...phaseItems.slice(0, 6));
405
- if (phaseItems.length > 6) output.push(` _…and ${phaseItems.length - 6} more_`);
406
- output.push('');
407
- }
408
- currentPhase = line.replace(/^#+\s*/, '').trim();
409
- phaseItems = [];
410
- inTable = false;
411
- continue;
412
- }
413
- // Table rows with status
414
- if (line.includes('| done |') || line.includes('| in-progress |') || line.includes('| blocked |')) {
415
- const match = line.match(/\|\s*[\d.]+\s*\|\s*(.+?)\s*\|\s*(done|in-progress|blocked)\s*\|/);
416
- if (match) {
417
- const emoji = match[2] === 'done' ? '✅' : match[2] === 'in-progress' ? '🔄' : '🚫';
418
- phaseItems.push(`${emoji} ${match[1].trim()}`);
419
- }
420
- }
421
- }
422
-
423
- // Flush last phase
424
- if (currentPhase && phaseItems.length) {
425
- output.push(`*${currentPhase}*`);
426
- output.push(...phaseItems.slice(0, 6));
427
- }
428
-
429
- // Extract next steps if present
430
- const nextIdx = lines.findIndex(l => l.startsWith('## Next Steps') || l.startsWith('## Next'));
431
- if (nextIdx !== -1) {
432
- const nextLines = [];
433
- for (let i = nextIdx + 1; i < Math.min(nextIdx + 10, lines.length); i++) {
434
- if (lines[i].startsWith('#')) break;
435
- const item = lines[i].trim();
436
- if (item.startsWith('1.') || item.startsWith('2.') || item.startsWith('3.') || item.startsWith('-')) {
437
- nextLines.push(item);
438
- }
439
- }
440
- if (nextLines.length) {
441
- output.push('');
442
- output.push('*Next Steps*');
443
- output.push(...nextLines);
444
- }
445
- }
446
-
447
- return output.join('\n') || '_(Plan is empty)_';
448
- } catch (err) {
449
- return `❌ Could not read plan: ${err.message}`;
450
- }
451
- }
452
-
453
- /**
454
- * @param {object} opts
455
- * @param {object} opts.webhookSecrets
456
- */
457
- export function createSlackCommandHandler({ webhookSecrets = {} } = {}) {
458
- return async function handleSlackCommand(req, res) {
459
- // Collect body
460
- const chunks = [];
461
- await new Promise((resolve, reject) => {
462
- req.on('data', c => chunks.push(c));
463
- req.on('end', resolve);
464
- req.on('error', reject);
465
- });
466
- const rawBody = Buffer.concat(chunks).toString('utf8');
467
-
468
- // Verify Slack signature
469
- const env = loadConfigEnv();
470
- const signingSecret = webhookSecrets.slack
471
- || env.SLACK_SIGNING_SECRET
472
- || process.env.SLACK_SIGNING_SECRET;
473
-
474
- if (signingSecret && !verifySlackSignature(rawBody, req.headers, signingSecret)) {
475
- res.writeHead(401, { 'Content-Type': 'application/json' });
476
- res.end(JSON.stringify({ error: 'Invalid Slack signature' }));
477
- return;
478
- }
479
-
480
- const params = parseFormBody(rawBody);
481
- const text = (params.text || '').trim().toLowerCase();
482
- const responseUrl = params.response_url || '';
483
- const botToken = env.SLACK_BOT_TOKEN || process.env.SLACK_BOT_TOKEN;
484
-
485
- // Acknowledge immediately — Slack requires a 200 within 3 seconds
486
- res.writeHead(200, { 'Content-Type': 'application/json' });
487
-
488
- let ackText;
489
- if (text === 'snapshot') ackText = '⏳ Running snapshot…';
490
- else if (text === 'risks') ackText = '⏳ Analysing risks…';
491
- else if (text === 'plan') ackText = '⏳ Loading plan…';
492
- else {
493
- res.end(JSON.stringify({
494
- response_type: 'ephemeral',
495
- text: 'Unknown command. Try: `/construct snapshot`, `/construct risks`, `/construct plan`',
496
- }));
497
- return;
498
- }
499
-
500
- res.end(JSON.stringify({ response_type: 'in_channel', text: ackText }));
501
-
502
- // Do the work after acknowledging
503
- setImmediate(async () => {
504
- let message;
505
- if (text === 'snapshot') message = await runSnapshotForSlack();
506
- else if (text === 'risks') message = await runRisksForSlack();
507
- else message = runPlanForSlack();
508
-
509
- await postSlackMessage(responseUrl, message, botToken).catch(() => {});
510
- });
511
- };
512
- }