@geraldmaron/construct 1.4.2 → 1.5.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 (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +57 -45
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +273 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +104 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -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;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * lib/contracts/coverage.mjs — executable-postcondition coverage for
3
+ * specialists/org/contracts/*.json (construct-rf26.12).
4
+ *
5
+ * A contract's postconditions are pure prose unless something mechanical
6
+ * evaluates them. This module measures the split: how many postconditions,
7
+ * across the full contract set, are tagged `postconditionType: 'executable'`
8
+ * (backed by a real check — see lib/contracts/validate.mjs's file header for
9
+ * the four enforcement mechanisms) versus `'advisory'` (documented, not
10
+ * mechanically checked). A legacy bare-string postcondition counts as
11
+ * neither classified kind and is reported separately — it should not occur
12
+ * in the shipped contract set after the rf26.12 migration, so a nonzero
13
+ * count here is itself a drift signal.
14
+ *
15
+ * Pure and deterministic: same registry -> same report. Consumed by
16
+ * `construct doctor consistency` (lib/doctor/watchers/consistency.mjs) and
17
+ * by tests/contracts-coverage.test.mjs as the durable coverage assertion.
18
+ */
19
+
20
+ import { loadRegistry } from '../registry/loader.mjs';
21
+
22
+ /**
23
+ * Compute postcondition coverage across every contract in the unified
24
+ * registry. Returns per-contract counts plus an aggregate summary.
25
+ */
26
+ export function computePostconditionCoverage({ repoRoot } = {}) {
27
+ const registry = loadRegistry(repoRoot ? { rootDir: repoRoot } : undefined);
28
+ const contracts = Object.values(registry.contracts || {});
29
+
30
+ const perContract = [];
31
+ let executable = 0;
32
+ let advisory = 0;
33
+ let unclassified = 0;
34
+
35
+ for (const contract of contracts) {
36
+ let cExecutable = 0;
37
+ let cAdvisory = 0;
38
+ let cUnclassified = 0;
39
+ for (const pc of contract.postconditions || []) {
40
+ if (typeof pc !== 'object' || pc === null) { cUnclassified += 1; continue; }
41
+ if (pc.postconditionType === 'executable') cExecutable += 1;
42
+ else if (pc.postconditionType === 'advisory') cAdvisory += 1;
43
+ else cUnclassified += 1;
44
+ }
45
+ const total = cExecutable + cAdvisory + cUnclassified;
46
+ executable += cExecutable;
47
+ advisory += cAdvisory;
48
+ unclassified += cUnclassified;
49
+ perContract.push({
50
+ id: contract.id,
51
+ total,
52
+ executable: cExecutable,
53
+ advisory: cAdvisory,
54
+ unclassified: cUnclassified,
55
+ coveragePct: total === 0 ? null : Math.round((cExecutable / total) * 1000) / 10,
56
+ });
57
+ }
58
+
59
+ const total = executable + advisory + unclassified;
60
+ return {
61
+ contractCount: contracts.length,
62
+ total,
63
+ executable,
64
+ advisory,
65
+ unclassified,
66
+ coveragePct: total === 0 ? 0 : Math.round((executable / total) * 1000) / 10,
67
+ perContract: perContract.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),
68
+ };
69
+ }
70
+
71
+ /**
72
+ * One-line human summary, matching the style of `construct decisions`
73
+ * (lib/decisions/registry.mjs's `Decisions: N (M enforced, K advisory)`).
74
+ */
75
+ export function formatCoverageSummary(coverage) {
76
+ return `contract postconditions: ${coverage.total} (${coverage.executable} executable, ${coverage.advisory} advisory, ${coverage.coveragePct}% executable)`;
77
+ }