@geraldmaron/construct 1.0.0 → 1.0.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 (471) hide show
  1. package/.env.example +10 -7
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1116 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +525 -38
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. package/rules/zh/testing.md +0 -34
@@ -0,0 +1,355 @@
1
+ /**
2
+ * lib/embed/config.mjs — embed mode configuration schema and loader.
3
+ *
4
+ * Reads embed.yaml from ~/.construct/embed.yaml (or a supplied path) and validates
5
+ * the file against the expected schema. Returns a parsed, normalized config object.
6
+ *
7
+ * Schema:
8
+ *
9
+ * sources:
10
+ * - provider: github
11
+ * repo: owner/name # single repo
12
+ * repos: [owner/a, owner/b] # OR list of repos
13
+ * refs: [prs, issues, commits]
14
+ * branch: main # for commits ref
15
+ * limit: 25
16
+ *
17
+ * - provider: slack
18
+ * channel: C12345678 # single channel ID or name
19
+ * channels: [general, eng] # OR list
20
+ * refs: [messages]
21
+ * oldest: 86400 # seconds of history (default 24h)
22
+ *
23
+ * - provider: linear
24
+ * team: ENG # team key (optional)
25
+ * project: My Project # project name filter (optional)
26
+ * refs: [issues, cycles]
27
+ * states: [Todo, In Progress]
28
+ *
29
+ * - provider: jira
30
+ * project: PROJ # project key
31
+ * projects: [PROJ, INFRA] # OR list
32
+ * refs: [issues, sprints]
33
+ * jql: "project = PROJ AND sprint in openSprints()" # override JQL
34
+ *
35
+ * outputs:
36
+ * - type: markdown # write snapshot to a file
37
+ * path: .cx/snapshot.md
38
+ * - type: slack # post to a Slack channel
39
+ * provider: slack
40
+ * channel: C12345
41
+ * - type: dashboard # push to the Construct dashboard
42
+ *
43
+ * approval:
44
+ * require: # action types that need approval before dispatch
45
+ * - issue.create
46
+ * - pr.merge
47
+ * timeout_ms: 3600000 # how long to wait before auto-expiring (default: 1h)
48
+ * fallback: reject # reject | proceed (default: reject)
49
+ *
50
+ * snapshot:
51
+ * intervalMs: 300000 # how often to regenerate full snapshot (default: 5m)
52
+ * maxItems: 100 # max items per source in snapshot
53
+ */
54
+
55
+ import fs from 'node:fs';
56
+ import os from 'node:os';
57
+ import path from 'node:path';
58
+
59
+ const DEFAULTS = {
60
+ snapshot: { intervalMs: 300_000, maxItems: 100 },
61
+ approval: { require: [], timeout_ms: 3_600_000, fallback: 'reject' },
62
+ roles: { primary: null, secondary: null },
63
+ };
64
+
65
+ /**
66
+ * Target types for embed output routing.
67
+ *
68
+ * targets:
69
+ * - type: repo
70
+ * ref: github.com/org/project-x # remote — interact via provider
71
+ * - type: repo
72
+ * path: /Users/user/Github/local-project # local filesystem
73
+ * - type: repo
74
+ * ref: github.com/org/project-y
75
+ * path: /Users/user/Github/project-y # both — prefer local, fall back to remote
76
+ * - type: workspace # always present as fallback
77
+ * path: ~/.construct/workspace
78
+ *
79
+ * roles:
80
+ * primary: product-manager # shapes embed analysis lens
81
+ * secondary: architect # persistent background concern
82
+ */
83
+ export const DEFAULT_WORKSPACE_PATH = path.join(os.homedir(), '.construct', 'workspace');
84
+
85
+ export const WORKSPACE_DOCS_LANES = ['adrs', 'prds', 'memos', 'notes', 'intake'];
86
+
87
+ export const OPERATING_PROFILE_VERSION = 'embed-operating-profile/v1';
88
+
89
+ export const DEFAULT_OPERATING_PROFILE = {
90
+ schemaVersion: OPERATING_PROFILE_VERSION,
91
+ mode: 'embed',
92
+ mission: 'Maintain situational awareness across connected project systems and turn weak signals into actionable plans, drafts, risks, and approval-queued work.',
93
+ strategy: {
94
+ defaultPosture: 'assistive',
95
+ autonomy: 'read-first',
96
+ writePolicy: 'approval-required-for-high-risk',
97
+ dependencyPolicy: 'zero-deps',
98
+ precedence: 'approval rules override operating profile preferences',
99
+ },
100
+ focalResources: [
101
+ { type: 'plan', path: 'plan.md', purpose: 'active implementation plan' },
102
+ { type: 'architecture', path: 'docs/concepts/architecture.md', purpose: 'system architecture and operating model' },
103
+ { type: 'knowledge', path: '.cx/knowledge', purpose: 'typed project knowledge and ingested evidence' },
104
+ { type: 'runtime', path: '.cx/roadmap.md', purpose: 'generated roadmap output' },
105
+ ],
106
+ approach: [
107
+ 'read configured sources',
108
+ 'compare observed activity with mission and focal resources',
109
+ 'identify gaps, risks, stale decisions, and missing artifacts',
110
+ 'draft roadmaps, PRDs, RFCs, ADRs, memos, status updates, summaries, and wireframes',
111
+ 'route high-risk writes through the approval queue',
112
+ ],
113
+ authority: {
114
+ read: 'autonomous',
115
+ summarize: 'autonomous',
116
+ draftArtifacts: 'autonomous',
117
+ createIssues: 'approval-queued',
118
+ updateIssues: 'approval-queued',
119
+ publishDocs: 'approval-queued',
120
+ externalPost: 'approval-queued',
121
+ repoWrites: 'approval-queued',
122
+ },
123
+ responsibilities: {
124
+ gapAnalysis: true,
125
+ issueLifecycle: { create: 'approval-queued', update: 'approval-queued', dedupe: 'recommend' },
126
+ artifacts: {
127
+ roadmaps: 'generate',
128
+ prds: 'draft',
129
+ rfcs: 'draft',
130
+ adrs: 'draft',
131
+ memos: 'draft',
132
+ statusUpdates: 'generate',
133
+ summaries: 'generate',
134
+ wireframes: 'draft',
135
+ risks: 'generate',
136
+ },
137
+ },
138
+ riskModel: {
139
+ detect: ['blocked work', 'missing owner', 'stale cadence', 'source errors', 'missing focal resource', 'unapproved high-risk action'],
140
+ dashboardStatus: { ok: 'no known gaps', attention: 'gaps or source errors exist', blocked: 'required focal resource or approval path missing' },
141
+ },
142
+ };
143
+
144
+ export const EMPTY_CONFIG = {
145
+ sources: [],
146
+ outputs: [],
147
+ targets: [{ type: 'workspace', path: DEFAULT_WORKSPACE_PATH }],
148
+ roles: { ...DEFAULTS.roles },
149
+ snapshot: { ...DEFAULTS.snapshot },
150
+ approval: { ...DEFAULTS.approval },
151
+ operatingProfile: DEFAULT_OPERATING_PROFILE,
152
+ };
153
+
154
+ function deepMerge(base, override) {
155
+ if (!override || typeof override !== 'object' || Array.isArray(override)) return base;
156
+ const merged = { ...base };
157
+ for (const [key, value] of Object.entries(override)) {
158
+ if (Array.isArray(value)) merged[key] = value;
159
+ else if (value && typeof value === 'object' && base[key] && typeof base[key] === 'object' && !Array.isArray(base[key])) {
160
+ merged[key] = deepMerge(base[key], value);
161
+ } else {
162
+ merged[key] = value;
163
+ }
164
+ }
165
+ return merged;
166
+ }
167
+
168
+ export function loadEmbedConfig(configPath) {
169
+ if (!fs.existsSync(configPath)) {
170
+ throw new Error(`Embed config not found: ${configPath}`);
171
+ }
172
+
173
+ const raw = fs.readFileSync(configPath, 'utf8');
174
+ // Minimal YAML subset parser: handles the flat-ish schema we use.
175
+ // Full YAML parsing requires a dep; we use a hand-rolled parser for zero-dep core.
176
+ const parsed = parseEmbedYaml(raw);
177
+ return normalize(parsed);
178
+ }
179
+
180
+ export function normalize(raw) {
181
+ const config = {
182
+ sources: [],
183
+ outputs: [],
184
+ targets: [],
185
+ roles: { ...DEFAULTS.roles, ...(raw.roles ?? {}) },
186
+ snapshot: { ...DEFAULTS.snapshot, ...(raw.snapshot ?? {}) },
187
+ approval: { ...DEFAULTS.approval, ...(raw.approval ?? {}) },
188
+ operatingProfile: deepMerge(DEFAULT_OPERATING_PROFILE, raw.operatingProfile ?? {}),
189
+ };
190
+
191
+ for (const src of raw.sources ?? []) {
192
+ if (!src.provider) throw new Error('Each source must have a "provider" field');
193
+ config.sources.push({
194
+ ...src,
195
+ provider: src.provider,
196
+ refs: Array.isArray(src.refs) ? src.refs : [src.refs ?? 'status'],
197
+ intervalMs: src.intervalMs ?? 60_000,
198
+ });
199
+ }
200
+
201
+ for (const out of raw.outputs ?? []) {
202
+ if (!out.type) throw new Error('Each output must have a "type" field');
203
+ config.outputs.push({ ...out });
204
+ }
205
+
206
+ // Targets: explicit list, always has workspace fallback
207
+ for (const t of raw.targets ?? []) {
208
+ if (!t.type) throw new Error('Each target must have a "type" field');
209
+ config.targets.push({ ...t });
210
+ }
211
+ // Ensure workspace fallback is always present
212
+ if (!config.targets.some((t) => t.type === 'workspace')) {
213
+ config.targets.push({ type: 'workspace', path: DEFAULT_WORKSPACE_PATH });
214
+ }
215
+
216
+ return config;
217
+ }
218
+
219
+ /**
220
+ * Hand-rolled parser for the embed YAML schema.
221
+ * Supports: top-level keys, nested objects, lists of objects, scalar values.
222
+ * Not a general YAML parser — only handles what embed.yaml uses.
223
+ */
224
+ export function parseEmbedYaml(text) {
225
+ const lines = text.split('\n');
226
+ const root = {};
227
+ let i = 0;
228
+
229
+ function peek() { return lines[i]; }
230
+ function advance() { return lines[i++]; }
231
+ function indent(line) { return line.match(/^(\s*)/)[1].length; }
232
+ function isComment(line) { return line.trimStart().startsWith('#'); }
233
+ function isEmpty(line) { return line.trim() === ''; }
234
+
235
+ function parseValue(val) {
236
+ const v = val.trim();
237
+ if (v === 'true') return true;
238
+ if (v === 'false') return false;
239
+ if (v === 'null' || v === '~' || v === '') return null;
240
+ if (v === '[]') return [];
241
+ if (v === '{}') return {};
242
+ // Inline flow list: [a, b, c]
243
+ if (v.startsWith('[') && v.endsWith(']')) {
244
+ const inner = v.slice(1, -1).trim();
245
+ if (inner === '') return [];
246
+ return inner.split(',').map((s) => parseValue(s.trim()));
247
+ }
248
+ const n = Number(v);
249
+ if (!Number.isNaN(n) && v !== '') return n;
250
+ // Strip quotes
251
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
252
+ return v.slice(1, -1);
253
+ }
254
+ return v;
255
+ }
256
+
257
+ function parseBlock(baseIndent) {
258
+ const obj = {};
259
+ while (i < lines.length) {
260
+ const line = peek();
261
+ if (isEmpty(line) || isComment(line)) { advance(); continue; }
262
+ const lineIndent = indent(line);
263
+ if (lineIndent < baseIndent) break;
264
+ if (lineIndent > baseIndent) { advance(); continue; } // skip malformed
265
+
266
+ advance();
267
+ const trimmed = line.trim();
268
+
269
+ // List item
270
+ if (trimmed.startsWith('- ')) {
271
+ // Not expected at block level — handled by parseList
272
+ break;
273
+ }
274
+
275
+ const colonIdx = trimmed.indexOf(':');
276
+ if (colonIdx === -1) continue;
277
+ const key = trimmed.slice(0, colonIdx).trim();
278
+ const rest = trimmed.slice(colonIdx + 1);
279
+
280
+ if (rest.trim() === '') {
281
+ // Next lines are either a list or nested object
282
+ const nextLine = lines[i];
283
+ if (!nextLine) { obj[key] = null; continue; }
284
+ const nextTrimmed = nextLine.trimStart();
285
+ if (nextTrimmed.startsWith('- ')) {
286
+ obj[key] = parseList(lineIndent + 2);
287
+ } else {
288
+ obj[key] = parseBlock(lineIndent + 2);
289
+ }
290
+ } else {
291
+ obj[key] = parseValue(rest);
292
+ }
293
+ }
294
+ return obj;
295
+ }
296
+
297
+ function parseList(baseIndent) {
298
+ const arr = [];
299
+ while (i < lines.length) {
300
+ const line = peek();
301
+ if (isEmpty(line) || isComment(line)) { advance(); continue; }
302
+ const lineIndent = indent(line);
303
+ if (lineIndent < baseIndent) break;
304
+
305
+ const trimmed = line.trim();
306
+ if (!trimmed.startsWith('- ')) break;
307
+ advance();
308
+
309
+ const rest = trimmed.slice(2).trim();
310
+ if (rest === '') {
311
+ // Multi-line list item object
312
+ arr.push(parseBlock(lineIndent + 2));
313
+ } else if (rest.includes(':')) {
314
+ // Inline object in list: "- provider: github"
315
+ const item = {};
316
+ const colonIdx = rest.indexOf(':');
317
+ const key = rest.slice(0, colonIdx).trim();
318
+ const val = rest.slice(colonIdx + 1).trim();
319
+ item[key] = val === '' ? parseBlock(lineIndent + 2) : parseValue(val);
320
+ // Consume continuation keys at same level
321
+ while (i < lines.length) {
322
+ const next = lines[i];
323
+ if (isEmpty(next) || isComment(next)) { i++; continue; }
324
+ if (indent(next) <= lineIndent) break;
325
+ i++;
326
+ const nt = next.trim();
327
+ const ci = nt.indexOf(':');
328
+ if (ci === -1) continue;
329
+ const nk = nt.slice(0, ci).trim();
330
+ const nv = nt.slice(ci + 1).trim();
331
+ if (nv === '') {
332
+ const nextNext = lines[i];
333
+ if (nextNext && nextNext.trimStart().startsWith('- ')) {
334
+ item[nk] = parseList(indent(next) + 2);
335
+ } else {
336
+ item[nk] = parseBlock(indent(next) + 2);
337
+ }
338
+ } else {
339
+ item[nk] = parseValue(nv);
340
+ }
341
+ }
342
+ arr.push(item);
343
+ } else {
344
+ arr.push(parseValue(rest));
345
+ }
346
+ }
347
+ return arr;
348
+ }
349
+
350
+ return parseBlock(0);
351
+ }
352
+
353
+ export function defaultEmbedConfigPath() {
354
+ return path.join(os.homedir(), '.construct', 'embed.yaml');
355
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * lib/embed/conflict-detection.mjs — Detect overlaps and contradictions
3
+ * between new intake signals and existing PRDs, ADRs, RFCs.
4
+ *
5
+ * Uses semantic similarity (via lib/embed/semantic.mjs) to compare new
6
+ * signals against the corpus of existing artifacts. Flags potential
7
+ * conflicts when similarity exceeds configurable thresholds.
8
+ *
9
+ * Two detection modes:
10
+ * 1. Overlap detection — new signal addresses same topic as existing artifact
11
+ * 2. Contradiction detection — new signal contradicts a documented decision
12
+ *
13
+ * Storage:
14
+ * ~/.cx/product-intel/artifact-index.json — cached artifact embeddings
15
+ * Rebuilt on first use or when artifact file count changes.
16
+ */
17
+
18
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+ import { embed, cosineSimilarity, extractTextFromPacket } from './semantic.mjs';
22
+ import { listArtifacts } from './artifact.mjs';
23
+
24
+ const INDEX_DIR = join(homedir(), '.cx', 'product-intel');
25
+ const INDEX_FILE = join(INDEX_DIR, 'artifact-index.json');
26
+
27
+ const OVERLAP_THRESHOLD = 0.6;
28
+ const CONTRADICTION_THRESHOLD = 0.7;
29
+ const STALE_INDEX_HOURS = 24;
30
+
31
+ /**
32
+ * Ensure index directory exists.
33
+ */
34
+ function ensureDir() {
35
+ if (!existsSync(INDEX_DIR)) mkdirSync(INDEX_DIR, { recursive: true });
36
+ }
37
+
38
+ let _artifactIndex = null;
39
+
40
+ /**
41
+ * Build or load the artifact index (cached by count + mtime).
42
+ * Index maps artifact path → { text, embedding, type, title }.
43
+ *
44
+ * @param {object} [opts]
45
+ * @param {string} [opts.rootDir] - Project root
46
+ * @param {boolean} [opts.forceRebuild] - Force rebuild even if cache is fresh
47
+ * @returns {Promise<Array<{ path: string, type: string, title: string, text: string, embedding: Float32Array|null }>>}
48
+ */
49
+ export async function buildArtifactIndex({ rootDir, forceRebuild = false } = {}) {
50
+ const root = rootDir || process.cwd();
51
+ ensureDir();
52
+
53
+ const artifacts = listArtifacts({ rootDir: root });
54
+ const cacheKey = artifacts.map(a => `${a.relativePath}:${statSync(a.path).mtimeMs}`).join('|');
55
+ const cacheHash = cacheKey.length;
56
+
57
+ // Check if cached index is fresh
58
+ if (!forceRebuild && existsSync(INDEX_FILE)) {
59
+ try {
60
+ const cached = JSON.parse(readFileSync(INDEX_FILE, 'utf8'));
61
+ if (cached.cacheHash === cacheHash && cached.index?.length === artifacts.length) {
62
+ const age = Date.now() - new Date(cached.builtAt).getTime();
63
+ if (age < STALE_INDEX_HOURS * 60 * 60 * 1000) {
64
+ _artifactIndex = cached.index;
65
+ return _artifactIndex;
66
+ }
67
+ }
68
+ } catch { /* rebuild */ }
69
+ }
70
+
71
+ // Build index — read artifact files, extract text, generate embeddings
72
+ const index = [];
73
+ for (const artifact of artifacts) {
74
+ try {
75
+ const content = readFileSync(artifact.path, 'utf8');
76
+ const text = extractArtifactText(content, artifact);
77
+ const vector = await embed(text);
78
+ index.push({
79
+ path: artifact.relativePath,
80
+ type: artifact.type,
81
+ title: artifact.title,
82
+ status: artifact.status,
83
+ text: text.slice(0, 2000),
84
+ embedding: vector ? Array.from(vector) : null,
85
+ });
86
+ } catch {
87
+ // skip unreadable artifacts
88
+ }
89
+ }
90
+
91
+ _artifactIndex = index;
92
+
93
+ // Persist index (without large embeddings if too many)
94
+ const persistent = index.map(a => ({
95
+ ...a,
96
+ embedding: a.embedding ? Array.from(a.embedding) : null,
97
+ }));
98
+
99
+ writeFileSync(INDEX_FILE, JSON.stringify({
100
+ cacheHash,
101
+ builtAt: new Date().toISOString(),
102
+ artifactCount: artifacts.length,
103
+ index: persistent,
104
+ }, null, 2) + '\n');
105
+
106
+ return index;
107
+ }
108
+
109
+ /**
110
+ * Extract searchable text from an artifact file.
111
+ *
112
+ * @param {string} content - Raw markdown content
113
+ * @param {object} artifact - Artifact metadata
114
+ * @returns {string} Cleaned text for embedding
115
+ */
116
+ function extractArtifactText(content, artifact) {
117
+ // Strip frontmatter
118
+ const body = content.replace(/^---[\s\S]*?---\n*/, '');
119
+
120
+ // Extract key sections based on type
121
+ const title = artifact.title || '';
122
+ const type = artifact.type || '';
123
+
124
+ const sections = [];
125
+
126
+ if (type === 'adr') {
127
+ const context = body.match(/## Context\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
128
+ const decision = body.match(/## Decision\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
129
+ sections.push(title, context, decision);
130
+ } else if (type === 'prd') {
131
+ const problem = body.match(/## Problem\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
132
+ const goals = body.match(/## Goals\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
133
+ sections.push(title, problem, goals);
134
+ } else if (type === 'rfc') {
135
+ const summary = body.match(/## Summary\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
136
+ const motivation = body.match(/## Motivation\s*\n([\s\S]*?)(?=^## )/m)?.[1] || '';
137
+ sections.push(title, summary, motivation);
138
+ } else {
139
+ // Generic — use first 2000 chars
140
+ sections.push(title, body.slice(0, 2000));
141
+ }
142
+
143
+ return sections.filter(Boolean).join('\n').slice(0, 3000);
144
+ }
145
+
146
+ /**
147
+ * Check a new intake signal for potential conflicts with existing artifacts.
148
+ *
149
+ * @param {object} packet - Intake packet with triage, excerpt
150
+ * @param {object} [opts]
151
+ * @param {string} [opts.rootDir]
152
+ * @param {number} [opts.overlapThreshold=0.6]
153
+ * @param {number} [opts.contradictionThreshold=0.7]
154
+ * @returns {Promise<Array<{ type: 'overlap'|'contradiction', severity: string, artifact: string, similarity: number, summary: string }>>}
155
+ */
156
+ export async function detectConflicts(packet, { rootDir, overlapThreshold = OVERLAP_THRESHOLD, contradictionThreshold = CONTRADICTION_THRESHOLD } = {}) {
157
+ const text = extractTextFromPacket(packet);
158
+ if (!text) return [];
159
+
160
+ const signalVec = await embed(text);
161
+ if (!signalVec) return [];
162
+
163
+ const index = await buildArtifactIndex({ rootDir });
164
+ const conflicts = [];
165
+
166
+ for (const entry of index) {
167
+ if (!entry.embedding) continue;
168
+ const entryVec = new Float32Array(entry.embedding);
169
+ const sim = cosineSimilarity(signalVec, entryVec);
170
+
171
+ if (sim >= overlapThreshold) {
172
+ // Determine if this is overlap or potential contradiction
173
+ const isAdr = entry.type === 'adr';
174
+ const isContradictory = isAdr && sim >= contradictionThreshold;
175
+
176
+ conflicts.push({
177
+ type: isContradictory ? 'contradiction' : 'overlap',
178
+ severity: sim >= 0.8 ? 'high' : sim >= 0.7 ? 'medium' : 'low',
179
+ artifact: entry.path,
180
+ artifactType: entry.type,
181
+ title: entry.title,
182
+ status: entry.status,
183
+ similarity: Number(sim.toFixed(3)),
184
+ summary: isContradictory
185
+ ? `Potential contradiction with ${entry.type.toUpperCase()}-${entry.title} (similarity: ${sim.toFixed(2)})`
186
+ : `Overlaps with ${entry.type.toUpperCase()}-${entry.title} (similarity: ${sim.toFixed(2)})`,
187
+ });
188
+ }
189
+ }
190
+
191
+ return conflicts.sort((a, b) => b.similarity - a.similarity);
192
+ }
193
+
194
+ /**
195
+ * Scan all pending intake items for inter-item conflicts
196
+ * (two items addressing same topic from different angles).
197
+ *
198
+ * @param {Array<object>} pendingPackets
199
+ * @param {object} [opts]
200
+ * @param {number} [opts.threshold=0.7]
201
+ * @returns {Promise<Array<{ itemA: string, itemB: string, similarity: number, summary: string }>>}
202
+ */
203
+ export async function detectInterItemConflicts(pendingPackets, { threshold = 0.7 } = {}) {
204
+ if (pendingPackets.length < 2) return [];
205
+
206
+ const items = [];
207
+ for (const packet of pendingPackets) {
208
+ const text = extractTextFromPacket(packet);
209
+ if (!text) continue;
210
+ const vector = await embed(text);
211
+ if (vector) {
212
+ items.push({ id: packet.id, text, embedding: vector });
213
+ }
214
+ }
215
+
216
+ const conflicts = [];
217
+ for (let i = 1; i < items.length; i++) {
218
+ for (let j = 0; j < i; j++) {
219
+ const sim = cosineSimilarity(items[i].embedding, items[j].embedding);
220
+ if (sim >= threshold) {
221
+ conflicts.push({
222
+ itemA: items[i].id,
223
+ itemB: items[j].id,
224
+ similarity: Number(sim.toFixed(3)),
225
+ summary: `Intake items conflict: ${items[i].id.slice(0, 20)} vs ${items[j].id.slice(0, 20)} (${(sim * 100).toFixed(0)}% similar)`,
226
+ });
227
+ }
228
+ }
229
+ }
230
+
231
+ return conflicts.sort((a, b) => b.similarity - a.similarity);
232
+ }
233
+
234
+ /**
235
+ * Reset the cached artifact index (call when artifacts change).
236
+ */
237
+ export function resetArtifactIndex() {
238
+ _artifactIndex = null;
239
+ if (existsSync(INDEX_FILE)) {
240
+ const { unlinkSync } = require('node:fs');
241
+ try { unlinkSync(INDEX_FILE); } catch { /* ignore */ }
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Get artifact index stats.
247
+ *
248
+ * @param {object} [opts]
249
+ * @param {string} [opts.rootDir]
250
+ * @returns {Promise<{ total: number, byType: object, cached: boolean }>}
251
+ */
252
+ export async function artifactIndexStats({ rootDir } = {}) {
253
+ const index = await buildArtifactIndex({ rootDir });
254
+ const byType = {};
255
+ for (const entry of index) {
256
+ byType[entry.type] = (byType[entry.type] || 0) + 1;
257
+ }
258
+ return {
259
+ total: index.length,
260
+ byType,
261
+ indexed: index.filter(e => e.embedding).length,
262
+ cached: _artifactIndex !== null,
263
+ };
264
+ }