@geraldmaron/construct 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -23,15 +23,14 @@ export const DEPLOYMENT_MODES = ['solo', 'team', 'enterprise'];
23
23
  export const MCP_BROKER_VALUES = ['auto', 'on', 'off'];
24
24
  export const DEFAULT_SCOPE_ID = 'rnd';
25
25
 
26
- export const SURFACES = ['claude', 'opencode', 'codex', 'copilot', 'vscode', 'cursor'];
27
-
28
26
  export const INGEST_STRATEGIES = ['adapter', 'provider', 'docling-remote'];
29
27
  export const INGEST_FALLBACKS = ['none', 'provider', 'adapter'];
30
28
  export const INGEST_ORCHESTRATIONS = ['prompt-only', 'orchestrated'];
31
29
 
32
- export const ORCHESTRATION_WORKER_BACKENDS = ['inline', 'provider'];
30
+ export const ORCHESTRATION_WORKER_BACKENDS = ['inline', 'provider', 'host'];
33
31
  export const ORCHESTRATION_STORES = ['filesystem', 'sqlite', 'postgres'];
34
32
  export const CHAIN_OF_THOUGHT_MODES = ['hidden', 'surface', 'telemetry_only'];
33
+ export const ORCHESTRATION_HOST_EXECUTION_MODES = ['auto', 'pickup', 'sampling'];
35
34
 
36
35
  // SessionStart context routing. `auto` keeps the rich payload on stdout for
37
36
  // interactive sessions and suppresses it (to a debug log) for non-interactive /
@@ -70,6 +69,7 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
70
69
  workerBackend: 'inline',
71
70
  store: 'filesystem',
72
71
  chainOfThought: 'hidden',
72
+ hostExecution: 'auto',
73
73
  }),
74
74
  telemetry: Object.freeze({
75
75
  enabled: true,
@@ -89,12 +89,6 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
89
89
  maxLiveFree: 24,
90
90
  }),
91
91
  }),
