@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,286 @@
1
+ /**
2
+ * lib/storage/vector-client.mjs — Unified vector storage client for pgvector.
3
+ *
4
+ * Hides whether we're using local Postgres, managed Postgres, or (future) external vector DB.
5
+ * All callers use: store(id, embedding, metadata) and search(queryEmbedding, filters).
6
+ *
7
+ * Works with any Postgres that has the pgvector extension installed.
8
+ * Connection string (DATABASE_URL) determines the target.
9
+ *
10
+ * Dimensionality is resolved from the embedding engine at construction time
11
+ * via `getEmbeddingModelInfo()`. The legacy `PGVECTOR_DIMENSIONS = 384` constant
12
+ * is retained as a fallback only when the engine cannot be reached. When
13
+ * `assertSchemaDimensions()` runs, the actual `embedding` column declared
14
+ * dimension is read from `pg_attribute` and compared against the engine; a
15
+ * mismatch surfaces a precise error so the operator can either rerun a
16
+ * compatible migration or change `CONSTRUCT_EMBEDDING_MODEL`.
17
+ *
18
+ * NOTE: `postgres` package is imported lazily to avoid ERR_MODULE_NOT_FOUND
19
+ * when this module is loaded in environments where `postgres` is not installed.
20
+ */
21
+
22
+ import { getEmbeddingModelInfo } from './embeddings-engine.mjs';
23
+
24
+ const DEFAULT_POOL = { max: 10, idle_timeout: 20, connect_timeout: 10 };
25
+ const FALLBACK_DIMENSIONS = 384;
26
+
27
+ export const PGVECTOR_DIMENSIONS = FALLBACK_DIMENSIONS;
28
+
29
+ export function floatArrayToPgVector(arr) {
30
+ if (!arr) return null;
31
+ const values = arr instanceof Float32Array ? Array.from(arr) : arr;
32
+ return `[${values.join(',')}]`;
33
+ }
34
+
35
+ export function assertPgvectorDimensions(embedding, { expected = FALLBACK_DIMENSIONS, label = 'embedding' } = {}) {
36
+ const length = embedding?.length;
37
+ if (length !== expected) {
38
+ throw new Error(`${label} has ${length ?? 0} dimensions; pgvector schema expects ${expected}. Configure a ${expected}d embedding model or run a compatible migration before indexing.`);
39
+ }
40
+ }
41
+
42
+ export class VectorClient {
43
+ constructor({ databaseUrl, env } = {}) {
44
+ this.url = databaseUrl || process.env.DATABASE_URL || null;
45
+ this.env = env || process.env;
46
+ this._sql = null;
47
+ this._pgModule = null;
48
+ this._engineDim = null;
49
+ }
50
+
51
+ /**
52
+ * Lazily resolve the active embedding engine's dimensionality. Cached for
53
+ * the life of the client. If engine resolution fails, falls back to
54
+ * FALLBACK_DIMENSIONS (384) and the caller surfaces the dim-mismatch error
55
+ * at insert/search time.
56
+ */
57
+ async getEngineDimensions() {
58
+ if (this._engineDim != null) return this._engineDim;
59
+ try {
60
+ const info = await getEmbeddingModelInfo({ env: this.env });
61
+ this._engineDim = info.dimensions;
62
+ } catch {
63
+ this._engineDim = FALLBACK_DIMENSIONS;
64
+ }
65
+ return this._engineDim;
66
+ }
67
+
68
+ /**
69
+ * Read the actual declared dimensionality of `construct_embeddings.embedding`
70
+ * from `pg_attribute` and compare against the engine. Throws a precise
71
+ * error on mismatch. Returns the matched dimension on success.
72
+ */
73
+ async assertSchemaDimensions() {
74
+ const sql = await this._getSql();
75
+ if (!sql) throw new Error('No DATABASE_URL configured; cannot verify pgvector schema');
76
+ const engineDim = await this.getEngineDimensions();
77
+
78
+ const rows = await sql`
79
+ SELECT format_type(atttypid, atttypmod) AS pg_type
80
+ FROM pg_attribute
81
+ WHERE attrelid = 'construct_embeddings'::regclass
82
+ AND attname = 'embedding'
83
+ AND NOT attisdropped
84
+ `;
85
+ const declared = rows?.[0]?.pg_type || '';
86
+ const match = declared.match(/vector\((\d+)\)/);
87
+ if (!match) {
88
+ throw new Error(
89
+ `construct_embeddings.embedding column has unexpected type '${declared}'. ` +
90
+ `Recreate the column with 'ALTER TABLE construct_embeddings ALTER COLUMN embedding TYPE vector(${engineDim})' ` +
91
+ `(truncate first; vectors do not survive a dim change).`
92
+ );
93
+ }
94
+ const schemaDim = Number(match[1]);
95
+ if (schemaDim !== engineDim) {
96
+ throw new Error(
97
+ `pgvector dimension mismatch: schema declares vector(${schemaDim}) but engine model produces ${engineDim}d. ` +
98
+ `Either set CONSTRUCT_EMBEDDING_MODEL to a ${schemaDim}d model, or recreate the column with ` +
99
+ `'ALTER TABLE construct_embeddings ALTER COLUMN embedding TYPE vector(${engineDim})' (truncate first).`
100
+ );
101
+ }
102
+ return schemaDim;
103
+ }
104
+
105
+ /**
106
+ * Lazily load the `postgres` module and create the connection.
107
+ */
108
+ async _getSql() {
109
+ if (!this.url) return null;
110
+ if (this._sql) return this._sql;
111
+
112
+ if (!this._pgModule) {
113
+ try {
114
+ const m = await import('postgres');
115
+ this._pgModule = m.default || m;
116
+ } catch (err) {
117
+ // Re-throw with a clearer message so callers understand why
118
+ throw new Error(
119
+ `Failed to load 'postgres' module: ${err.message}. ` +
120
+ `Ensure 'postgres' is installed (npm install postgres).`
121
+ );
122
+ }
123
+ }
124
+
125
+ this._sql = this._pgModule(this.url, DEFAULT_POOL);
126
+ return this._sql;
127
+ }
128
+
129
+ /**
130
+ * Check if the SQL backend is available and healthy.
131
+ */
132
+ async isHealthy() {
133
+ const sql = await this._getSql();
134
+ if (!sql) return false;
135
+ try {
136
+ const result = await sql`SELECT 1 AS health`;
137
+ return result?.[0]?.health === 1;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Check if pgvector extension is enabled.
145
+ */
146
+ async isPgvectorEnabled() {
147
+ const sql = await this._getSql();
148
+ if (!sql) return false;
149
+ try {
150
+ const result = await sql`
151
+ SELECT EXISTS (
152
+ SELECT 1 FROM pg_extension WHERE extname = 'vector'
153
+ ) AS enabled
154
+ `;
155
+ return result?.[0]?.enabled === true;
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Store an observation with its embedding.
163
+ */
164
+ async storeObservation({ id, project, role, category, summary, content, tags, confidence, source, embedding, gitSha }) {
165
+ const sql = await this._getSql();
166
+ if (!sql) return { mode: 'file', reason: 'no_sql' };
167
+ const expected = await this.getEngineDimensions();
168
+ assertPgvectorDimensions(embedding, { expected, label: 'observation embedding' });
169
+
170
+ const embeddingVec = floatArrayToPgVector(embedding);
171
+ await sql`
172
+ INSERT INTO construct_observations (id, project, role, category, summary, content, tags, confidence, source, git_sha, embedding)
173
+ VALUES (${id}, ${project}, ${role}, ${category}, ${summary}, ${content}, ${JSON.stringify(tags || [])}, ${confidence || 0.8}, ${source || null}, ${gitSha || null}, ${embeddingVec})
174
+ ON CONFLICT (id) DO UPDATE SET
175
+ summary = EXCLUDED.summary,
176
+ content = EXCLUDED.content,
177
+ tags = EXCLUDED.tags,
178
+ confidence = EXCLUDED.confidence,
179
+ source = EXCLUDED.source,
180
+ git_sha = EXCLUDED.git_sha,
181
+ embedding = EXCLUDED.embedding,
182
+ updated_at = now()
183
+ `;
184
+ return { mode: 'sql', id };
185
+ }
186
+
187
+ /**
188
+ * Search observations by vector similarity.
189
+ */
190
+ async searchObservations({ project, queryEmbedding, limit = 10, minSimilarity = 0.3, role, category }) {
191
+ const sql = await this._getSql();
192
+ if (!sql) return [];
193
+ const expected = await this.getEngineDimensions();
194
+ assertPgvectorDimensions(queryEmbedding, { expected, label: 'observation query embedding' });
195
+
196
+ const queryVec = floatArrayToPgVector(queryEmbedding);
197
+ const conditions = [sql`project = ${project}`];
198
+ if (role) conditions.push(sql`role = ${role}`);
199
+ if (category) conditions.push(sql`category = ${category}`);
200
+ conditions.push(sql`1 - (embedding <=> ${queryVec}) > ${minSimilarity}`);
201
+
202
+ const results = await sql`
203
+ SELECT
204
+ id, role, category, summary, content, tags, confidence, source, git_sha, created_at,
205
+ 1 - (embedding <=> ${queryVec}) AS similarity
206
+ FROM construct_observations
207
+ WHERE ${sql.join(conditions, ' AND ')}
208
+ ORDER BY embedding <=> ${queryVec}
209
+ LIMIT ${limit}
210
+ `;
211
+ return results;
212
+ }
213
+
214
+ /**
215
+ * Store a document with its embedding.
216
+ */
217
+ async storeDocument({ id, project, kind, title, summary, body, sourcePath, tags, contentHash, embedding, model }) {
218
+ const sql = await this._getSql();
219
+ if (!sql) return { mode: 'file', reason: 'no_sql' };
220
+ const expected = await this.getEngineDimensions();
221
+ assertPgvectorDimensions(embedding, { expected, label: 'document embedding' });
222
+ if (!model) {
223
+ const info = await getEmbeddingModelInfo({ env: this.env });
224
+ model = info.model;
225
+ }
226
+
227
+ const embeddingVec = floatArrayToPgVector(embedding);
228
+ await sql`
229
+ INSERT INTO construct_documents (id, project, kind, title, summary, body, source_path, tags, content_hash)
230
+ VALUES (${id}, ${project}, ${kind}, ${title}, ${summary || ''}, ${body}, ${sourcePath || ''}, ${JSON.stringify(tags || [])}, ${contentHash || ''})
231
+ ON CONFLICT (id) DO UPDATE SET
232
+ title = EXCLUDED.title,
233
+ summary = EXCLUDED.summary,
234
+ body = EXCLUDED.body,
235
+ source_path = EXCLUDED.source_path,
236
+ tags = EXCLUDED.tags,
237
+ content_hash = EXCLUDED.content_hash,
238
+ updated_at = now()
239
+ `;
240
+
241
+ await sql`
242
+ INSERT INTO construct_embeddings (document_id, model, embedding, content_hash)
243
+ VALUES (${id}, ${model}, ${embeddingVec}, ${contentHash || ''})
244
+ ON CONFLICT (document_id) DO UPDATE SET
245
+ model = EXCLUDED.model,
246
+ embedding = EXCLUDED.embedding,
247
+ content_hash = EXCLUDED.content_hash,
248
+ updated_at = now()
249
+ `;
250
+ return { mode: 'sql', id };
251
+ }
252
+
253
+ /**
254
+ * Search documents by vector similarity.
255
+ */
256
+ async searchDocuments({ project, queryEmbedding, limit = 10, minSimilarity = 0.3 }) {
257
+ const sql = await this._getSql();
258
+ if (!sql) return [];
259
+ const expected = await this.getEngineDimensions();
260
+ assertPgvectorDimensions(queryEmbedding, { expected, label: 'document query embedding' });
261
+
262
+ const queryVec = floatArrayToPgVector(queryEmbedding);
263
+ const results = await sql`
264
+ SELECT
265
+ d.id, d.title, d.summary, d.body, d.source_path, d.tags, d.kind,
266
+ 1 - (e.embedding <=> ${queryVec}) AS similarity
267
+ FROM construct_documents d
268
+ JOIN construct_embeddings e ON d.id = e.document_id
269
+ WHERE d.project = ${project}
270
+ AND 1 - (e.embedding <=> ${queryVec}) > ${minSimilarity}
271
+ ORDER BY e.embedding <=> ${queryVec}
272
+ LIMIT ${limit}
273
+ `;
274
+ return results;
275
+ }
276
+
277
+ /**
278
+ * Close the connection pool.
279
+ */
280
+ async close() {
281
+ if (this._sql) {
282
+ await this._sql.end();
283
+ this._sql = null;
284
+ }
285
+ }
286
+ }
@@ -1,7 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * lib/storage/vector-store.mjs — derived semantic retrieval facade.
3
+ * lib/storage/vector-store.mjs — Local JSON vector index facade.
4
+ *
5
+ * Stores vector records on disk for fallback retrieval when no SQL backend is
6
+ * available. The model field is metadata supplied by the caller — this module
7
+ * does not embed; it only persists what the engine produces.
8
+ *
9
+ * Search APIs accept a pre-computed query embedding so the embedding model is
10
+ * the caller's choice and there is no implicit dependency on a specific model
11
+ * inside this module.
4
12
  */
13
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
+ import { dirname, resolve } from 'node:path';
15
+ import { scoreByEmbedding, rankByBm25 } from './embeddings.mjs';
16
+ import { embedSync } from './embeddings-legacy.mjs';
5
17
 
6
18
  export function vectorStoreMode(env = process.env) {
7
19
  if (env.CONSTRUCT_VECTOR_URL) return 'remote';
@@ -22,38 +34,67 @@ export function describeVectorStore(env = process.env) {
22
34
  };
23
35
  }
24
36
 
25
- function normalizeTerms(input) {
26
- return String(input || '')
27
- .toLowerCase()
28
- .split(/[^a-z0-9]+/)
29
- .filter((term) => term.length >= 3);
30
- }
37
+ export function readLocalVectorIndex(indexPath) {
38
+ const resolvedPath = resolve(String(indexPath || ''));
39
+ if (!resolvedPath || !existsSync(resolvedPath)) {
40
+ return { version: 1, model: null, updatedAt: null, records: [] };
41
+ }
31
42
 
32
- export function scoreSemanticText(text, query) {
33
- const terms = normalizeTerms(query);
34
- if (terms.length === 0) return 0;
35
- const haystack = String(text || '').toLowerCase();
36
- let score = 0;
37
- for (const term of terms) {
38
- if (haystack.includes(term)) score += 1;
43
+ try {
44
+ const parsed = JSON.parse(readFileSync(resolvedPath, 'utf8'));
45
+ return {
46
+ version: parsed.version ?? 1,
47
+ model: parsed.model || null,
48
+ updatedAt: parsed.updatedAt || null,
49
+ records: Array.isArray(parsed.records) ? parsed.records : [],
50
+ };
51
+ } catch {
52
+ return { version: 1, model: null, updatedAt: null, records: [] };
39
53
  }
40
- return score;
54
+ }
55
+
56
+ export function writeLocalVectorIndex(indexPath, records = [], { model } = {}) {
57
+ const resolvedPath = resolve(String(indexPath || ''));
58
+ if (!resolvedPath) throw new Error('Missing local vector index path');
59
+ if (!model) throw new Error('writeLocalVectorIndex requires an explicit model id (caller must supply)');
60
+ mkdirSync(dirname(resolvedPath), { recursive: true });
61
+ const payload = {
62
+ version: 1,
63
+ model,
64
+ updatedAt: new Date().toISOString(),
65
+ records,
66
+ };
67
+ writeFileSync(resolvedPath, `${JSON.stringify(payload, null, 2)}\n`);
68
+ return payload;
69
+ }
70
+
71
+ /**
72
+ * Search the local vector index. Accepts either a query string (which is
73
+ * embedded via the legacy hashing adapter inline — fast, deterministic, no
74
+ * engine init) or a pre-computed query embedding for engine-aligned callers.
75
+ *
76
+ * The hashing path matches what `addObservation` and `syncFileStateToSql`
77
+ * write to disk when no SQL backend is configured, so dimensions agree.
78
+ * Callers that store engine-dim vectors should pass an explicit Float32Array.
79
+ */
80
+ export function searchLocalVectorIndex(indexPath, query, { limit = 10 } = {}) {
81
+ const index = readLocalVectorIndex(indexPath);
82
+ const queryEmbedding = typeof query === 'string'
83
+ ? embedSync(query)
84
+ : (query instanceof Float32Array ? Array.from(query) : (query || []));
85
+ return scoreByEmbedding(index.records, queryEmbedding, { limit });
41
86
  }
42
87
 
43
88
  export function vectorSearchLocal(records = [], query = '', { limit = 10 } = {}) {
44
- return records
45
- .map((record) => ({
46
- record,
47
- score: scoreSemanticText([
48
- record.title,
49
- record.summary,
50
- record.body,
51
- record.text,
52
- Array.isArray(record.tags) ? record.tags.join(' ') : '',
53
- ].filter(Boolean).join('\n'), query),
54
- }))
55
- .filter(({ score }) => score > 0)
56
- .sort((a, b) => b.score - a.score || String(a.record.id || '').localeCompare(String(b.record.id || '')))
57
- .slice(0, limit)
58
- .map(({ record, score }) => ({ ...record, score }));
89
+ const bm25Docs = records.map((record) => ({
90
+ ...record,
91
+ text: [
92
+ record.title,
93
+ record.summary,
94
+ record.body,
95
+ record.text,
96
+ Array.isArray(record.tags) ? record.tags.join(' ') : '',
97
+ ].filter(Boolean).join('\n'),
98
+ }));
99
+ return rankByBm25(bm25Docs, query, { limit });
59
100
  }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * lib/task-graph/generate.mjs — derive a task graph from an R&D triage packet.
3
+ *
4
+ * The triage's recommendedChain is the workflow template: one node per
5
+ * persona in the chain, with depends_on edges linking each node to its
6
+ * predecessor. Node types align with rdStage (implementation, design,
7
+ * evaluation, …). Acceptance criteria are seeded from the triage stage;
8
+ * verification requirements ride alongside the workflow contract.
9
+ */
10
+
11
+ let counter = 0;
12
+ function uniqueSuffix() {
13
+ counter = (counter + 1) % 1000;
14
+ return String(counter).padStart(3, '0');
15
+ }
16
+
17
+ function timestamp() {
18
+ return `${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 23)}-${uniqueSuffix()}`;
19
+ }
20
+
21
+ const NODE_TYPE_BY_STAGE = {
22
+ signal: 'framing',
23
+ framing: 'framing',
24
+ hypothesis: 'hypothesis',
25
+ research: 'research',
26
+ artifact: 'design',
27
+ design: 'design',
28
+ implementation: 'implementation',
29
+ evaluation: 'evaluation',
30
+ release: 'release',
31
+ operations: 'runbook',
32
+ unknown: 'framing',
33
+ };
34
+
35
+ const OWNER_NODE_TYPE_OVERRIDE = {
36
+ qa: 'verification',
37
+ reviewer: 'review',
38
+ evaluator: 'evaluation',
39
+ 'trace-reviewer': 'evaluation',
40
+ debugger: 'diagnosis',
41
+ 'docs-keeper': 'release',
42
+ 'legal-compliance': 'compliance-review',
43
+ security: 'review',
44
+ };
45
+
46
+ const ACCEPTANCE_BY_STAGE = {
47
+ framing: ['problem statement is explicit', 'success metric named', 'scope boundaries documented'],
48
+ hypothesis: ['hypothesis is falsifiable', 'success / failure threshold named', 'evaluation method chosen'],
49
+ research: ['three or more sources cited', 'date coverage stated', 'claim → source mapping recorded'],
50
+ design: ['interface contract documented', 'tradeoffs called out', 'reversibility note attached'],
51
+ implementation: ['tests pass locally', 'lint clean', 'diff scoped to the change'],
52
+ evaluation: ['baseline metric recorded', 'failure cases enumerated', 'regression criterion explicit'],
53
+ release: ['changelog entry drafted', 'docs cross-checked', 'rollback path stated'],
54
+ operations: ['runbook step list complete', 'on-call notified', 'monitoring confirmed'],
55
+ unknown: ['define acceptance criteria for this work'],
56
+ };
57
+
58
+ const VERIFICATION_BY_STAGE = {
59
+ implementation: ['npm test', 'lint:comments'],
60
+ evaluation: ['construct evals retrieval'],
61
+ release: ['release:check'],
62
+ operations: ['service health probe'],
63
+ };
64
+
65
+ function pickNodeType({ owner, rdStage }) {
66
+ if (OWNER_NODE_TYPE_OVERRIDE[owner]) return OWNER_NODE_TYPE_OVERRIDE[owner];
67
+ return NODE_TYPE_BY_STAGE[rdStage] || 'framing';
68
+ }
69
+
70
+ function pickNodeTitle({ owner, recommendedAction, request }) {
71
+ const action = recommendedAction || 'work';
72
+ const shortRequest = (request || '').trim().slice(0, 80);
73
+ return shortRequest
74
+ ? `${owner}: ${action} — ${shortRequest}`
75
+ : `${owner}: ${action}`;
76
+ }
77
+
78
+ /**
79
+ * Produce a task graph from a triage packet.
80
+ *
81
+ * @param {object} opts
82
+ * @param {object} opts.triage — output of classifyRdIntake
83
+ * @param {string} [opts.project]
84
+ * @param {string} [opts.request] — original signal text, used for node titles
85
+ * @param {object} [opts.intake] — intake metadata, persisted on node 1 for traceability
86
+ * @returns {{ id, project, createdAt, triage, intake, nodes, edges, verificationRequirements }}
87
+ */
88
+ export function generateTaskGraphFromTriage({ triage, project = 'construct', request = '', intake = null } = {}) {
89
+ if (!triage) throw new Error('generateTaskGraphFromTriage: triage is required');
90
+ const chain = Array.isArray(triage.recommendedChain) && triage.recommendedChain.length > 0
91
+ ? triage.recommendedChain
92
+ : [triage.primaryOwner || 'orchestrator'];
93
+
94
+ const ts = timestamp();
95
+ const graphId = `task-graph-${ts}`;
96
+ const createdAt = new Date().toISOString();
97
+
98
+ const nodes = chain.map((owner, idx) => {
99
+ const nodeId = `${graphId}-${String(idx + 1).padStart(2, '0')}-${owner}`;
100
+ const dependsOn = idx === 0 ? [] : [`${graphId}-${String(idx).padStart(2, '0')}-${chain[idx - 1]}`];
101
+ return {
102
+ id: nodeId,
103
+ project,
104
+ title: pickNodeTitle({ owner, recommendedAction: triage.recommendedAction, request }),
105
+ type: pickNodeType({ owner, rdStage: triage.rdStage }),
106
+ owner,
107
+ status: 'pending',
108
+ dependsOn,
109
+ inputs: idx === 0 && intake ? [{ kind: 'intake', sourcePath: intake.sourcePath }] : [],
110
+ outputs: [],
111
+ acceptanceCriteria: ACCEPTANCE_BY_STAGE[triage.rdStage] || ACCEPTANCE_BY_STAGE.unknown,
112
+ evidence: [],
113
+ risk: triage.risk || 'low',
114
+ createdAt,
115
+ updatedAt: createdAt,
116
+ };
117
+ });
118
+
119
+ const edges = [];
120
+ for (let i = 1; i < nodes.length; i++) {
121
+ edges.push({ from: nodes[i - 1].id, to: nodes[i].id, type: 'handoff_to' });
122
+ edges.push({ from: nodes[i].id, to: nodes[i - 1].id, type: 'depends_on' });
123
+ }
124
+
125
+ return {
126
+ id: graphId,
127
+ project,
128
+ createdAt,
129
+ triage,
130
+ intake,
131
+ nodes,
132
+ edges,
133
+ verificationRequirements: VERIFICATION_BY_STAGE[triage.rdStage] || [],
134
+ };
135
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * lib/task-graph/schema.mjs — task graph node + edge schema and validation.
3
+ *
4
+ * A task graph is the deterministic plan-of-work derived from an R&D
5
+ * intake triage. Each node represents a unit of work owned by a persona;
6
+ * edges encode ordering (depends_on, blocks, handoff_to), validation
7
+ * (validates), and history (supersedes, derived_from).
8
+ *
9
+ * Node schema:
10
+ * {
11
+ * id, project, title, type, owner, status,
12
+ * dependsOn[], inputs[], outputs[], acceptanceCriteria[],
13
+ * evidence[], risk, createdAt, updatedAt
14
+ * }
15
+ *
16
+ * Status moves through: pending → claimed → in-progress → done | blocked |
17
+ * needs-input | skipped. Evidence is appended as each verification step
18
+ * produces output (see PR 8 worker sandbox).
19
+ */
20
+
21
+ export const EDGE_TYPES = ['depends_on', 'blocks', 'handoff_to', 'validates', 'supersedes', 'derived_from'];
22
+
23
+ export const NODE_STATUSES = ['pending', 'claimed', 'in-progress', 'done', 'blocked', 'needs-input', 'skipped'];
24
+
25
+ export const NODE_TYPES = [
26
+ 'diagnosis',
27
+ 'implementation',
28
+ 'verification',
29
+ 'review',
30
+ 'research',
31
+ 'framing',
32
+ 'design',
33
+ 'hypothesis',
34
+ 'evaluation',
35
+ 'release',
36
+ 'runbook',
37
+ 'compliance-review',
38
+ ];
39
+
40
+ export function isValidStatus(status) {
41
+ return NODE_STATUSES.includes(status);
42
+ }
43
+
44
+ export function isValidEdgeType(type) {
45
+ return EDGE_TYPES.includes(type);
46
+ }
47
+
48
+ const REQUIRED_NODE_FIELDS = ['id', 'project', 'title', 'type', 'owner', 'status'];
49
+
50
+ export function validateNode(node) {
51
+ const errors = [];
52
+ if (!node || typeof node !== 'object') {
53
+ errors.push('node must be an object');
54
+ return errors;
55
+ }
56
+ for (const field of REQUIRED_NODE_FIELDS) {
57
+ if (!node[field]) errors.push(`missing required field: ${field}`);
58
+ }
59
+ if (node.status && !isValidStatus(node.status)) errors.push(`invalid status: ${node.status}`);
60
+ if (node.dependsOn && !Array.isArray(node.dependsOn)) errors.push('dependsOn must be an array');
61
+ return errors;
62
+ }
63
+
64
+ export function validateGraph(graph) {
65
+ const errors = [];
66
+ if (!graph?.id) errors.push('graph.id is required');
67
+ if (!Array.isArray(graph?.nodes) || graph.nodes.length === 0) errors.push('graph.nodes must be a non-empty array');
68
+ const ids = new Set();
69
+ for (const node of graph?.nodes || []) {
70
+ const nodeErrors = validateNode(node);
71
+ for (const err of nodeErrors) errors.push(`node ${node?.id || '(unknown)'}: ${err}`);
72
+ if (ids.has(node?.id)) errors.push(`duplicate node id: ${node.id}`);
73
+ ids.add(node?.id);
74
+ }
75
+ for (const node of graph?.nodes || []) {
76
+ for (const dep of node.dependsOn || []) {
77
+ if (!ids.has(dep)) errors.push(`node ${node.id}: dependsOn references unknown node ${dep}`);
78
+ }
79
+ }
80
+ return errors;
81
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * lib/task-graph/store.mjs — task graph persistence (filesystem adapter).
3
+ *
4
+ * Stores generated graphs under `.cx/task-graphs/<graph-id>.json`. The
5
+ * shape mirrors the in-memory graph from generate.mjs end-to-end so a
6
+ * graph round-trips without lossy field handling. team / enterprise mode
7
+ * will add a Postgres adapter alongside this; both implement the same
8
+ * read / write surface.
9
+ */
10
+
11
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
12
+ import path from 'node:path';
13
+
14
+ const STORE_SUBDIR = '.cx/task-graphs';
15
+
16
+ export function storeDir(rootDir) {
17
+ return path.join(rootDir, STORE_SUBDIR);
18
+ }
19
+
20
+ export class FilesystemTaskGraphStore {
21
+ constructor(rootDir) {
22
+ if (!rootDir) throw new Error('FilesystemTaskGraphStore: rootDir is required');
23
+ this.rootDir = rootDir;
24
+ }
25
+
26
+ save(graph) {
27
+ if (!graph?.id) throw new Error('save: graph.id is required');
28
+ const dir = storeDir(this.rootDir);
29
+ mkdirSync(dir, { recursive: true });
30
+ const filePath = path.join(dir, `${graph.id}.json`);
31
+ writeFileSync(filePath, JSON.stringify(graph, null, 2) + '\n', 'utf8');
32
+ return { id: graph.id, filePath };
33
+ }
34
+
35
+ read(id) {
36
+ const filePath = path.join(storeDir(this.rootDir), `${id}.json`);
37
+ if (!existsSync(filePath)) return null;
38
+ return JSON.parse(readFileSync(filePath, 'utf8'));
39
+ }
40
+
41
+ list() {
42
+ const dir = storeDir(this.rootDir);
43
+ if (!existsSync(dir)) return [];
44
+ return readdirSync(dir)
45
+ .filter((name) => name.endsWith('.json'))
46
+ .map((name) => path.join(dir, name))
47
+ .map((filePath) => {
48
+ try {
49
+ return JSON.parse(readFileSync(filePath, 'utf8'));
50
+ } catch {
51
+ return null;
52
+ }
53
+ })
54
+ .filter(Boolean)
55
+ .sort((a, b) => (a.createdAt || '').localeCompare(b.createdAt || ''));
56
+ }
57
+
58
+ updateNodeStatus(graphId, nodeId, status, patch = {}) {
59
+ const graph = this.read(graphId);
60
+ if (!graph) throw new Error(`updateNodeStatus: graph ${graphId} not found`);
61
+ const node = graph.nodes.find((n) => n.id === nodeId);
62
+ if (!node) throw new Error(`updateNodeStatus: node ${nodeId} not found in ${graphId}`);
63
+ if (status !== undefined) node.status = status;
64
+ node.updatedAt = new Date().toISOString();
65
+ if (patch.addEvidence) {
66
+ node.evidence = [...(node.evidence || []), patch.addEvidence];
67
+ }
68
+ this.save(graph);
69
+ return node;
70
+ }
71
+ }