@geraldmaron/construct 1.0.0 → 1.0.2

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 +9 -6
  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 +1000 -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 +523 -36
  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,132 @@
1
+ /**
2
+ * lib/mcp/tools/document.mjs — Document-handling MCP tools: extract text, ingest, infer schema, list schema artifacts.
3
+ *
4
+ * All functions are async-safe; extractDocumentText and listSchemaArtifactsTool are synchronous.
5
+ * Depends on lib/document-extract.mjs, lib/document-ingest.mjs, lib/schema-infer.mjs, lib/schema-artifact.mjs.
6
+ */
7
+ import { existsSync } from 'node:fs';
8
+ import { resolve } from 'node:path';
9
+ import { extractDocumentText as extractLocalDocumentText } from '../../document-extract.mjs';
10
+ import { ingestDocuments } from '../../document-ingest.mjs';
11
+ import { inferDocumentSchema, inferUnifiedSchema } from '../../schema-infer.mjs';
12
+ import { writeSchemaArtifact, listSchemaArtifacts, SCHEMA_DIR } from '../../schema-artifact.mjs';
13
+
14
+ export function extractDocumentText(args) {
15
+ const filePath = resolve(String(args.file_path || ''));
16
+ const maxChars = Number.isFinite(Number(args.max_chars)) && Number(args.max_chars) > 0
17
+ ? Math.min(Number(args.max_chars), 200_000)
18
+ : 20_000;
19
+
20
+ if (!existsSync(filePath)) {
21
+ return { error: `File not found: ${filePath}` };
22
+ }
23
+
24
+ try {
25
+ const extracted = extractLocalDocumentText(filePath, { maxChars });
26
+ return {
27
+ file_path: extracted.filePath,
28
+ extension: extracted.extension,
29
+ extraction_method: extracted.extractionMethod,
30
+ text: extracted.text,
31
+ truncated: extracted.truncated,
32
+ characters: extracted.characters,
33
+ };
34
+ } catch (error) {
35
+ return {
36
+ error: `Failed to extract text from ${filePath}: ${error.message ?? String(error)}`,
37
+ };
38
+ }
39
+ }
40
+
41
+ export async function ingestDocument(args) {
42
+ const filePath = resolve(String(args.file_path || ''));
43
+ const outputPath = args.out_path ? resolve(String(args.out_path)) : null;
44
+ const outputDir = args.out_dir ? resolve(String(args.out_dir)) : null;
45
+ const target = typeof args.target === 'string' && args.target ? args.target : 'knowledge/internal';
46
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
47
+ const sync = Boolean(args.sync);
48
+
49
+ if (!existsSync(filePath)) {
50
+ return { error: `File not found: ${filePath}` };
51
+ }
52
+
53
+ try {
54
+ return await ingestDocuments([filePath], {
55
+ cwd,
56
+ outputPath,
57
+ outputDir,
58
+ target,
59
+ sync,
60
+ env: process.env,
61
+ });
62
+ } catch (error) {
63
+ return {
64
+ error: `Failed to ingest ${filePath}: ${error.message ?? String(error)}`,
65
+ };
66
+ }
67
+ }
68
+
69
+ export async function inferDocumentSchemaTool(args) {
70
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
71
+ const maxChars = Number.isFinite(Number(args.max_chars)) && Number(args.max_chars) > 0
72
+ ? Math.min(Number(args.max_chars), 200_000)
73
+ : 40_000;
74
+ const save = Boolean(args.save);
75
+
76
+ const rawPaths = Array.isArray(args.file_paths) && args.file_paths.length > 0
77
+ ? args.file_paths
78
+ : args.file_path
79
+ ? [args.file_path]
80
+ : [];
81
+
82
+ if (rawPaths.length === 0) {
83
+ return { error: 'file_path or file_paths is required' };
84
+ }
85
+
86
+ const filePaths = rawPaths.map((p) => resolve(String(p)));
87
+ const missingFiles = filePaths.filter((p) => !existsSync(p));
88
+ if (missingFiles.length > 0) {
89
+ return { error: `File(s) not found: ${missingFiles.join(', ')}` };
90
+ }
91
+
92
+ try {
93
+ let schema;
94
+ if (filePaths.length === 1) {
95
+ const sampleSize = Number.isFinite(Number(args.sample_size)) ? Number(args.sample_size) : 10;
96
+ const threshold = Number.isFinite(Number(args.threshold)) ? Number(args.threshold) : 0.5;
97
+ schema = await inferDocumentSchema(filePaths[0], { maxChars });
98
+ void sampleSize; void threshold;
99
+ } else {
100
+ const sampleSize = Number.isFinite(Number(args.sample_size)) ? Number(args.sample_size) : 10;
101
+ const threshold = Number.isFinite(Number(args.threshold)) ? Number(args.threshold) : 0.5;
102
+ schema = await inferUnifiedSchema(filePaths, { maxChars, sampleSize, threshold });
103
+ }
104
+
105
+ let artifact = null;
106
+ if (save) {
107
+ artifact = writeSchemaArtifact(schema, { cwd });
108
+ }
109
+
110
+ return { ...schema, ...(artifact ? { artifact_path: artifact.relativePath } : {}) };
111
+ } catch (error) {
112
+ return { error: `Schema inference failed: ${error.message ?? String(error)}` };
113
+ }
114
+ }
115
+
116
+ export function listSchemaArtifactsTool(args) {
117
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
118
+ try {
119
+ const artifacts = listSchemaArtifacts({ cwd });
120
+ return {
121
+ count: artifacts.length,
122
+ schema_dir: SCHEMA_DIR,
123
+ artifacts: artifacts.map((a) => ({
124
+ path: a.relativePath,
125
+ modified: a.stat.mtime.toISOString(),
126
+ size_bytes: a.stat.size,
127
+ })),
128
+ };
129
+ } catch (error) {
130
+ return { error: `Failed to list schema artifacts: ${error.message ?? String(error)}` };
131
+ }
132
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * lib/mcp/tools/memory.mjs — Memory and session MCP tools: search, add observations, create entities, recent, session CRUD.
3
+ *
4
+ * Exposes memorySearch, memoryAddObservations, memoryCreateEntities, memoryRecent,
5
+ * sessionList, sessionLoad, sessionSearch, sessionSave.
6
+ * memorySearch is async (hybrid store path); all others are synchronous.
7
+ */
8
+ import { resolve } from 'node:path';
9
+ import { listSessions, loadSession, searchSessions, updateSession, buildResumeContext } from '../../session-store.mjs';
10
+ import { addObservation, searchObservations, listObservations } from '../../observation-store.mjs';
11
+ import { createEntity } from '../../entity-store.mjs';
12
+ import { buildHybridSearchResultsAsync } from '../../storage/hybrid-query.mjs';
13
+
14
+ export async function memorySearch(args) {
15
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
16
+ const project = args.project || null;
17
+ const limit = args.limit || 10;
18
+ const query = String(args.query || '');
19
+
20
+ const obsResults = searchObservations(cwd, query, {
21
+ role: args.role || null,
22
+ category: args.category || null,
23
+ project,
24
+ limit,
25
+ });
26
+
27
+ let hybridResults = [];
28
+ try {
29
+ const hybrid = await buildHybridSearchResultsAsync(cwd, query, { limit, env: process.env });
30
+ hybridResults = (hybrid.results || []).map((r) => ({
31
+ ...r,
32
+ source: 'hybrid',
33
+ }));
34
+ } catch { /* best effort — hybrid store is optional */ }
35
+
36
+ if (obsResults.length > 0 || hybridResults.length > 0) {
37
+ return {
38
+ observations: obsResults,
39
+ documents: hybridResults,
40
+ total: obsResults.length + hybridResults.length,
41
+ };
42
+ }
43
+
44
+ const recent = listObservations(cwd, { project, limit: 5 });
45
+ if (recent.length > 0) {
46
+ return { observations: recent, documents: [], total: recent.length, note: 'No semantic matches — showing recent observations.' };
47
+ }
48
+
49
+ return {
50
+ observations: [],
51
+ documents: hybridResults,
52
+ total: hybridResults.length,
53
+ note: hybridResults.length === 0
54
+ ? 'No observations recorded yet. Use memory_add_observations to capture patterns and insights.'
55
+ : undefined,
56
+ };
57
+ }
58
+
59
+ export function memoryAddObservations(args) {
60
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
61
+ const project = args.project || cwd.split('/').pop() || 'unknown';
62
+ const observations = (args.observations || []).slice(0, 10);
63
+ const created = [];
64
+ for (const obs of observations) {
65
+ const record = addObservation(cwd, {
66
+ role: obs.role || 'unknown',
67
+ category: obs.category || 'insight',
68
+ summary: obs.summary || '',
69
+ content: obs.content || obs.summary || '',
70
+ tags: obs.tags || [],
71
+ project,
72
+ confidence: obs.confidence ?? 0.8,
73
+ source: obs.source || null,
74
+ });
75
+ if (record) created.push({ id: record.id, summary: record.summary });
76
+ }
77
+ return { created, count: created.length };
78
+ }
79
+
80
+ export function memoryCreateEntities(args) {
81
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
82
+ const project = args.project || cwd.split('/').pop() || 'unknown';
83
+ const entities = (args.entities || []).slice(0, 10);
84
+ const created = [];
85
+ for (const ent of entities) {
86
+ const record = createEntity(cwd, {
87
+ name: ent.name || '',
88
+ type: ent.type || 'concept',
89
+ summary: ent.summary || '',
90
+ project,
91
+ observationIds: ent.observation_ids || [],
92
+ });
93
+ if (record) created.push({ name: record.name, type: record.type });
94
+ }
95
+ return { created, count: created.length };
96
+ }
97
+
98
+ export function memoryRecent(args) {
99
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
100
+ const project = args.project || cwd.split('/').pop() || null;
101
+ const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 50);
102
+ const raw = listObservations(cwd, { project, limit: limit * 3 });
103
+ const seen = new Set();
104
+ const dedup = [];
105
+ for (const o of raw) {
106
+ const key = `${o.role}::${o.summary}`;
107
+ if (seen.has(key)) continue;
108
+ seen.add(key);
109
+ dedup.push(o);
110
+ if (dedup.length >= limit) break;
111
+ }
112
+ return { project, count: dedup.length, observations: dedup };
113
+ }
114
+
115
+ export function sessionList(args) {
116
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
117
+ return listSessions(cwd, { status: args.status || null, limit: args.limit || 20 });
118
+ }
119
+
120
+ export function sessionLoad(args) {
121
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
122
+ const session = loadSession(cwd, String(args.session_id));
123
+ if (!session) return { error: 'Session not found: ' + args.session_id };
124
+ return { ...session, resumeContext: buildResumeContext(session) };
125
+ }
126
+
127
+ export function sessionSearch(args) {
128
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
129
+ return searchSessions(cwd, String(args.query || ''));
130
+ }
131
+
132
+ export function sessionSave(args) {
133
+ const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
134
+ const updates = {};
135
+ if (args.summary) updates.summary = args.summary;
136
+ if (args.decisions) updates.decisions = args.decisions;
137
+ if (args.files_changed) updates.filesChanged = args.files_changed;
138
+ if (args.open_questions) updates.openQuestions = args.open_questions;
139
+ if (args.task_snapshot) updates.taskSnapshot = args.task_snapshot;
140
+ if (args.status) updates.status = args.status;
141
+ const updated = updateSession(cwd, String(args.session_id), updates);
142
+ if (!updated) return { error: 'Session not found: ' + args.session_id };
143
+ return updated;
144
+ }
145
+
146
+ /**
147
+ * Rovo Search — cross-system semantic search via Atlassian Rovo.
148
+ * Returns excerpts with source attribution. Does NOT write to observation store.
149
+ * Used for broad queries across all accessible sources (Jira, Confluence, GitHub).
150
+ */
151
+ export async function rovoSearch(args) {
152
+ const query = String(args.query || '').trim();
153
+ if (!query) return { error: 'query is required' };
154
+
155
+ const topK = Math.min(Math.max(Number(args.top_k) || 10, 1), 50);
156
+ const sources = args.sources ? String(args.sources).split(',').map(s => s.trim()).filter(Boolean) : null;
157
+
158
+ try {
159
+ const { search } = await import('@atlassian/rovo-search');
160
+ const results = await search(query, { limit: topK, sources });
161
+ return {
162
+ query,
163
+ total: results.length,
164
+ results: results.slice(0, topK).map(r => ({
165
+ title: r.title,
166
+ excerpt: r.excerpt,
167
+ url: r.url,
168
+ source: r.source,
169
+ type: r.type,
170
+ updated: r.updated,
171
+ })),
172
+ };
173
+ } catch {
174
+ // Fallback: use the Atlassian MCP search tool if available
175
+ try {
176
+ const { default: fetch } = await import('node-fetch');
177
+ const cloudId = process.env.ATLASSIAN_CLOUD_ID || 'hashicorp.atlassian.net';
178
+ const res = await fetch(`https://api.atlassian.com/rovo/v1/search`, {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'application/json',
182
+ 'Authorization': `Bearer ${process.env.ATLASSIAN_API_TOKEN}`,
183
+ },
184
+ body: JSON.stringify({ query, limit: topK }),
185
+ });
186
+ if (!res.ok) throw new Error(`Rovo search failed: ${res.status}`);
187
+ const data = await res.json();
188
+ return {
189
+ query,
190
+ total: data.results?.length || 0,
191
+ results: (data.results || []).slice(0, topK).map(r => ({
192
+ title: r.title,
193
+ excerpt: r.excerpt,
194
+ url: r.url,
195
+ source: r.source,
196
+ type: r.type,
197
+ updated: r.updated,
198
+ })),
199
+ };
200
+ } catch (err) {
201
+ return {
202
+ error: `Rovo search unavailable: ${err.message}. Ensure Atlassian API credentials are configured.`,
203
+ query,
204
+ results: [],
205
+ total: 0,
206
+ };
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,349 @@
1
+ /**
2
+ * lib/mcp/tools/project.mjs — Project-level MCP tools: agent health, diff summary, file scan, and project context.
3
+ *
4
+ * Exposes agentHealth, summarizeDiff, scanFile, and projectContext.
5
+ * All tools are synchronous except where noted. Requires ROOT_DIR and homedir from the caller.
6
+ */
7
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
8
+ import { join, resolve } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+ import { execSync } from 'node:child_process';
11
+ import { loadConstructEnv } from '../../env-config.mjs';
12
+ import { inspectContextState } from '../../context-state.mjs';
13
+ import { loadWorkflow, summarizeWorkflow, inspectWorkflowHealth } from '../../workflow-state.mjs';
14
+ import { readCurrentModels, resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from '../../model-router.mjs';
15
+ import { buildPublicHealthSurface } from '../../status.mjs';
16
+
17
+ export function exec(cmd, cwd) {
18
+ return execSync(cmd, { stdio: 'pipe', timeout: 10000, cwd }).toString().trim();
19
+ }
20
+
21
+ export function readJSON(filePath) {
22
+ if (!existsSync(filePath)) return null;
23
+ try {
24
+ return JSON.parse(readFileSync(filePath, 'utf8'));
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ const SECRET_PATTERNS = [
31
+ { name: 'Anthropic API key', pattern: /ANTHROPIC_API_KEY\s*=\s*(sk-ant-[a-zA-Z0-9\-_]{20,})/i },
32
+ { name: 'OpenAI API key', pattern: /OPENAI_API_KEY\s*=\s*(sk-[a-zA-Z0-9]{40,})/i },
33
+ { name: 'OpenRouter key', pattern: /(sk-or-v1-[a-zA-Z0-9]{40,})/ },
34
+ { name: 'AWS access key', pattern: /(AKIA[0-9A-Z]{16})/ },
35
+ { name: 'Private key (PEM)', pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
36
+ { name: 'GitHub personal access token', pattern: /(ghp_[a-zA-Z0-9]{36})/ },
37
+ { name: 'GitHub Actions token', pattern: /(ghs_[a-zA-Z0-9]{36})/ },
38
+ { name: 'Database URL with credentials', pattern: /DATABASE_URL\s*=\s*(postgresql:\/\/[^@]+:[^@]+@)/i },
39
+ ];
40
+
41
+ const PLACEHOLDER_PATTERNS = [
42
+ /\.\.\./,
43
+ /YOUR_KEY/i,
44
+ /<[^>]+>/,
45
+ /^sk-\.\.\./,
46
+ /^pk-lf-\.\.\./,
47
+ /__[A-Z_]+__/,
48
+ ];
49
+
50
+ function isPlaceholder(value) {
51
+ return PLACEHOLDER_PATTERNS.some((p) => p.test(value));
52
+ }
53
+
54
+ function isBinary(buffer) {
55
+ const len = Math.min(buffer.length, 512);
56
+ for (let i = 0; i < len; i++) {
57
+ if (buffer[i] === 0) return true;
58
+ }
59
+ return false;
60
+ }
61
+
62
+ export function agentHealth(args) {
63
+ const reviewDir = join(homedir(), '.cx', 'performance-reviews');
64
+
65
+ if (!existsSync(reviewDir)) {
66
+ return { error: "No performance reviews found. Run 'construct review' to generate one." };
67
+ }
68
+
69
+ let files;
70
+ try {
71
+ files = readdirSync(reviewDir);
72
+ } catch {
73
+ return { error: "No performance reviews found. Run 'construct review' to generate one." };
74
+ }
75
+
76
+ const processed = files
77
+ .filter((f) => f.endsWith('.json') && !f.endsWith('-raw.json'))
78
+ .sort()
79
+ .reverse();
80
+
81
+ const raw = files
82
+ .filter((f) => f.endsWith('-raw.json'))
83
+ .sort()
84
+ .reverse();
85
+
86
+ const candidates = processed.length > 0 ? processed : raw;
87
+
88
+ if (candidates.length === 0) {
89
+ return { error: "No performance reviews found. Run 'construct review' to generate one." };
90
+ }
91
+
92
+ let data;
93
+ try {
94
+ const content = readFileSync(join(reviewDir, candidates[0]), 'utf8');
95
+ data = JSON.parse(content);
96
+ } catch {
97
+ return { error: 'Failed to parse performance review file.' };
98
+ }
99
+
100
+ const reviewDate = data.reviewDate ?? data.generatedAt ?? candidates[0].replace(/\.json$/, '');
101
+ const period = data.period ?? data.windowDays ?? null;
102
+
103
+ let agents = [];
104
+
105
+ if (Array.isArray(data.agents)) {
106
+ agents = data.agents;
107
+ } else if (data.agentReviews) {
108
+ agents = Object.entries(data.agentReviews).map(([name, review]) => ({
109
+ name,
110
+ status: review.status ?? review.health ?? 'unknown',
111
+ avgScore: review.avgScore ?? review.averageScore ?? null,
112
+ trend: review.trend ?? null,
113
+ failureRate: review.failureRate ?? null,
114
+ invocations: review.invocations ?? review.totalCalls ?? null,
115
+ }));
116
+ } else if (data.summary && Array.isArray(data.summary.agents)) {
117
+ agents = data.summary.agents;
118
+ }
119
+
120
+ if (args.agent_name) {
121
+ const target = args.agent_name.toLowerCase();
122
+ agents = agents.filter((a) => (a.name ?? '').toLowerCase().includes(target));
123
+ }
124
+
125
+ const normalized = agents.map((a) => ({
126
+ name: a.name ?? 'unknown',
127
+ status: a.status ?? 'unknown',
128
+ avgScore: a.avgScore ?? a.averageScore ?? null,
129
+ trend: a.trend ?? null,
130
+ failureRate: a.failureRate ?? null,
131
+ invocations: a.invocations ?? a.totalCalls ?? null,
132
+ }));
133
+
134
+ return { reviewDate, period, agents: normalized };
135
+ }
136
+
137
+ export function summarizeDiff(args) {
138
+ const baseRef = args.base_ref ?? 'HEAD~1';
139
+ const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
140
+
141
+ let stat, nameStatus;
142
+ try {
143
+ stat = exec(`git diff --stat ${baseRef}`, cwd);
144
+ nameStatus = exec(`git diff --name-status ${baseRef}`, cwd);
145
+ } catch {
146
+ return { error: 'Not a git repository or git not available' };
147
+ }
148
+
149
+ const changes = nameStatus
150
+ .split('\n')
151
+ .filter(Boolean)
152
+ .map((line) => {
153
+ const [status, ...parts] = line.split('\t');
154
+ return { status: status.trim(), file: parts.join('\t') };
155
+ });
156
+
157
+ const statLines = stat.split('\n');
158
+ const summaryLine = statLines[statLines.length - 1] ?? '';
159
+ const filesMatch = summaryLine.match(/(\d+) files? changed/);
160
+ const insertionsMatch = summaryLine.match(/(\d+) insertions?\(\+\)/);
161
+ const deletionsMatch = summaryLine.match(/(\d+) deletions?\(-\)/);
162
+
163
+ const filesChanged = filesMatch ? parseInt(filesMatch[1], 10) : changes.length;
164
+ const insertions = insertionsMatch ? parseInt(insertionsMatch[1], 10) : 0;
165
+ const deletions = deletionsMatch ? parseInt(deletionsMatch[1], 10) : 0;
166
+
167
+ const added = changes.filter((c) => c.status === 'A').length;
168
+ const modified = changes.filter((c) => c.status === 'M').length;
169
+ const deleted = changes.filter((c) => c.status === 'D').length;
170
+
171
+ const parts = [];
172
+ if (modified > 0) parts.push(`${modified} file${modified > 1 ? 's' : ''} modified`);
173
+ if (added > 0) parts.push(`${added} file${added > 1 ? 's' : ''} added`);
174
+ if (deleted > 0) parts.push(`${deleted} file${deleted > 1 ? 's' : ''} deleted`);
175
+
176
+ const summary =
177
+ parts.length > 0
178
+ ? `${parts.join(', ')} (+${insertions}/-${deletions} lines) vs ${baseRef}.`
179
+ : `No changes vs ${baseRef}.`;
180
+
181
+ return { base_ref: baseRef, files_changed: filesChanged, insertions, deletions, changes, summary };
182
+ }
183
+
184
+ export function scanFile(args) {
185
+ const filePath = resolve(args.file_path);
186
+
187
+ let rawBuffer;
188
+ try {
189
+ rawBuffer = readFileSync(filePath);
190
+ } catch (err) {
191
+ return { error: `Cannot read file: ${err.message}` };
192
+ }
193
+
194
+ if (isBinary(rawBuffer)) {
195
+ return { file_path: filePath, secrets: [], quality_issues: [], clean: true };
196
+ }
197
+
198
+ const content = rawBuffer.toString('utf8');
199
+ const lines = content.split('\n');
200
+
201
+ const secrets = [];
202
+ for (let i = 0; i < lines.length; i++) {
203
+ const line = lines[i];
204
+ for (const { name, pattern } of SECRET_PATTERNS) {
205
+ const match = pattern.exec(line);
206
+ if (!match) continue;
207
+ const captured = match[1] ?? match[0];
208
+ if (isPlaceholder(captured)) continue;
209
+ secrets.push({
210
+ pattern: name,
211
+ line: i + 1,
212
+ masked_value: captured.slice(0, 20) + '...',
213
+ });
214
+ }
215
+ }
216
+
217
+ const quality_issues = [];
218
+
219
+ if (lines.length > 800) {
220
+ quality_issues.push({ type: 'file_too_long', detail: `File has ${lines.length} lines (limit: 800)` });
221
+ }
222
+
223
+ const funcKeywords = /^\s*(function\s+\w+|async\s+function\s+\w+|\w+\s*=\s*(?:async\s+)?\(|def\s+\w+|func\s+\w+)/;
224
+ let funcStart = null;
225
+ let braceDepth = 0;
226
+ let inBlock = false;
227
+
228
+ for (let i = 0; i < lines.length; i++) {
229
+ const line = lines[i];
230
+
231
+ if (funcKeywords.test(line)) {
232
+ funcStart = i + 1;
233
+ braceDepth = 0;
234
+ inBlock = false;
235
+ }
236
+
237
+ if (funcStart !== null) {
238
+ for (const ch of line) {
239
+ if (ch === '{') { braceDepth++; inBlock = true; }
240
+ else if (ch === '}') { braceDepth--; }
241
+ }
242
+ if (inBlock && braceDepth <= 0) {
243
+ const funcLen = i + 1 - funcStart + 1;
244
+ if (funcLen > 50) {
245
+ quality_issues.push({
246
+ type: 'function_too_long',
247
+ detail: `Function starting at line ${funcStart} is ${funcLen} lines (limit: 50)`,
248
+ line: funcStart,
249
+ });
250
+ }
251
+ funcStart = null;
252
+ inBlock = false;
253
+ braceDepth = 0;
254
+ }
255
+ }
256
+
257
+ const todoMatch = /\/\/\s*(TODO|FIXME|HACK)\b|#\s*(TODO|FIXME|HACK)\b/i.exec(line);
258
+ if (todoMatch) {
259
+ const tag = (todoMatch[1] ?? todoMatch[2]).toUpperCase();
260
+ quality_issues.push({
261
+ type: 'comment_marker',
262
+ detail: `${tag} comment`,
263
+ line: i + 1,
264
+ });
265
+ }
266
+ }
267
+
268
+ return {
269
+ file_path: filePath,
270
+ secrets,
271
+ quality_issues,
272
+ clean: secrets.length === 0 && quality_issues.length === 0,
273
+ };
274
+ }
275
+
276
+ export function projectContext(args, { ROOT_DIR }) {
277
+ const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
278
+ const contextInspection = inspectContextState(cwd);
279
+ const context_state = contextInspection.state;
280
+ const context = context_state?.markdown || null;
281
+ const has_context_file = contextInspection.hasFile;
282
+ const workflow = loadWorkflow(cwd);
283
+ const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
284
+ const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
285
+ const executionContractModel = resolveExecutionContractModelMetadata({
286
+ envValues: mergedEnv,
287
+ registryModels: registry.models ?? {},
288
+ requestedTier: selectModelTierForWorkCategory(workflow?.tasks?.find((task) => task.key === workflow?.currentTaskKey)?.workCategory),
289
+ workCategory: null,
290
+ });
291
+ const publicHealth = buildPublicHealthSurface({
292
+ cwd,
293
+ contextInspection,
294
+ workflow,
295
+ executionContractModel,
296
+ });
297
+
298
+ let recent_commits = [];
299
+ let working_tree_status = 'clean';
300
+ let changed_files = [];
301
+
302
+ try {
303
+ const log = exec('git log --oneline -10', cwd);
304
+ recent_commits = log.split('\n').filter(Boolean);
305
+ } catch {
306
+ // not a git repo or no commits
307
+ }
308
+
309
+ try {
310
+ const status = exec('git status --short', cwd);
311
+ const statusLines = status.split('\n').filter(Boolean);
312
+ changed_files = statusLines.map((l) => l.trim());
313
+ working_tree_status = statusLines.length > 0 ? 'dirty' : 'clean';
314
+ } catch {
315
+ // not a git repo
316
+ }
317
+
318
+ return { cwd, has_context_file, context, context_state, recent_commits, working_tree_status, changed_files, publicHealth };
319
+ }
320
+
321
+ export function workflowStatus(args, { ROOT_DIR }) {
322
+ const cwd = args.cwd ? resolve(args.cwd) : process.cwd();
323
+ const workflow = loadWorkflow(cwd);
324
+ const workflowHealth = inspectWorkflowHealth(workflow, { cwd });
325
+ const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
326
+ const registry = readJSON(join(ROOT_DIR, 'agents', 'registry.json')) ?? {};
327
+ const executionContractModel = resolveExecutionContractModelMetadata({
328
+ envValues: mergedEnv,
329
+ registryModels: registry.models ?? {},
330
+ requestedTier: selectModelTierForWorkCategory(workflow?.tasks?.find((task) => task.key === workflow?.currentTaskKey)?.workCategory),
331
+ workCategory: null,
332
+ });
333
+ const publicHealth = buildPublicHealthSurface({
334
+ cwd,
335
+ workflow,
336
+ executionContractModel,
337
+ });
338
+ return {
339
+ cwd,
340
+ exists: Boolean(workflow),
341
+ summary: summarizeWorkflow(workflow),
342
+ workflow,
343
+ alignment: {
344
+ status: workflowHealth.alignment.status,
345
+ findings: workflowHealth.alignment.findings,
346
+ },
347
+ publicHealth,
348
+ };
349
+ }