@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
@@ -5,10 +5,8 @@
5
5
  * - Async (preferred): `extractDocumentTextAsync` routes PDF/Office/HTML
6
6
  * through the docling Python sidecar and audio/video through whisper.cpp.
7
7
  * Returns { text, markdown, metadata, droppedInfo, ... }.
8
- * - Sync (legacy): `extractDocumentText` keeps regex/CLI extractors.
9
- * PDF/Office output is lower fidelity (tables flattened, formulas
10
- * dropped, scanned pages return empty). Audio/video throws
11
- * { code: 'ASR_REQUIRED' } on this path.
8
+ * - Sync (legacy): `extractDocumentText` for text/transcript/calendar/email;
9
+ * PDF/Office zip formats require docling or the node-native unpdf/mammoth path.
12
10
  *
13
11
  * `extractDocumentMetadata` returns frontmatter + title/authors/dates.
14
12
  *
@@ -46,17 +44,12 @@ export const AUDIO_VIDEO_EXTS = new Set(['.mp3', '.wav', '.m4a', '.mp4', '.mov',
46
44
 
47
45
  export const ZIP_DOCUMENT_EXTS = new Set(['.docx', '.xlsx', '.pptx', '.odt', '.ods']);
48
46
 
49
- // Office zip formats with no Node-native backend callers must route through docling
50
- // or opt into the legacy zip-xml path via CONSTRUCT_ALLOW_LEGACY_EXTRACT=1.
47
+ // Office zip formats with no Node-native backend require docling.
51
48
  const OFFICE_REQUIRES_DOCLING_EXTS = new Set(['.xlsx', '.pptx', '.odt', '.ods']);
52
49
 
53
- function legacyExtractAllowed(env = process.env) {
54
- return env.CONSTRUCT_ALLOW_LEGACY_EXTRACT === '1';
55
- }
56
-
57
50
  function makeOfficeRequiresDoclingError(extension) {
58
51
  const err = new Error(
59
- `${extension} extraction requires docling. Run \`construct install --with-docling\`, use high-fidelity ingest, or set CONSTRUCT_ALLOW_LEGACY_EXTRACT=1 for the legacy zip-xml path.`,
52
+ `${extension} extraction requires docling. Run \`construct install --with-docling\` or use high-fidelity ingest.`,
60
53
  );
61
54
  err.code = 'OFFICE_REQUIRES_DOCLING';
62
55
  err.extension = extension;
@@ -65,7 +58,7 @@ function makeOfficeRequiresDoclingError(extension) {
65
58
 
66
59
  function makePdfRequiresDoclingError() {
67
60
  const err = new Error(
68
- 'PDF extraction requires docling, the node-native unpdf backend, or CONSTRUCT_ALLOW_LEGACY_EXTRACT=1 for pdftotext/PDFKit.',
61
+ 'PDF extraction requires docling or the node-native unpdf backend. Run `construct install --with-docling` or ensure unpdf is installed.',
69
62
  );
70
63
  err.code = 'PDF_REQUIRES_DOCLING';
71
64
  return err;
@@ -85,9 +78,6 @@ export const EXTRACTABLE_DOCUMENT_EXTS = new Set([
85
78
  '.pdf',
86
79
  ]);
87
80
 
88
- // Minimum extracted characters per page that distinguishes text-bearing PDFs
89
- // from scanned images. Below this threshold droppedInfo is emitted.
90
- const PDF_MIN_TEXT_THRESHOLD = 50;
91
81
 
92
82
  function normalizeText(value) {
93
83
  return String(value)
@@ -149,103 +139,6 @@ function extractUtf8(filePath, extension) {
149
139
  return { text, method: 'utf8', droppedInfo: [], structured: null };
150
140
  }
151
141
 
152
- function zipEntries(filePath) {
153
- return run('unzip', ['-Z1', filePath]).split('\n').map((line) => line.trim()).filter(Boolean);
154
- }
155
-
156
- function unzipEntry(filePath, entryPath) {
157
- return run('unzip', ['-p', filePath, entryPath]);
158
- }
159
-
160
- function extractZipDocument(filePath, extension) {
161
- const entries = zipEntries(filePath);
162
- let targets = [];
163
- const droppedInfo = [];
164
-
165
- if (extension === '.docx') {
166
- targets = entries.filter((entry) => entry === 'word/document.xml' || entry.startsWith('word/header') || entry.startsWith('word/footer'));
167
-
168
- // Count embedded images (word/media/ folder entries)
169
- const imageCount = entries.filter((e) => e.startsWith('word/media/')).length;
170
- if (imageCount > 0) {
171
- droppedInfo.push(makeDropInfo({ kind: 'inline-image', count: imageCount, reason: 'Embedded images not extracted from DOCX', recoverable: true }));
172
- }
173
-
174
- // Count tables in the main document XML
175
- try {
176
- const docXml = unzipEntry(filePath, 'word/document.xml');
177
- const tableCount = (docXml.match(/<w:tbl[\s>]/g) || []).length;
178
- if (tableCount > 0) {
179
- droppedInfo.push(makeDropInfo({ kind: 'table', count: tableCount, reason: 'Table structure flattened; cell boundaries and column headers not preserved', recoverable: true }));
180
- }
181
- } catch { /* ignore — main extraction still proceeds */ }
182
-
183
- // Count comments
184
- if (entries.includes('word/comments.xml')) {
185
- try {
186
- const commentsXml = unzipEntry(filePath, 'word/comments.xml');
187
- const commentCount = (commentsXml.match(/<w:comment\s/g) || []).length;
188
- if (commentCount > 0) {
189
- droppedInfo.push(makeDropInfo({ kind: 'comment', count: commentCount, reason: 'Document review comments not included in extracted text', recoverable: true }));
190
- }
191
- } catch { /* ignore */ }
192
- }
193
- } else if (extension === '.xlsx') {
194
- targets = entries.filter((entry) => entry === 'xl/sharedStrings.xml' || /^xl\/worksheets\/sheet\d+\.xml$/.test(entry));
195
-
196
- // Count formula cells across all worksheets
197
- let formulaCount = 0;
198
- for (const sheetEntry of entries.filter((e) => /^xl\/worksheets\/sheet\d+\.xml$/.test(e))) {
199
- try {
200
- const sheetXml = unzipEntry(filePath, sheetEntry);
201
- formulaCount += (sheetXml.match(/<f\b/g) || []).length;
202
- } catch { /* ignore */ }
203
- }
204
- if (formulaCount > 0) {
205
- droppedInfo.push(makeDropInfo({ kind: 'formula', count: formulaCount, reason: 'Spreadsheet formulas not evaluated; only cell string values extracted', recoverable: true }));
206
- }
207
-
208
- // Count embedded images
209
- const imageCount = entries.filter((e) => e.startsWith('xl/media/')).length;
210
- if (imageCount > 0) {
211
- droppedInfo.push(makeDropInfo({ kind: 'inline-image', count: imageCount, reason: 'Embedded images not extracted from XLSX', recoverable: true }));
212
- }
213
- } else if (extension === '.pptx') {
214
- targets = entries.filter((entry) => /^ppt\/slides\/slide\d+\.xml$/.test(entry));
215
-
216
- // Count speaker notes slides
217
- const notesCount = entries.filter((e) => /^ppt\/notesSlides\/notesSlide\d+\.xml$/.test(e)).length;
218
- if (notesCount > 0) {
219
- droppedInfo.push(makeDropInfo({ kind: 'speaker-notes', count: notesCount, reason: 'Speaker notes slides not included in extracted text', recoverable: true }));
220
- }
221
-
222
- // Count slides with animation sequences
223
- let animationCount = 0;
224
- for (const slideEntry of targets) {
225
- try {
226
- const slideXml = unzipEntry(filePath, slideEntry);
227
- if (/<p:timing\b/.test(slideXml)) animationCount++;
228
- } catch { /* ignore */ }
229
- }
230
- if (animationCount > 0) {
231
- droppedInfo.push(makeDropInfo({ kind: 'animation', count: animationCount, reason: 'Slide animation timing sequences not preserved in extracted text', recoverable: false }));
232
- }
233
-
234
- // Count embedded images
235
- const imageCount = entries.filter((e) => e.startsWith('ppt/media/')).length;
236
- if (imageCount > 0) {
237
- droppedInfo.push(makeDropInfo({ kind: 'inline-image', count: imageCount, reason: 'Embedded images not extracted from PPTX', recoverable: true }));
238
- }
239
- } else if (extension === '.odt' || extension === '.ods') {
240
- targets = entries.filter((entry) => entry === 'content.xml');
241
- }
242
-
243
- if (targets.length === 0) throw new Error(`No readable document entries found for ${extension}`);
244
-
245
- const text = normalizeText(targets.map((entry) => stripMarkup(unzipEntry(filePath, entry))).join('\n\n'));
246
- return { text, method: 'zip-xml', droppedInfo, structured: null };
247
- }
248
-
249
142
  // Minimal RTF-to-text: strip control words, groups, and escapes. RTF is a
250
143
  // text format, so this works everywhere; it is the cross-platform fallback when
251
144
  // textutil (macOS only) is absent. Lossy on complex documents, but never throws.
@@ -280,84 +173,12 @@ function extractRichText(filePath, extension) {
280
173
  throw new Error('textutil not available for rich-text extraction');
281
174
  }
282
175
 
283
- function extractPdfWithPdftotext(filePath) {
284
- return {
285
- text: normalizeText(run('pdftotext', ['-layout', '-nopgbrk', filePath, '-'])),
286
- method: 'pdftotext',
287
- droppedInfo: [],
288
- structured: null,
289
- };
290
- }
291
-
292
- function extractPdfWithSwift(filePath) {
293
- const script = `
294
- import Foundation
295
- import PDFKit
296
-
297
- let path = CommandLine.arguments[1]
298
- let url = URL(fileURLWithPath: path)
299
- guard let document = PDFDocument(url: url) else {
300
- fputs("Unable to open PDF\\n", stderr)
301
- exit(1)
302
- }
303
- print(document.string ?? "")
304
- `;
305
- return {
306
- text: normalizeText(run('swift', ['-e', script, filePath])),
307
- method: 'swift-pdfkit',
308
- droppedInfo: [],
309
- structured: null,
310
- };
311
- }
312
-
313
- function extractPdfWithMdls(filePath) {
314
- const text = extractWithMdls(filePath);
315
- return { text, method: 'mdls', droppedInfo: [], structured: null };
316
- }
317
-
318
176
  function extractWithMdls(filePath) {
319
177
  const text = normalizeText(run('mdls', ['-raw', '-name', 'kMDItemTextContent', filePath]));
320
178
  if (!text || text === '(null)') throw new Error('Spotlight text extraction unavailable');
321
179
  return text;
322
180
  }
323
181
 
324
- function extractPdf(filePath) {
325
- let extracted;
326
- if (commandExists('pdftotext')) {
327
- extracted = extractPdfWithPdftotext(filePath);
328
- } else if (process.platform === 'darwin' && commandExists('swift')) {
329
- try {
330
- extracted = extractPdfWithSwift(filePath);
331
- } catch {
332
- extracted = extractPdfWithMdls(filePath);
333
- }
334
- } else if (process.platform === 'darwin' && commandExists('mdls')) {
335
- extracted = extractPdfWithMdls(filePath);
336
- } else {
337
- throw new Error('No PDF extraction backend available');
338
- }
339
-
340
- // Scanned-PDF heuristic: if extracted text is very short relative to file size,
341
- // the PDF probably contains only images and needs OCR to be useful.
342
- const stat = statSync(filePath);
343
- const pages = Math.max(1, Math.ceil(stat.size / 50_000));
344
- const threshold = PDF_MIN_TEXT_THRESHOLD * pages;
345
- if (extracted.text.length < threshold) {
346
- const ocrHint = process.env.CONSTRUCT_PDF_OCR_HINT || 'none';
347
- const hintMsg = ocrHint !== 'none'
348
- ? `OCR backend hint: ${ocrHint}. See docs/guides/intake/scanned-pdfs.md.`
349
- : 'Set CONSTRUCT_PDF_OCR_HINT=tesseract|pdfocr to enable OCR. See docs/guides/intake/scanned-pdfs.md.';
350
- extracted.droppedInfo.push(makeDropInfo({
351
- kind: 'scanned-pdf',
352
- count: pages,
353
- reason: `Extracted text (${extracted.text.length} chars) is below minimum threshold for ${pages} page(s); document may be a scanned image. ${hintMsg}`,
354
- recoverable: true,
355
- }));
356
- }
357
-
358
- return extracted;
359
- }
360
-
361
182
  export function isExtractableDocumentPath(filePath) {
362
183
  return EXTRACTABLE_DOCUMENT_EXTS.has(extname(filePath).toLowerCase());
363
184
  }
@@ -710,10 +531,7 @@ export function extractDocumentText(filePath, { maxChars = null } = {}) {
710
531
  const envelope = extractCalendar(resolvedPath);
711
532
  extracted = { text: envelope.text, method: 'ics', droppedInfo: envelope.droppedInfo, structured: envelope.structured };
712
533
  } else if (ZIP_DOCUMENT_EXTS.has(extension)) {
713
- if (!legacyExtractAllowed()) {
714
- throw makeOfficeRequiresDoclingError(extension);
715
- }
716
- extracted = extractZipDocument(resolvedPath, extension);
534
+ throw makeOfficeRequiresDoclingError(extension);
717
535
  } else if (RICH_TEXT_EXTS.has(extension)) {
718
536
  extracted = extractRichText(resolvedPath, extension);
719
537
  } else if (MDLS_DOCUMENT_EXTS.has(extension) && process.platform === 'darwin' && commandExists('mdls')) {
@@ -721,10 +539,7 @@ export function extractDocumentText(filePath, { maxChars = null } = {}) {
721
539
  } else if (EMAIL_DOCUMENT_EXTS.has(extension)) {
722
540
  extracted = extractEml(resolvedPath);
723
541
  } else if (extension === '.pdf') {
724
- if (!legacyExtractAllowed()) {
725
- throw makePdfRequiresDoclingError();
726
- }
727
- extracted = extractPdf(resolvedPath);
542
+ throw makePdfRequiresDoclingError();
728
543
  } else {
729
544
  throw new Error(`Unsupported document type: ${extension || 'unknown'}`);
730
545
  }
@@ -798,12 +613,9 @@ export async function extractDocumentTextNodeNative(filePath, { maxChars = null
798
613
  if (text) {
799
614
  return buildExtractionResult(resolvedPath, extension, { text, method: nodeBackend.method, droppedInfo: [], structured: null }, maxChars);
800
615
  }
801
- } catch { /* optional dep absent or parse failure — legacy path only when explicitly allowed */ }
802
- if (!legacyExtractAllowed()) {
803
- if (extension === '.docx') throw makeOfficeRequiresDoclingError(extension);
804
- throw makePdfRequiresDoclingError();
805
- }
806
- return extractDocumentText(resolvedPath, { maxChars });
616
+ } catch { /* optional dep absent or parse failure */ }
617
+ if (extension === '.docx') throw makeOfficeRequiresDoclingError(extension);
618
+ throw makePdfRequiresDoclingError();
807
619
  }
808
620
 
809
621
  return extractDocumentText(resolvedPath, { maxChars });
@@ -3,9 +3,8 @@
3
3
  *
4
4
  * Generates PRDs, RFCs, and ADRs as structured markdown files under docs/.
5
5
  * Each artifact type has a fixed schema and front-matter. Numbering is auto-
6
- * incremented by scanning the target directory. Entry points cover the CLI,
7
- * the embed daemon when analysis triggers a recommendation, and the dashboard
8
- * API.
6
+ * incremented by scanning the target directory. Entry points cover the CLI
7
+ * and the embed daemon when analysis triggers a recommendation.
9
8
  */
10
9
 
11
10
  import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
package/lib/embed/cli.mjs CHANGED
@@ -10,7 +10,7 @@
10
10
  * Exports:
11
11
  * resolveEmbedStatus(env, homeDir) — returns { level, label, detail }
12
12
  * level: 'running' | 'stopped' | 'none'
13
- * Consumed by session-start hook and dashboard /api/embed/status endpoint.
13
+ * Consumed by session-start hook and `construct embed status`.
14
14
  *
15
15
  * autoStartEmbedIfNeeded(env, rootDir, homeDir) — spawns daemon when
16
16
  * provider credentials are present but daemon is stopped. No-op if already
@@ -38,7 +38,6 @@
38
38
  * - type: slack # post to a Slack channel
39
39
  * provider: slack
40
40
  * channel: C12345
41
- * - type: dashboard # push to the Construct dashboard
42
41
  *
43
42
  * approval:
44
43
  * require: # action types that need approval before dispatch
@@ -138,7 +137,7 @@ export const DEFAULT_OPERATING_PROFILE = {
138
137
  },
139
138
  riskModel: {
140
139
  detect: ['blocked work', 'missing owner', 'stale cadence', 'source errors', 'missing focal resource', 'unapproved high-risk action'],
141
- dashboardStatus: { ok: 'no known gaps', attention: 'gaps or source errors exist', blocked: 'required focal resource or approval path missing' },
140
+ riskStatus: { ok: 'no known gaps', attention: 'gaps or source errors exist', blocked: 'required focal resource or approval path missing' },
142
141
  },
143
142
  };
144
143
 
@@ -128,7 +128,7 @@ export function createCustomerProfile({ name, owner, workspace, status = 'active
128
128
  updatedAt: new Date().toISOString(),
129
129
  };
130
130
 
131
- const content = `---
131
+ const content = `---
132
132
  cx_doc_id: ${randomUUID()}
133
133
  created_at: ${profile.createdAt}
134
134
  updated_at: ${profile.updatedAt}
@@ -737,7 +737,7 @@ this.#scheduler.register(
737
737
  async () => {
738
738
  const result = await this.#inboxWatcher.poll();
739
739
  if (result.processed.length) {
740
- const { getRebrand } = await import('../profiles/rebrand.mjs');
740
+ const { getRebrand } = await import('../scopes/rebrand.mjs');
741
741
  const { signalNoun } = getRebrand(this.#rootDir);
742
742
  const noun = result.processed.length === 1 ? signalNoun : `${signalNoun}s`;
743
743
  process.stderr.write(`[embed] Classified ${result.processed.length} ${noun} from [${result.dirs.join(', ')}]\n`);
@@ -932,9 +932,8 @@ this.#scheduler.register(
932
932
  );
933
933
 
934
934
  // ── Job 13: telemetry-heartbeat ──────────────────────────────────────
935
- // Every 5 min — emit a daemon.heartbeat trace event so the
936
- // dashboard always has recent data, even when no agent/worker activity
937
- // has occurred. The heartbeat is fire-and-forget; failure is non-fatal.
935
+ // Every 5 min — emit a daemon.heartbeat trace event so telemetry backends
936
+ // always have recent data, even when no agent/worker activity has occurred.
938
937
  this.#scheduler.register(
939
938
  'telemetry-heartbeat',
940
939
  5 * 60_000,
@@ -962,8 +961,8 @@ this.#scheduler.register(
962
961
 
963
962
  // ── Job 14: telemetry-generation ─────────────────────────────────────────
964
963
  // Every 5 min — make a tiny LLM call and record it as a telemetry generation
965
- // observation so the dashboard's Latency/TTFT/Throughput/Error sections
966
- // always have recent data to display, even when no agent sessions are active.
964
+ // observation so external telemetry backends always have recent data to display,
965
+ // even when no agent sessions are active.
967
966
  this.#scheduler.register(
968
967
  'telemetry-generation',
969
968
  5 * 60_000,
@@ -149,11 +149,36 @@ export function matchSourceFromQuery(query, env = process.env, cwd = process.cwd
149
149
  * @param {object} [opts.env] - Env override
150
150
  * @returns {Promise<DemandFetchResult>}
151
151
  */
152
- export async function demandFetch({ query, rootDir, env, cwd } = {}) {
152
+ export async function demandFetch({ query, rootDir, env, cwd, teamId = null, targetIds = null, registry: injectedRegistry = null, providerRegistry = null } = {}) {
153
153
  const resolvedEnv = env ?? loadConstructEnv();
154
154
  const root = rootDir ?? (resolvedEnv.CX_DATA_DIR?.trim() || homedir());
155
155
  const projectRoot = cwd ?? root;
156
156
 
157
+ // Team-scoped fetch: resolve the team's declared sources and drive the fetch
158
+ // from them. A requested target outside the team is a typed OUT_OF_SCOPE error
159
+ // — never a silent wrong-source fetch. Resolution is by typed selector, so the
160
+ // team's effective sources are stable rather than substring-matched.
161
+ if (teamId) {
162
+ const { resolveTeamSources } = await import('../config/source-targets.mjs');
163
+ let registry = injectedRegistry;
164
+ if (!registry) {
165
+ const { loadRegistry } = await import('../registry/loader.mjs');
166
+ try { registry = loadRegistry({ rootDir: projectRoot, skipValidation: true }); } catch { registry = null; }
167
+ }
168
+ let targets = resolveTeamSources(teamId, { registry, config: {}, env: resolvedEnv });
169
+ if (Array.isArray(targetIds) && targetIds.length) {
170
+ const known = new Set(targets.map((t) => t.id));
171
+ const unknown = targetIds.filter((id) => !known.has(id));
172
+ if (unknown.length) {
173
+ return { ok: false, reason: 'OUT_OF_SCOPE', message: `target_ids not in team ${teamId}: ${unknown.join(', ')}`, items: [], teamId, targetIds };
174
+ }
175
+ targets = targets.filter((t) => targetIds.includes(t.id));
176
+ }
177
+ if (targets.length) {
178
+ return demandFetchTeam({ teamId, targets, rootDir: root, env: resolvedEnv, providerRegistry });
179
+ }
180
+ }
181
+
157
182
  const match = matchSourceFromQuery(query, resolvedEnv, projectRoot);
158
183
 
159
184
  // Self-referential queries about Construct itself → route to knowledge_search,
@@ -259,7 +284,7 @@ export async function demandFetch({ query, rootDir, env, cwd } = {}) {
259
284
  category: 'insight',
260
285
  summary: item.title ?? item.summary ?? item.id,
261
286
  content: buildObservationContent(item, match),
262
- tags: ['demand-fetch', match.provider, match.ref ?? match.provider],
287
+ tags: ['demand-fetch', match.provider, match.ref ?? match.provider, ...(teamId ? [`team:${teamId}`] : [])],
263
288
  confidence: 0.9,
264
289
  source: `demand-fetch:${match.provider}`,
265
290
  });
@@ -275,6 +300,7 @@ export async function demandFetch({ query, rootDir, env, cwd } = {}) {
275
300
  match,
276
301
  written,
277
302
  errors: errorItems.length ? errorItems : undefined,
303
+ ...(teamId ? { teamId } : {}),
278
304
  };
279
305
  }
280
306
 
@@ -451,6 +477,92 @@ function buildReadCalls(match, env, cwd = process.cwd()) {
451
477
  return [];
452
478
  }
453
479
 
480
+ // Read calls for one resolved team source, honoring the target's own filters
481
+ // (jira jql, github refs/limit, slack oldest) rather than the generic defaults.
482
+
483
+ function buildReadCallsForTarget(target) {
484
+ const sel = target.selector ?? {};
485
+ const f = target.filters ?? {};
486
+ const display = `${target.provider}/${sel.repo ?? sel.project ?? sel.team ?? sel.channel ?? target.id}`;
487
+ switch (target.provider) {
488
+ case 'github': {
489
+ const opts = { repos: [sel.repo], limit: Number.isInteger(f.limit) ? f.limit : 25 };
490
+ const refs = Array.isArray(f.refs) ? f.refs : ['meta', 'readme', 'docs', 'prs', 'issues', 'commits'];
491
+ return refs.map((ref) => ({ ref, opts, display }));
492
+ }
493
+ case 'jira': {
494
+ const jql = typeof f.jql === 'string'
495
+ ? f.jql
496
+ : `project = "${sel.project}" AND statusCategory != Done ORDER BY updated DESC`;
497
+ return [{ ref: 'issues', opts: { jql, limit: Number.isInteger(f.limit) ? f.limit : 50 }, display }];
498
+ }
499
+ case 'linear':
500
+ return [{ ref: 'issues', opts: { team: sel.team, limit: Number.isInteger(f.limit) ? f.limit : 50 }, display }];
501
+ case 'slack':
502
+ return [{ ref: 'messages', opts: { channels: [sel.channel], oldest: Number.isInteger(f.oldest) ? f.oldest : 86400, limit: Number.isInteger(f.limit) ? f.limit : 50 }, display }];
503
+ default:
504
+ return [];
505
+ }
506
+ }
507
+
508
+ // Drive the fetch from a team's resolved sources: one provider read per target,
509
+ // every observation tagged team:<id> + target:<id> so results stay retrievable by
510
+ // team scope. providerRegistry is injectable for hermetic tests.
511
+
512
+ async function demandFetchTeam({ teamId, targets, rootDir, env, providerRegistry = null }) {
513
+ let registry = providerRegistry;
514
+ if (!registry) {
515
+ const { ProviderRegistry } = await import('./providers/registry.mjs');
516
+ registry = await ProviderRegistry.fromEnv(env);
517
+ }
518
+
519
+ const allItems = [];
520
+ const errors = [];
521
+ let written = 0;
522
+
523
+ for (const target of targets) {
524
+ const provider = registry.get(target.provider);
525
+ if (!provider) {
526
+ errors.push({ provider: target.provider, targetId: target.id, message: `provider "${target.provider}" not registered` });
527
+ continue;
528
+ }
529
+ for (const { ref, opts, display } of buildReadCallsForTarget(target)) {
530
+ try {
531
+ const items = await provider.read(ref, opts);
532
+ for (const item of items ?? []) {
533
+ if (item.type === 'error') { errors.push(item); continue; }
534
+ allItems.push(item);
535
+ try {
536
+ addObservation(rootDir, {
537
+ role: 'construct',
538
+ category: 'insight',
539
+ summary: item.title ?? item.summary ?? item.id,
540
+ content: buildObservationContent(item, { provider: target.provider, ref: target.id, display }),
541
+ tags: ['demand-fetch', 'team', target.provider, `team:${teamId}`, `target:${target.id}`],
542
+ confidence: 0.88,
543
+ source: `demand-fetch:team:${teamId}:${target.id}`,
544
+ });
545
+ written++;
546
+ } catch (err) { process.stderr.write('[demand-fetch.mjs] team-store-obs: ' + (err?.message ?? String(err)) + '\n'); }
547
+ }
548
+ } catch (err) {
549
+ errors.push({ provider: target.provider, targetId: target.id, message: err.message });
550
+ }
551
+ }
552
+ }
553
+
554
+ return {
555
+ ok: errors.length === 0 || allItems.length > 0,
556
+ reason: allItems.length ? 'team_fetched' : 'team_empty',
557
+ message: `Fetched ${allItems.length} item(s) from team ${teamId} (${targets.length} source(s)), stored ${written} observation(s)`,
558
+ items: allItems,
559
+ teamId,
560
+ targetIds: targets.map((t) => t.id),
561
+ written,
562
+ errors: errors.length ? errors : undefined,
563
+ };
564
+ }
565
+
454
566
  function buildObservationContent(item, match) {
455
567
  const lines = [`Source: ${match.display}`, `Provider: ${match.provider}`];
456
568
  if (item.url) lines.push(`URL: ${item.url}`);
@@ -227,9 +227,11 @@ export async function runDocsLifecycle(opts) {
227
227
  const actions = [];
228
228
  const recommendations = [];
229
229
  const conflicts = [];
230
-
231
- // Auto-suppress stale recommendations before generating new ones
232
- const suppressed = autoSuppressStale();
230
+ const localTargets = targets.filter((target) => target.access !== 'remote' && target.path);
231
+ let suppressed = 0;
232
+ for (const target of localTargets) {
233
+ suppressed += autoSuppressStale({ project: target.path });
234
+ }
233
235
 
234
236
  for (const target of targets) {
235
237
  const gaps = detectDocGaps(target, { snapshot, roleLens });
@@ -266,6 +268,7 @@ export async function runDocsLifecycle(opts) {
266
268
  recencyBonus: 2,
267
269
  sourceSignalIds: payload.intakeIds || [],
268
270
  lane: gap.lane,
271
+ project: target.path,
269
272
  });
270
273
  recommendations.push(recResult);
271
274
  allGaps.push({ ...gap, target: targetLabel(target), recommendation: recResult });
@@ -276,13 +279,14 @@ export async function runDocsLifecycle(opts) {
276
279
  const artifactRecs = recommendArtifacts(snapshot, { rootDir: target.path });
277
280
  for (const rec of artifactRecs) {
278
281
  // Use recommendation store for dedup
279
- const { active, existing } = isRecommendationActive(rec.type, rec.title);
282
+ const { active, existing } = isRecommendationActive(rec.type, rec.title, { project: target.path });
280
283
  if (!active) {
281
284
  const recResult = createRecommendation({
282
285
  type: rec.type,
283
286
  title: rec.title,
284
287
  reason: rec.reason,
285
288
  signalCount: 1,
289
+ project: target.path,
286
290
  });
287
291
  recommendations.push(recResult);
288
292
  allGaps.push({
@@ -340,7 +344,7 @@ export async function runDocsLifecycle(opts) {
340
344
  }
341
345
  }
342
346
 
343
- const stats = recommendationStats();
347
+ const stats = recommendationStats({ project: localTargets[0]?.path });
344
348
 
345
349
  return {
346
350
  gaps: allGaps,
@@ -2,8 +2,8 @@
2
2
  * lib/embed/intake-metrics.mjs — Intake pipeline observability.
3
3
  *
4
4
  * Aggregates volume, velocity, processing time, and acceptance rate from
5
- * the filesystem intake queue. Designed for the dashboard /api/insights
6
- * endpoint and the `construct intake metrics` CLI.
5
+ * the filesystem intake queue. Consumed by `construct intake metrics` and
6
+ * MCP insight tools.
7
7
  *
8
8
  * Data sources:
9
9
  * .cx/intake/pending/ — JSON files waiting for triage
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * lib/embed/notifications.mjs — embed action notification bus.
3
3
  *
4
- * Decoupled event emitter for embed daemon dashboard SSE.
5
- * The dashboard server subscribes to this bus; the daemon emits events.
6
- * Slack stub is wired but no-ops without config.
4
+ * In-process event emitter for embed daemon lifecycle and operator toasts.
5
+ * Slack webhook delivery is optional via notifySlack.
7
6
  */
8
7
 
9
8
  import { EventEmitter } from 'node:events';