92
- roleSelection: Object.freeze({
93
- primary: null,
94
- secondary: null,
95
- perConversationOverride: true,
96
- }),
97
- hosts: Object.freeze(Object.fromEntries(SURFACES.map((s) => [s, Object.freeze({ enabled: true })]))),
98
92
  resources: Object.freeze({
99
93
  disk: Object.freeze({
100
94
  tracesMaxDays: 30,
@@ -109,6 +103,8 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
109
103
  handoffsMaxDays: 30,
110
104
  handoffsMaxItems: 50,
111
105
  totalCxMaxMb: 2000,
106
+ observationsMaxDays: 90,
107
+ observationsMaxRows: 5000,
112
108
  }),
113
109
  process: Object.freeze({
114
110
  embedDaemonMaxRssMb: 800,
@@ -156,7 +152,7 @@ export const FIELD_RULES = {
156
152
  types: { type: 'object', required: false },
157
153
  },
158
154
  },
159
- scope: { type: 'string', required: false, maxLength: 40 },
155
+ scope: { type: 'string', required: false, maxLength: 40, enum: ['rnd', 'operations', 'creative', 'research'] },
160
156
  autoEmbed: { type: 'boolean', required: false },
161
157
  ingest: {
162
158
  type: 'object',
@@ -174,6 +170,7 @@ export const FIELD_RULES = {
174
170
  workerBackend: { type: 'string', enum: ORCHESTRATION_WORKER_BACKENDS },
175
171
  store: { type: 'string', enum: ORCHESTRATION_STORES },
176
172
  chainOfThought: { type: 'string', enum: CHAIN_OF_THOUGHT_MODES },
173
+ hostExecution: { type: 'string', enum: ORCHESTRATION_HOST_EXECUTION_MODES },
177
174
  },
178
175
  },
179
176
  telemetry: {
@@ -190,27 +187,7 @@ export const FIELD_RULES = {
190
187
  outputMode: { type: 'string', enum: HOOK_OUTPUT_MODES },
191
188
  },
192
189
  },
193
- roleSelection: {
194
- type: 'object',
195
- required: false,
196
- fields: {
197
- primary: { type: ['string', 'null'], maxLength: 50 },
198
- secondary: { type: ['string', 'null'], maxLength: 50 },
199
- perConversationOverride: { type: 'boolean' },
200
- },
201
- },
202
190
  resources: { type: 'object', required: false },
203
- hosts: {
204
- type: 'object',
205
- required: false,
206
- fields: Object.fromEntries(SURFACES.map((s) => [s, {
207
- type: 'object',
208
- required: false,
209
- fields: {
210
- enabled: { type: 'boolean', required: false },
211
- },
212
- }])),
213
- },
214
191
  costs: {
215
192
  type: 'object',
216
193
  required: false,
@@ -0,0 +1,70 @@
1
+ /**
2
+ * lib/config/source-target-registry.mjs — manifest-derived source-target descriptors.
3
+ *
4
+ * Loads the merged (builtin/user/project) extension manifests and collects each
5
+ * manifest's optional `sourceTarget` block into a descriptor keyed by the block's
6
+ * own `provider` field — the string used in construct.config.json's
7
+ * `sources.targets[].provider`, in legacy env-derived targets, and in embed
8
+ * source records. That key is distinct from the manifest `id` for providers
9
+ * whose manifest id differs from their config-facing provider name (the Jira
10
+ * manifest id is `atlassian-jira`; its sourceTarget.provider is `jira`).
11
+ *
12
+ * lib/config/source-targets.mjs and lib/embed/demand-fetch.mjs iterate these
13
+ * descriptors instead of naming providers directly, so adding a fifth
14
+ * source-target-eligible provider requires only a manifest with a
15
+ * `sourceTarget` block — no edits to either file. See each descriptor field
16
+ * for its consumer:
17
+ * selector / secondaryField — validateSourceTarget, normalizeConfigTarget
18
+ * signature — targetSignature
19
+ * legacyEnv — legacyEnvSourceTargets
20
+ * aliases — resolveKnownSourcesFromTargets
21
+ * embed — targetsToEmbedSources
22
+ * embedFilters — targetsToEmbedSourcesWithFilters
23
+ * demandFetch.target* — lib/embed/demand-fetch.mjs buildReadCallsForTarget
24
+ * demandFetch.query* — lib/embed/demand-fetch.mjs buildReadCalls
25
+ *
26
+ * Computed once at module load (mirrors the BUILT_INS pattern in
27
+ * lib/providers/registry.mjs) using the default rootDir (process.cwd()).
28
+ * Callers that need a project-tier-aware view for a different rootDir should
29
+ * call loadSourceTargetDescriptors(rootDir) directly.
30
+ */
31
+
32
+ import { loadManifestsFromDir, mergeManifests, resolveManifestDirs } from '../extensions/loader.mjs';
33
+
34
+ export function loadSourceTargetDescriptors(rootDir = process.cwd()) {
35
+ const { builtin, user, project } = resolveManifestDirs({ rootDir });
36
+ const { manifests: builtinManifests } = loadManifestsFromDir(builtin);
37
+ const { manifests: userManifests } = loadManifestsFromDir(user);
38
+ const { manifests: projectManifests } = loadManifestsFromDir(project);
39
+ const merged = mergeManifests(builtinManifests, userManifests, projectManifests);
40
+
41
+ const byProvider = new Map();
42
+ for (const manifest of merged) {
43
+ const descriptor = manifest.sourceTarget;
44
+ if (!descriptor || typeof descriptor !== 'object' || !descriptor.provider) continue;
45
+ byProvider.set(descriptor.provider, descriptor);
46
+ }
47
+ return byProvider;
48
+ }
49
+
50
+ const DESCRIPTORS = loadSourceTargetDescriptors();
51
+
52
+ export const SOURCE_TARGET_PROVIDERS = Object.freeze([...DESCRIPTORS.keys()]);
53
+
54
+ export function getSourceTargetDescriptor(provider) {
55
+ return DESCRIPTORS.get(provider) ?? null;
56
+ }
57
+
58
+ export function listSourceTargetDescriptors() {
59
+ return [...DESCRIPTORS.values()];
60
+ }
61
+
62
+ /**
63
+ * Fill a descriptor string template's `{name}` placeholders from `vars`.
64
+ * Shared by lib/config/source-targets.mjs (signatures) and
65
+ * lib/embed/demand-fetch.mjs (JQL construction) so both consume the same
66
+ * placeholder syntax the manifests declare.
67
+ */
68
+ export function renderTemplate(template, vars) {
69
+ return template.replace(/\{(\w+)\}/g, (_, key) => (key in vars ? String(vars[key]) : ''));
70
+ }
@@ -1,18 +1,36 @@
1
1
  /**
2
2
  * lib/config/source-targets.mjs — typed integration source targets in construct.config.json.
3
3
  *
4
- * Validates GitHub, Jira, Linear, and Slack selector records, merges legacy env
5
- * target lists additively, deduplicates by provider+selector signature, and
6
- * converts resolved targets into embed provider source records for auto-discovery.
4
+ * Validates provider selector records, merges legacy env target lists
5
+ * additively, deduplicates by provider+selector signature, and converts
6
+ * resolved targets into embed provider source records for auto-discovery.
7
+ *
8
+ * Every function here dispatches on the manifest-derived descriptors from
9
+ * lib/config/source-target-registry.mjs rather than naming providers
10
+ * directly — adding a fifth source-target-eligible provider means adding a
11
+ * `sourceTarget` block to that provider's manifest, not editing this file.
12
+ * See source-target-registry.mjs's header for what each descriptor field
13
+ * drives.
7
14
  */
8
15
 
9
- export const SOURCE_PROVIDERS = Object.freeze(['github', 'jira', 'linear', 'slack']);
16
+ import {
17
+ SOURCE_TARGET_PROVIDERS,
18
+ getSourceTargetDescriptor,
19
+ listSourceTargetDescriptors,
20
+ renderTemplate,
21
+ } from './source-target-registry.mjs';
22
+
23
+ export const SOURCE_PROVIDERS = SOURCE_TARGET_PROVIDERS;
10
24
 
11
- export const SLACK_INTENTS = Object.freeze(['internal', 'risk', 'decision', 'external', 'how-to']);
25
+ const slackDescriptor = getSourceTargetDescriptor('slack');
26
+ export const SLACK_INTENTS = Object.freeze(slackDescriptor?.secondaryField?.enum ?? []);
12
27
 
13
28
  const ID_RE = /^[a-z][a-z0-9_-]{0,63}$/i;
14
- const GITHUB_REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
15
- const JIRA_PROJECT_RE = /^[A-Z][A-Z0-9]{1,9}$/;
29
+
30
+ function applyPatternTransform(value, mode) {
31
+ if (mode === 'trimUpper') return value.trim().toUpperCase();
32
+ return value.trim();
33
+ }
16
34
 
17
35
  export function validateSourceTarget(target, index = 0) {
18
36
  const errors = [];
@@ -35,34 +53,26 @@ export function validateSourceTarget(target, index = 0) {
35
53
  return errors;
36
54
  }
37
55
 
56
+ const descriptor = getSourceTargetDescriptor(target.provider);
57
+ if (!descriptor) return errors;
58
+
38
59
  const sel = target.selector;
39
- switch (target.provider) {
40
- case 'github':
41
- if (typeof sel.repo !== 'string' || !GITHUB_REPO_RE.test(sel.repo.trim())) {
42
- errors.push(`${at}.selector.repo: required owner/repo slug`);
43
- }
44
- break;
45
- case 'jira':
46
- if (typeof sel.project !== 'string' || !JIRA_PROJECT_RE.test(sel.project.trim().toUpperCase())) {
47
- errors.push(`${at}.selector.project: required Jira project key (2–10 uppercase letters/digits)`);
48
- }
49
- break;
50
- case 'linear':
51
- if (typeof sel.team !== 'string' || !sel.team.trim()) {
52
- errors.push(`${at}.selector.team: required Linear team key or name`);
53
- }
54
- break;
55
- case 'slack': {
56
- if (typeof sel.channel !== 'string' || !sel.channel.trim()) {
57
- errors.push(`${at}.selector.channel: required Slack channel name or ID`);
58
- }
59
- if (sel.intent !== undefined && !SLACK_INTENTS.includes(sel.intent)) {
60
- errors.push(`${at}.selector.intent: must be one of ${SLACK_INTENTS.join(', ')}`);
61
- }
62
- break;
60
+ const { field, pattern, patternTransform, hint } = descriptor.selector;
61
+ const raw = sel[field];
62
+ const valid = typeof raw === 'string' && (
63
+ pattern
64
+ ? new RegExp(pattern).test(applyPatternTransform(raw, patternTransform))
65
+ : !!raw.trim()
66
+ );
67
+ if (!valid) {
68
+ errors.push(`${at}.selector.${field}: ${hint}`);
69
+ }
70
+
71
+ if (descriptor.secondaryField) {
72
+ const { field: secField, enum: secEnum, hint: secHint } = descriptor.secondaryField;
73
+ if (sel[secField] !== undefined && !secEnum.includes(sel[secField])) {
74
+ errors.push(`${at}.selector.${secField}: ${secHint} ${secEnum.join(', ')}`);
63
75
  }
64
- default:
65
- break;
66
76
  }
67
77
 
68
78
  return errors;
@@ -101,73 +111,71 @@ function slugId(value) {
101
111
  return String(value).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
102
112
  }
103
113
 
104
- function parseSlackChannelEntry(raw) {
114
+ function parseSlackChannelEntry(raw, descriptor) {
105
115
  const trimmed = String(raw).trim().replace(/^#/, '');
106
116
  const colon = trimmed.indexOf(':');
117
+ const fallback = descriptor.secondaryField.default;
107
118
  if (colon === -1) {
108
- return { channel: trimmed, intent: 'internal' };
119
+ return { channel: trimmed, intent: fallback };
109
120
  }
110
- return { channel: trimmed.slice(0, colon), intent: trimmed.slice(colon + 1) || 'internal' };
121
+ const intent = trimmed.slice(colon + 1) || fallback;
122
+ return { channel: trimmed.slice(0, colon), intent };
123
+ }
124
+
125
+ function legacyEnvRawValue(vars, env) {
126
+ return vars.reduce((acc, name) => (acc !== undefined ? acc : env[name]), undefined);
111
127
  }
112
128
 
113
129
  export function legacyEnvSourceTargets(env = process.env) {
114
130
  const targets = [];
115
131
 
116
- for (const repo of parseCsv(env.GITHUB_REPOS ?? env.GITHUB_REPO)) {
117
- targets.push({
118
- id: `env-github-${slugId(repo)}`,
119
- provider: 'github',
120
- selector: { repo },
121
- provenance: 'env:GITHUB_REPOS',
122
- });
123
- }
124
-
125
- for (const project of parseCsv(env.JIRA_PROJECTS)) {
126
- const key = project.toUpperCase();
127
- targets.push({
128
- id: `env-jira-${slugId(key)}`,
129
- provider: 'jira',
130
- selector: { project: key },
131
- provenance: 'env:JIRA_PROJECTS',
132
- });
133
- }
134
-
135
- for (const team of parseCsv(env.LINEAR_TEAMS)) {
136
- targets.push({
137
- id: `env-linear-${slugId(team)}`,
138
- provider: 'linear',
139
- selector: { team },
140
- provenance: 'env:LINEAR_TEAMS',
141
- });
142
- }
132
+ for (const descriptor of listSourceTargetDescriptors()) {
133
+ const { provider, legacyEnv, selector } = descriptor;
134
+ if (!legacyEnv) continue;
135
+ const raw = legacyEnvRawValue(legacyEnv.vars, env);
136
+ const provenance = `env:${legacyEnv.vars[0]}`;
137
+
138
+ if (legacyEnv.parse === 'slackChannelColon') {
139
+ for (const entry of parseCsv(raw)) {
140
+ const { channel, intent } = parseSlackChannelEntry(entry, descriptor);
141
+ targets.push({
142
+ id: `env-${provider}-${slugId(channel)}`,
143
+ provider,
144
+ selector: { [selector.field]: channel, [descriptor.secondaryField.field]: intent },
145
+ provenance,
146
+ });
147
+ }
148
+ continue;
149
+ }
143
150
 
144
- for (const entry of parseCsv(env.SLACK_CHANNELS ?? env.SLACK_CHANNEL)) {
145
- const { channel, intent } = parseSlackChannelEntry(entry);
146
- targets.push({
147
- id: `env-slack-${slugId(channel)}`,
148
- provider: 'slack',
149
- selector: { channel, intent: SLACK_INTENTS.includes(intent) ? intent : 'internal' },
150
- provenance: 'env:SLACK_CHANNELS',
151
- });
151
+ for (const entry of parseCsv(raw)) {
152
+ const value = legacyEnv.valueTransform === 'upper' ? entry.toUpperCase() : entry;
153
+ targets.push({
154
+ id: `env-${provider}-${slugId(value)}`,
155
+ provider,
156
+ selector: { [selector.field]: value },
157
+ provenance,
158
+ });
159
+ }
152
160
  }
153
161
 
154
162
  return targets;
155
163
  }
156
164
 
165
+ function signatureValue(raw, mode) {
166
+ if (mode === 'optionalLower') return raw?.trim().toLowerCase();
167
+ if (mode === 'upperOrEmpty') return String(raw ?? '').trim().toUpperCase();
168
+ return String(raw ?? '').trim().toLowerCase();
169
+ }
170
+
157
171
  export function targetSignature(target) {
158
172
  const sel = target.selector ?? {};
159
- switch (target.provider) {
160
- case 'github':
161
- return `github:repo:${sel.repo?.trim().toLowerCase()}`;
162
- case 'jira':
163
- return `jira:project:${String(sel.project ?? '').trim().toUpperCase()}`;
164
- case 'linear':
165
- return `linear:team:${String(sel.team ?? '').trim().toLowerCase()}`;
166
- case 'slack':
167
- return `slack:channel:${String(sel.channel ?? '').trim().toLowerCase()}:intent:${sel.intent ?? 'internal'}`;
168
- default:
169
- return `${target.provider}:${JSON.stringify(sel)}`;
170
- }
173
+ const descriptor = getSourceTargetDescriptor(target.provider);
174
+ if (!descriptor) return `${target.provider}:${JSON.stringify(sel)}`;
175
+
176
+ const value = signatureValue(sel[descriptor.selector.field], descriptor.signature.valueMode);
177
+ const intent = descriptor.secondaryField ? (sel[descriptor.secondaryField.field] ?? descriptor.secondaryField.default) : undefined;
178
+ return renderTemplate(descriptor.signature.template, { value, intent });
171
179
  }
172
180
 
173
181
  export function mergeSourceTargets(configTargets = [], envTargets = []) {
@@ -191,19 +199,21 @@ export function normalizeConfigTarget(raw) {
191
199
  provenance: raw.provenance ?? 'config',
192
200
  };
193
201
 
194
- if (out.provider === 'jira' && out.selector.project) {
195
- out.selector.project = String(out.selector.project).trim().toUpperCase();
196
- }
197
- if (out.provider === 'github' && out.selector.repo) {
198
- out.selector.repo = String(out.selector.repo).trim();
199
- }
200
- if (out.provider === 'linear' && out.selector.team) {
201
- out.selector.team = String(out.selector.team).trim();
202
+ const descriptor = getSourceTargetDescriptor(out.provider);
203
+ if (!descriptor) return out;
204
+
205
+ const { field, normalize } = descriptor.selector;
206
+ if (out.selector[field]) {
207
+ const value = String(out.selector[field]);
208
+ out.selector[field] = normalize === 'trimHash'
209
+ ? value.trim().replace(/^#/, '')
210
+ : applyPatternTransform(value, normalize === 'trimUpper' ? 'trimUpper' : 'trim');
202
211
  }
203
- if (out.provider === 'slack' && out.selector.channel) {
204
- out.selector.channel = String(out.selector.channel).trim().replace(/^#/, '');
205
- if (!out.selector.intent) out.selector.intent = 'internal';
212
+
213
+ if (descriptor.secondaryField && !out.selector[descriptor.secondaryField.field]) {
214
+ out.selector[descriptor.secondaryField.field] = descriptor.secondaryField.default;
206
215
  }
216
+
207
217
  return out;
208
218
  }
209
219
 
@@ -215,55 +225,41 @@ export function resolveEffectiveSourceTargetsFromConfig(config, env = process.en
215
225
 
216
226
  export function targetsToEmbedSources(targets) {
217
227
  const sources = [];
218
- const githubRepos = targets.filter((t) => t.provider === 'github').map((t) => t.selector.repo);
219
- if (githubRepos.length) {
220
- sources.push({
221
- provider: 'github',
222
- repos: githubRepos,
223
- refs: ['prs', 'issues', 'commits'],
224
- limit: 25,
225
- });
226
- sources.push({
227
- provider: 'github',
228
- repos: githubRepos,
229
- refs: ['meta', 'readme', 'docs'],
230
- limit: 25,
231
- });
232
- }
233
-
234
- for (const target of targets.filter((t) => t.provider === 'jira')) {
235
- sources.push({
236
- provider: 'jira',
237
- project: target.selector.project,
238
- refs: ['issues'],
239
- limit: 50,
240
- });
241
- }
242
228
 
243
- for (const target of targets.filter((t) => t.provider === 'linear')) {
244
- sources.push({
245
- provider: 'linear',
246
- team: target.selector.team,
247
- refs: ['issues'],
248
- limit: 50,
249
- });
250
- }
251
-
252
- const slackByIntent = new Map();
253
- for (const target of targets.filter((t) => t.provider === 'slack')) {
254
- const intent = target.selector.intent ?? 'internal';
255
- if (!slackByIntent.has(intent)) slackByIntent.set(intent, []);
256
- slackByIntent.get(intent).push(target.selector.channel);
257
- }
258
- for (const [intent, channels] of slackByIntent) {
259
- sources.push({
260
- provider: 'slack',
261
- channels,
262
- intent,
263
- refs: ['messages'],
264
- oldest: 86400,
265
- limit: 50,
266
- });
229
+ for (const descriptor of listSourceTargetDescriptors()) {
230
+ const { provider, embed } = descriptor;
231
+ if (!embed) continue;
232
+ const selField = descriptor.selector.field;
233
+ const providerTargets = targets.filter((t) => t.provider === provider);
234
+ if (!providerTargets.length) continue;
235
+
236
+ if (embed.mode === 'aggregate') {
237
+ const values = providerTargets.map((t) => t.selector[selField]);
238
+ for (const group of embed.groups) {
239
+ sources.push({ provider, [embed.valueField]: values, refs: group.refs, limit: group.limit });
240
+ }
241
+ } else if (embed.mode === 'perTarget') {
242
+ for (const target of providerTargets) {
243
+ sources.push({ provider, [embed.valueField]: target.selector[selField], refs: embed.refs, limit: embed.limit });
244
+ }
245
+ } else if (embed.mode === 'groupBy') {
246
+ const byGroup = new Map();
247
+ for (const target of providerTargets) {
248
+ const key = target.selector[embed.groupField] ?? embed.groupDefault;
249
+ if (!byGroup.has(key)) byGroup.set(key, []);
250
+ byGroup.get(key).push(target.selector[selField]);
251
+ }
252
+ for (const [key, values] of byGroup) {
253
+ sources.push({
254
+ provider,
255
+ [embed.valueField]: values,
256
+ [embed.groupField]: key,
257
+ refs: embed.refs,
258
+ oldest: embed.oldest,
259
+ limit: embed.limit,
260
+ });
261
+ }
262
+ }
267
263
  }
268
264
 
269
265
  return sources;
@@ -273,38 +269,33 @@ export function resolveKnownSourcesFromTargets(targets) {
273
269
  const sources = [];
274
270
 
275
271
  for (const target of targets) {
272
+ const descriptor = getSourceTargetDescriptor(target.provider);
273
+ if (!descriptor) continue;
276
274
  const sel = target.selector ?? {};
277
- if (target.provider === 'github' && sel.repo) {
278
- const repo = sel.repo;
279
- const short = repo.split('/').pop();
280
- sources.push({ id: repo.toLowerCase(), provider: 'github', ref: repo, display: repo, targetId: target.id });
281
- sources.push({ id: short.toLowerCase(), provider: 'github', ref: repo, display: repo, targetId: target.id });
282
- sources.push({ id: short.replace(/-/g, '').toLowerCase(), provider: 'github', ref: repo, display: repo, targetId: target.id });
275
+ const value = sel[descriptor.selector.field];
276
+ if (!value) continue;
277
+
278
+ const display = descriptor.aliases.displayPrefix ? `${descriptor.aliases.displayPrefix}/${value}` : value;
279
+
280
+ if (descriptor.aliases.strategy === 'repoWords') {
281
+ const short = value.split('/').pop();
282
+ sources.push({ id: value.toLowerCase(), provider: target.provider, ref: value, display, targetId: target.id });
283
+ sources.push({ id: short.toLowerCase(), provider: target.provider, ref: value, display, targetId: target.id });
284
+ sources.push({ id: short.replace(/-/g, '').toLowerCase(), provider: target.provider, ref: value, display, targetId: target.id });
283
285
  const words = short.split('-');
284
286
  if (words.length > 1) {
285
- sources.push({ id: words[words.length - 1].toLowerCase(), provider: 'github', ref: repo, display: repo, targetId: target.id });
287
+ sources.push({ id: words[words.length - 1].toLowerCase(), provider: target.provider, ref: value, display, targetId: target.id });
286
288
  }
287
- }
288
- if (target.provider === 'jira' && sel.project) {
289
- const key = sel.project;
290
- sources.push({ id: key.toLowerCase(), provider: 'jira', ref: key, display: `Jira/${key}`, targetId: target.id });
291
- }
292
- if (target.provider === 'linear' && sel.team) {
293
- const team = sel.team;
294
- sources.push({ id: team.toLowerCase(), provider: 'linear', ref: team, display: `Linear/${team}`, targetId: target.id });
295
- }
296
- if (target.provider === 'slack' && sel.channel) {
297
- const ch = sel.channel;
298
- sources.push({ id: ch.toLowerCase(), provider: 'slack', ref: ch, display: `Slack/${ch}`, targetId: target.id });
289
+ } else {
290
+ sources.push({ id: value.toLowerCase(), provider: target.provider, ref: value, display, targetId: target.id });
299
291
  }
300
292
  }
301
293
 
302
294
  const providers = new Set(targets.map((t) => t.provider));
303
- if (providers.has('jira')) {
304
- sources.push({ id: 'jira', provider: 'jira', ref: null, display: 'Jira' });
305
- }
306
- if (providers.has('linear')) {
307
- sources.push({ id: 'linear', provider: 'linear', ref: null, display: 'Linear' });
295
+ for (const descriptor of listSourceTargetDescriptors()) {
296
+ if (descriptor.aliases.catchAll && providers.has(descriptor.provider)) {
297
+ sources.push({ id: descriptor.provider, provider: descriptor.provider, ref: null, display: descriptor.aliases.displayPrefix });
298
+ }
308
299
  }
309
300
 
310
301
  return sources;
@@ -343,48 +334,31 @@ export function targetsToEmbedSourcesWithFilters(targets, { teamId = null } = {}
343
334
  });
344
335
 
345
336
  for (const target of targets) {
337
+ const descriptor = getSourceTargetDescriptor(target.provider);
338
+ if (!descriptor?.embedFilters) continue;
339
+ const ef = descriptor.embedFilters;
346
340
  const f = target.filters ?? {};
347
341
  const sel = target.selector ?? {};
348
- switch (target.provider) {
349
- case 'github':
350
- sources.push(tag({
351
- provider: 'github',
352
- repos: [sel.repo],
353
- refs: Array.isArray(f.refs) ? f.refs : ['prs', 'issues', 'commits'],
354
- limit: Number.isInteger(f.limit) ? f.limit : 25,
355
- }, target));
356
- break;
357
- case 'jira':
358
- sources.push(tag({
359
- provider: 'jira',
360
- project: sel.project,
361
- refs: ['issues'],
362
- ...(typeof f.jql === 'string' ? { jql: f.jql } : {}),
363
- limit: Number.isInteger(f.limit) ? f.limit : 50,
364
- }, target));
365
- break;
366
- case 'linear':
367
- sources.push(tag({
368
- provider: 'linear',
369
- team: sel.team,
370
- refs: ['issues'],
371
- ...(Array.isArray(f.states) ? { states: f.states } : {}),
372
- limit: Number.isInteger(f.limit) ? f.limit : 50,
373
- }, target));
374
- break;
375
- case 'slack':
376
- sources.push(tag({
377
- provider: 'slack',
378
- channels: [sel.channel],
379
- intent: sel.intent ?? 'internal',
380
- refs: ['messages'],
381
- oldest: Number.isInteger(f.oldest) ? f.oldest : 86400,
382
- limit: Number.isInteger(f.limit) ? f.limit : 50,
383
- }, target));
384
- break;
385
- default:
386
- break;
342
+ const selField = descriptor.selector.field;
343
+
344
+ const rec = {
345
+ provider: target.provider,
346
+ [ef.valueField]: ef.valueKind === 'array' ? [sel[selField]] : sel[selField],
347
+ refs: ef.refsFilterKey && Array.isArray(f[ef.refsFilterKey]) ? f[ef.refsFilterKey] : ef.defaultRefs,
348
+ limit: Number.isInteger(f.limit) ? f.limit : ef.defaultLimit,
349
+ };
350
+
351
+ if (ef.includeSecondaryField && descriptor.secondaryField) {
352
+ rec[descriptor.secondaryField.field] = sel[descriptor.secondaryField.field] ?? descriptor.secondaryField.default;
387
353
  }
354
+
355
+ for (const key of ef.extraFilterKeys ?? []) {
356
+ if (key === 'jql' && typeof f.jql === 'string') rec.jql = f.jql;
357
+ if (key === 'states' && Array.isArray(f.states)) rec.states = f.states;
358
+ if (key === 'oldest') rec.oldest = Number.isInteger(f.oldest) ? f.oldest : descriptor.embed?.oldest;
359
+ }
360
+
361
+ sources.push(tag(rec, target));
388
362
  }
389
363
 
390
364
  return sources;