@geraldmaron/construct 1.2.3 → 1.3.1

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