@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
@@ -0,0 +1,236 @@
1
+ /**
2
+ * lib/embed/presets/ops-triage.mjs — deterministic triage analysis for the
3
+ * `operations` embed preset (triage mode; see the embed-capability manifest
4
+ * schema decision record).
5
+ *
6
+ * Pure deterministic `reasoningExecutor(plan, ctx)` that the embed capability
7
+ * tick accepts: reads operations specialist's bound provider snapshot slice
8
+ * (Jira + Confluence/Directory sections, binding-scoped and filter-narrowed
9
+ * by caller) and applies ops triage framework. Produces output packet validating
10
+ * against `operations-triage` output contract plus writeIntents (Jira link +
11
+ * comment proposals) for caller to enqueue for approval.
12
+ *
13
+ * Every load-bearing claim carries provenance: a source id (Jira issue key,
14
+ * Confluence page id) reader can re-verify. Finding with no covering evidence
15
+ * is reported with `[unverified]`, never a fabricated issue key.
16
+ *
17
+ * Analysis moves (mapped to ops triage framework):
18
+ * 1. Inventory issues from bound Jira project.
19
+ * 2. Detect duplicates: similar summaries + same component/type.
20
+ * 3. Check specification completeness: description length, field coverage.
21
+ * 4. Each proposal is typed writeIntent (duplicate-link or needs-info).
22
+ * 5. No write executes without approval.
23
+ */
24
+
25
+ const JIRA_PROVIDER = 'atlassian-jira';
26
+ const CONFLUENCE_PROVIDER = 'atlassian-confluence';
27
+ const DIRECTORY_PROVIDER = 'directory';
28
+
29
+ const JIRA_LINK_KIND = 'updateItem';
30
+ const JIRA_COMMENT_KIND = 'comment';
31
+
32
+ // Minimum description length to be considered specified.
33
+ const MIN_SPEC_LENGTH = 50;
34
+
35
+ // Similarity threshold for duplicate detection (word-based Jaccard similarity).
36
+ const DUPLICATE_SIMILARITY_THRESHOLD = 0.3;
37
+
38
+ function sectionItems(sections, providerId) {
39
+ const section = (sections || []).find((s) => s.provider === providerId);
40
+ return Array.isArray(section?.items) ? section.items : [];
41
+ }
42
+
43
+ function issueKey(issue) {
44
+ return issue?.key ?? issue?.id ?? 'unknown';
45
+ }
46
+
47
+ function issueId(issue) {
48
+ return issue?.id ?? issue?.key ?? 'unknown';
49
+ }
50
+
51
+ // Word-based similarity: ratio of common words to total unique words; signals potential duplicates for manual review.
52
+
53
+ function wordSimilarity(text1, text2) {
54
+ const normalize = (t) => String(t ?? '').toLowerCase().split(/\W+/).filter((w) => w.length > 2);
55
+ const words1 = new Set(normalize(text1));
56
+ const words2 = new Set(normalize(text2));
57
+ const union = new Set([...words1, ...words2]);
58
+ const intersection = new Set([...words1].filter((w) => words2.has(w)));
59
+ return union.size === 0 ? 0 : intersection.size / union.size;
60
+ }
61
+
62
+ // Extract properties for triage checks: key, summary, description, type, component, status.
63
+
64
+ export function inventoryNewIssues(issues) {
65
+ if (!Array.isArray(issues)) return [];
66
+ return issues.map((issue) => ({
67
+ key: issueKey(issue),
68
+ id: issueId(issue),
69
+ summary: issue?.summary ?? '[untitled]',
70
+ description: String(issue?.description ?? '').trim(),
71
+ type: issue?.type ?? 'unknown',
72
+ component: issue?.component ?? issue?.components?.[0] ?? 'unknown',
73
+ status: issue?.status ?? 'unknown',
74
+ created: issue?.created ?? issue?.createdDate ?? 'unknown',
75
+ sourceIssue: issue,
76
+ }));
77
+ }
78
+
79
+ // Compare each issue against others for same type + component + high summary similarity.
80
+
81
+ export function findDuplicates(inventoriedIssues) {
82
+ const findings = [];
83
+ const proposals = [];
84
+
85
+ // For each issue, check against all earlier issues (to avoid duplicate pairs).
86
+ for (let i = 0; i < inventoriedIssues.length; i++) {
87
+ const current = inventoriedIssues[i];
88
+ for (let j = 0; j < i; j++) {
89
+ const other = inventoriedIssues[j];
90
+
91
+ // Heuristic: same type + same component + high summary similarity.
92
+ const sameType = current.type === other.type;
93
+ const sameComponent = current.component === other.component;
94
+ const similarity = wordSimilarity(current.summary, other.summary);
95
+
96
+ if (sameType && sameComponent && similarity >= DUPLICATE_SIMILARITY_THRESHOLD) {
97
+ findings.push({
98
+ kind: 'potential-duplicate',
99
+ issue: current.key,
100
+ candidate: other.key,
101
+ similarity: similarity.toFixed(2),
102
+ evidence: {
103
+ issue: current.key,
104
+ candidate: other.key,
105
+ type: current.type,
106
+ component: current.component,
107
+ similarity: similarity.toFixed(2),
108
+ },
109
+ statement: `${current.key} (${current.summary}) appears to be a duplicate of ${other.key} (${other.summary}) — same type/component, similarity ${(similarity * 100).toFixed(0)}% (sources ${current.key}, ${other.key}).`,
110
+ });
111
+
112
+ // Propose a duplicate-link: link the newer issue to the older one.
113
+ proposals.push({
114
+ providerId: JIRA_PROVIDER,
115
+ writeKind: JIRA_LINK_KIND,
116
+ issueKey: current.key,
117
+ duplicateCandidateKey: other.key,
118
+ payload: {
119
+ type: 'link',
120
+ linkType: 'duplicates',
121
+ fromKey: current.key,
122
+ toKey: other.key,
123
+ },
124
+ });
125
+ }
126
+ }
127
+ }
128
+
129
+ return { findings, proposals };
130
+ }
131
+
132
+ // Check specification completeness: description too short or no acceptance criteria/expected outcome.
133
+
134
+ export function findUnderspecified(inventoriedIssues) {
135
+ const findings = [];
136
+ const proposals = [];
137
+
138
+ for (const issue of inventoriedIssues) {
139
+ const reasons = [];
140
+ if (issue.description.length < MIN_SPEC_LENGTH) {
141
+ reasons.push('description too brief');
142
+ }
143
+ if (!issue.description.includes('acceptance') && !issue.description.includes('criteria')) {
144
+ reasons.push('no visible acceptance criteria');
145
+ }
146
+
147
+ if (reasons.length > 0) {
148
+ findings.push({
149
+ kind: 'underspecified',
150
+ issue: issue.key,
151
+ reasons,
152
+ evidence: {
153
+ issue: issue.key,
154
+ descriptionLength: issue.description.length,
155
+ reasons,
156
+ },
157
+ statement: `${issue.key} is under-specified: ${reasons.join(', ')} (source ${issue.key}).`,
158
+ });
159
+
160
+ // Propose a needs-info comment with a template.
161
+ proposals.push({
162
+ providerId: JIRA_PROVIDER,
163
+ writeKind: JIRA_COMMENT_KIND,
164
+ issueKey: issue.key,
165
+ payload: {
166
+ type: 'comment',
167
+ text: 'Hi! This ticket needs a bit more detail to get started. Could you add:\n- A clear problem statement\n- Acceptance criteria or expected outcomes\n- Any known constraints or dependencies\n\nThanks!',
168
+ },
169
+ });
170
+ }
171
+ }
172
+
173
+ return { findings, proposals };
174
+ }
175
+
176
+ // Pure deterministic triage analysis: reads sections, produces output packet + write proposals + summary.
177
+
178
+ export function analyzeOpsTriage(sections, { now = Date.now(), generatedAt } = {}) {
179
+ const issues = sectionItems(sections, JIRA_PROVIDER);
180
+
181
+ const inventoried = inventoryNewIssues(issues);
182
+ const duplicates = findDuplicates(inventoried);
183
+ const underspecified = findUnderspecified(inventoried);
184
+
185
+ const allFindings = [...duplicates.findings, ...underspecified.findings];
186
+ const allProposals = [...duplicates.proposals, ...underspecified.proposals];
187
+ const provenance = inventoried.map((i) => i.key);
188
+
189
+ const analysis = {
190
+ generatedAt: generatedAt ?? new Date(now).toISOString(),
191
+ inventoried,
192
+ duplicates: duplicates.findings,
193
+ underspecified: underspecified.findings,
194
+ allFindings,
195
+ };
196
+
197
+ const summary = [
198
+ `Triaged ${inventoried.length} issues.`,
199
+ `Found ${duplicates.findings.length} potential duplicates.`,
200
+ `Found ${underspecified.findings.length} under-specified tickets.`,
201
+ ].join(' ');
202
+
203
+ // Each contract field is a section wrapper ({ count, findings/items })
204
+ // rather than a bare array so a legitimately empty section still counts as
205
+ // present under the output contract — an absent section signals a broken
206
+ // producer, an empty one signals "checked, nothing found".
207
+ const outputPacket = {
208
+ inventoried: { count: inventoried.length, items: inventoried },
209
+ duplicates: { count: duplicates.findings.length, findings: duplicates.findings },
210
+ underspecified: { count: underspecified.findings.length, findings: underspecified.findings },
211
+ proposals: {
212
+ count: allProposals.length,
213
+ items: allProposals.map((p) => ({
214
+ providerId: p.providerId,
215
+ writeKind: p.writeKind,
216
+ issueKey: p.issueKey,
217
+ summary: p.payload.type === 'link' ? `Link ${p.issueKey} as duplicate of ${p.duplicateCandidateKey}` : `Comment on ${p.issueKey}`,
218
+ })),
219
+ },
220
+ provenance: { count: provenance.length, sources: provenance },
221
+ summary,
222
+ };
223
+
224
+ return { analysis, outputPacket, writeProposals: allProposals, summary };
225
+ }
226
+
227
+ // F5 reasoningExecutor: wraps analyzeOpsTriage for embed capability tick; accepts plan for F5 contract compatibility.
228
+
229
+ export function createOpsTriageReasoningExecutor(config = {}) {
230
+ return async function opsTriageReasoningExecutor(_plan, ctx = {}) {
231
+ const { outputPacket, writeProposals, summary } = analyzeOpsTriage(ctx.sections ?? [], config);
232
+ return { outputPacket, writeProposals, summary };
233
+ };
234
+ }
235
+
236
+ export default createOpsTriageReasoningExecutor;
@@ -0,0 +1,341 @@
1
+ /**
2
+ * lib/embed/presets/pm-feedback.mjs — deterministic PM feedback→requirements
3
+ * analysis for the `product-manager` embed preset (see the embed-capability
4
+ * manifest schema decision record).
5
+ *
6
+ * Pure deterministic `reasoningExecutor(plan, ctx)` that the embed capability
7
+ * tick accepts: reads the product-manager specialist's bound provider
8
+ * snapshot slice (feedback + Confluence sections, already binding-scoped and
9
+ * filter-narrowed by the caller) and walks the F7 PM value-tradeoff framework
10
+ * (cx-pm-value-tradeoff) over it. It emits an output packet that validates
11
+ * against the `pm-requirements-candidates` output contract. There is no
12
+ * writeIntent output — this preset is artifact-only: it produces requirement
13
+ * candidates for a human PM to act on, never a provider write.
14
+ *
15
+ * Trust boundary (B14 / N1): feedback-item text originates from an external,
16
+ * unauthenticated source and is stamped EXTERNAL_UNAUTHENTICATED. Every
17
+ * feedback string that reaches the output packet is confined to a `quote`
18
+ * field as quoted evidence: clustering, scoring, and cross-referencing all
19
+ * branch on structural fields (tags, theme keywords, requirement id substring
20
+ * matches), so the feedback text itself can only be displayed, not executed
21
+ * or used as a control-flow directive.
22
+ *
23
+ * Every load-bearing claim carries provenance: a source id (feedback file+row,
24
+ * Confluence page id) the reader can re-verify. A candidate with no covering
25
+ * PRD requirement is reported as `new` (net-new), never linked to a
26
+ * fabricated requirement id.
27
+ *
28
+ * Analysis moves (mapped to the F7 framework steps):
29
+ * 1. user-value: cluster feedback-items by theme (tag or keyword match),
30
+ * name the recurring job each cluster represents.
31
+ * 2. tradeoffs: score each cluster with the user-value rubric (frequency +
32
+ * author diversity + explicit pain signal).
33
+ * 3. prioritization: cross-reference each cluster against PRD requirement
34
+ * items (Confluence) — supports / contradicts / new.
35
+ * 4. acceptance-criteria: emit one requirements-candidate per cluster,
36
+ * provenance-linked to every contributing feedback row and (when
37
+ * matched) the PRD requirement id.
38
+ */
39
+
40
+ const FEEDBACK_PROVIDER = 'feedback';
41
+ const CONFLUENCE_PROVIDER = 'atlassian-confluence';
42
+
43
+ // A requirement line in a PRD body: an id token (REQ-12, R-3, FR-4…) followed
44
+ // by a colon or dash and the requirement text. Matching is deterministic so a
45
+ // seeded fixture produces the same requirement ids on every run.
46
+ const REQUIREMENT_LINE_RE = /^\s*(?:[-*]\s*)?((?:REQ|FR|NFR|R)-\d+)\s*[:.\-–)]\s*(.+?)\s*$/i;
47
+
48
+ // Explicit pain-signal terms that raise a cluster's user-value score when
49
+ // present in feedback text. This is a fixed vocabulary lookup, not an
50
+ // instruction follow — feedback text can only ever match or not match these
51
+ // literal terms, never redirect the analysis.
52
+ const PAIN_TERMS = ['broken', 'frustrating', 'confusing', 'cannot', "can't", 'blocked', 'unusable', 'slow', 'crashes', 'lost'];
53
+
54
+ // Minimum shared-word ratio (Jaccard on words) for two feedback items to be
55
+ // placed in the same theme cluster when no explicit tag is present.
56
+ const THEME_SIMILARITY_THRESHOLD = 0.25;
57
+
58
+ function sectionItems(sections, providerId) {
59
+ const section = (sections || []).find((s) => s.provider === providerId);
60
+ return Array.isArray(section?.items) ? section.items : [];
61
+ }
62
+
63
+ function feedbackId(item) {
64
+ return item?.id ?? 'unknown';
65
+ }
66
+
67
+ function feedbackProvenance(item) {
68
+ const file = item?.provenance?.file ?? 'unknown';
69
+ const row = item?.provenance?.row;
70
+ return row != null ? `${file}#${row}` : file;
71
+ }
72
+
73
+ function normalizeWords(text) {
74
+ return String(text ?? '').toLowerCase().split(/\W+/).filter((w) => w.length > 2);
75
+ }
76
+
77
+ function wordSimilarity(text1, text2) {
78
+ const words1 = new Set(normalizeWords(text1));
79
+ const words2 = new Set(normalizeWords(text2));
80
+ const union = new Set([...words1, ...words2]);
81
+ const intersection = new Set([...words1].filter((w) => words2.has(w)));
82
+ return union.size === 0 ? 0 : intersection.size / union.size;
83
+ }
84
+
85
+ // Every trust check is a structural read of the `_trust` stamp — never a
86
+ // parse of the feedback text itself — so an untrusted item can only ever be
87
+ // labeled, never able to change which branch this function takes.
88
+
89
+ function trustLabel(item) {
90
+ return item?._trust?.level ?? 'unknown';
91
+ }
92
+
93
+ /**
94
+ * Cluster feedback-items by theme. An explicit shared tag wins; absent a
95
+ * shared tag, items land in the same cluster when their text passes the
96
+ * word-similarity threshold against the cluster's seed item. Deterministic:
97
+ * clusters are built in input order, so a seeded fixture always produces the
98
+ * same cluster membership.
99
+ */
100
+ export function clusterByTheme(feedbackItems) {
101
+ const clusters = [];
102
+
103
+ for (const item of feedbackItems) {
104
+ const tags = Array.isArray(item?.tags) ? item.tags : [];
105
+ let target = null;
106
+
107
+ if (tags.length > 0) {
108
+ target = clusters.find((c) => c.tag && tags.includes(c.tag));
109
+ }
110
+ if (!target) {
111
+ target = clusters.find((c) => !c.tag && wordSimilarity(item?.text, c.seedText) >= THEME_SIMILARITY_THRESHOLD);
112
+ }
113
+
114
+ if (target) {
115
+ target.items.push(item);
116
+ } else {
117
+ clusters.push({
118
+ theme: tags[0] ?? feedbackId(item),
119
+ tag: tags[0] ?? null,
120
+ seedText: item?.text ?? '',
121
+ items: [item],
122
+ });
123
+ }
124
+ }
125
+
126
+ return clusters.map((c) => ({ theme: c.theme, items: c.items }));
127
+ }
128
+
129
+ /**
130
+ * Score a theme cluster with the user-value rubric: frequency (item count),
131
+ * author diversity (distinct authors), and explicit pain signal (count of
132
+ * PAIN_TERMS matches across the cluster's feedback text). Score is a plain
133
+ * sum of these three signals — deterministic, no external weighting table.
134
+ * Every contributing pain-term match is itself evidence, not instruction: it
135
+ * is recorded as a literal string match, never executed.
136
+ */
137
+ export function scoreCluster(cluster) {
138
+ const items = cluster.items || [];
139
+ const frequency = items.length;
140
+ const authors = new Set(items.map((i) => i?.author).filter(Boolean));
141
+ const authorDiversity = authors.size;
142
+
143
+ let painSignal = 0;
144
+ const painMatches = [];
145
+ for (const item of items) {
146
+ const lower = String(item?.text ?? '').toLowerCase();
147
+ const matched = PAIN_TERMS.filter((term) => lower.includes(term));
148
+ if (matched.length > 0) {
149
+ painSignal += matched.length;
150
+ painMatches.push({ id: feedbackId(item), terms: matched });
151
+ }
152
+ }
153
+
154
+ return {
155
+ frequency,
156
+ authorDiversity,
157
+ painSignal,
158
+ userValueScore: frequency + authorDiversity + painSignal,
159
+ painMatches,
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Parse every PRD doc's body into requirement items, mirroring the P3 TPM
165
+ * preset's parser so requirement ids are stable across presets.
166
+ */
167
+ export function parseRequirements(docs) {
168
+ const requirements = [];
169
+ for (const doc of docs || []) {
170
+ const sourceDocId = doc?.id ?? doc?.pageId ?? doc?.key ?? 'unknown';
171
+ const sourceTitle = doc?.title ?? '[untitled]';
172
+ const body = String(doc?.body ?? doc?.text ?? doc?.content ?? '');
173
+ for (const line of body.split(/\r?\n/)) {
174
+ const match = REQUIREMENT_LINE_RE.exec(line);
175
+ if (!match) continue;
176
+ const reqId = match[1].toUpperCase();
177
+ requirements.push({
178
+ reqId,
179
+ text: match[2].trim(),
180
+ sourceDocId,
181
+ sourceTitle,
182
+ provenance: `${sourceDocId}#${reqId}`,
183
+ });
184
+ }
185
+ }
186
+ return requirements;
187
+ }
188
+
189
+ // A cluster's theme text is checked against a requirement's text for shared
190
+ // words above the threshold; a cluster whose feedback explicitly mentions a
191
+ // requirement id string is a stronger, direct match.
192
+
193
+ function clusterMentionsRequirement(cluster, req) {
194
+ const reqIdUpper = req.reqId.toUpperCase();
195
+ const directMention = (cluster.items || []).some((item) => String(item?.text ?? '').toUpperCase().includes(reqIdUpper));
196
+ if (directMention) return { matched: true, kind: 'direct' };
197
+
198
+ const clusterText = (cluster.items || []).map((i) => i?.text ?? '').join(' ');
199
+ const similarity = wordSimilarity(clusterText, req.text);
200
+ if (similarity >= THEME_SIMILARITY_THRESHOLD) return { matched: true, kind: 'thematic', similarity };
201
+
202
+ return { matched: false };
203
+ }
204
+
205
+ /**
206
+ * Cross-reference each theme cluster against the parsed PRD requirements.
207
+ * Returns one row per cluster: `supports` when the cluster's feedback
208
+ * reinforces an existing requirement, `contradicts` when pain signal is high
209
+ * against a requirement already marked covered-in-PRD text as resolved (this
210
+ * preset does not have Jira coverage data, so contradiction detection is
211
+ * conservative — thematic match plus a high pain signal), otherwise `new`
212
+ * (net-new, no matching requirement in the bound PRD set).
213
+ */
214
+ export function crossReferenceRequirements(clusters, requirements) {
215
+ return clusters.map((cluster) => {
216
+ let best = null;
217
+ for (const req of requirements) {
218
+ const match = clusterMentionsRequirement(cluster, req);
219
+ if (match.matched) {
220
+ if (!best || match.kind === 'direct') best = { req, match };
221
+ if (best.match.kind === 'direct') break;
222
+ }
223
+ }
224
+
225
+ if (!best) {
226
+ return { theme: cluster.theme, relation: 'new', requirement: null, matchKind: null };
227
+ }
228
+ return {
229
+ theme: cluster.theme,
230
+ relation: 'supports',
231
+ requirement: { reqId: best.req.reqId, text: best.req.text, provenance: best.req.provenance },
232
+ matchKind: best.match.kind,
233
+ };
234
+ });
235
+ }
236
+
237
+ /**
238
+ * Build one requirements-candidate per theme cluster. Every candidate
239
+ * carries provenance to every feedback row it draws on and, when matched, to
240
+ * the PRD requirement it relates to. Feedback text is carried only inside a
241
+ * `quote` field on each evidence row — quoted evidence, never instruction —
242
+ * and every quoted item's trust label is recorded alongside it so a
243
+ * downstream reader can see it came from an EXTERNAL_UNAUTHENTICATED source.
244
+ */
245
+ export function buildCandidates(clusters, scores, crossRefs) {
246
+ return clusters.map((cluster, i) => {
247
+ const score = scores[i];
248
+ const crossRef = crossRefs[i];
249
+
250
+ const evidence = cluster.items.map((item) => ({
251
+ feedbackId: feedbackId(item),
252
+ provenance: feedbackProvenance(item),
253
+ trust: trustLabel(item),
254
+ quote: item?.text ?? '',
255
+ }));
256
+
257
+ const relation = crossRef.relation;
258
+ const candidateId = `RC-${String(i + 1).padStart(3, '0')}`;
259
+
260
+ return {
261
+ candidateId,
262
+ theme: cluster.theme,
263
+ userValueScore: score.userValueScore,
264
+ scoreBreakdown: {
265
+ frequency: score.frequency,
266
+ authorDiversity: score.authorDiversity,
267
+ painSignal: score.painSignal,
268
+ },
269
+ relation,
270
+ requirement: crossRef.requirement,
271
+ evidence,
272
+ statement: relation === 'new'
273
+ ? `Theme '${cluster.theme}' (${cluster.items.length} feedback item(s), user-value score ${score.userValueScore}) has no matching PRD requirement — net-new candidate (sources ${evidence.map((e) => e.provenance).join(', ')}).`
274
+ : `Theme '${cluster.theme}' (${cluster.items.length} feedback item(s), user-value score ${score.userValueScore}) ${relation} ${crossRef.requirement.reqId} (source ${crossRef.requirement.provenance}); feedback sources ${evidence.map((e) => e.provenance).join(', ')}.`,
275
+ };
276
+ });
277
+ }
278
+
279
+ /**
280
+ * Pure deterministic PM feedback analysis: reads sections, produces the
281
+ * output packet (contract shape) plus the structured analysis. No write
282
+ * proposals — this preset is artifact-only.
283
+ */
284
+ export function analyzePmFeedback(sections, { now = Date.now(), generatedAt } = {}) {
285
+ const feedbackItems = sectionItems(sections, FEEDBACK_PROVIDER);
286
+ const docs = sectionItems(sections, CONFLUENCE_PROVIDER);
287
+
288
+ const clusters = clusterByTheme(feedbackItems);
289
+ const scores = clusters.map(scoreCluster);
290
+ const requirements = parseRequirements(docs);
291
+ const crossRefs = crossReferenceRequirements(clusters, requirements);
292
+ const candidates = buildCandidates(clusters, scores, crossRefs);
293
+
294
+ const provenance = [
295
+ ...feedbackItems.map((item) => feedbackProvenance(item)),
296
+ ...requirements.map((r) => r.provenance),
297
+ ];
298
+
299
+ const analysis = {
300
+ generatedAt: generatedAt ?? new Date(now).toISOString(),
301
+ clusters,
302
+ scores,
303
+ requirements,
304
+ crossRefs,
305
+ candidates,
306
+ };
307
+
308
+ const summary = [
309
+ `Clustered ${feedbackItems.length} feedback item(s) into ${clusters.length} theme(s).`,
310
+ `Cross-referenced against ${requirements.length} PRD requirement(s).`,
311
+ `Produced ${candidates.length} requirements-candidate(s).`,
312
+ ].join(' ');
313
+
314
+ // Each contract field is a section wrapper ({ count, items }) rather than a
315
+ // bare array so a legitimately empty section still counts as present under
316
+ // the output contract — an absent section signals a broken producer, an
317
+ // empty one signals "checked, nothing found".
318
+ const outputPacket = {
319
+ candidates: { count: candidates.length, items: candidates },
320
+ clusters: { count: clusters.length, items: clusters.map((c) => ({ theme: c.theme, itemCount: c.items.length })) },
321
+ requirements: { count: requirements.length, items: requirements },
322
+ provenance: { count: provenance.length, sources: provenance },
323
+ summary,
324
+ };
325
+
326
+ return { analysis, outputPacket, writeProposals: [], summary };
327
+ }
328
+
329
+ /**
330
+ * F5 reasoningExecutor: wraps analyzePmFeedback for the embed capability
331
+ * tick; accepts plan for F5 contract compatibility. Always returns an empty
332
+ * writeProposals array — this preset never proposes a provider write.
333
+ */
334
+ export function createPmFeedbackReasoningExecutor(config = {}) {
335
+ return async function pmFeedbackReasoningExecutor(_plan, ctx = {}) {
336
+ const { outputPacket, summary } = analyzePmFeedback(ctx.sections ?? [], config);
337
+ return { outputPacket, writeProposals: [], summary };
338
+ };
339
+ }
340
+
341
+ export default createPmFeedbackReasoningExecutor;