@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,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * readme-age-check.mjs — Stop hook (async)
4
+ *
5
+ * On session end, scans tracked READMEs whose last commit is older than 90
6
+ * days and emits `readme.stale` for cx-docs-keeper. Rate-limited via
7
+ * ~/.cx/readme-age-state.json so the same README isn't reported more than
8
+ * once per week.
9
+ *
10
+ * @p95ms 1500
11
+ * @maxBlockingScope none (async, non-blocking)
12
+ */
13
+
14
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
15
+ import { execSync } from 'node:child_process';
16
+ import { homedir } from 'node:os';
17
+ import { join } from 'node:path';
18
+ import { emitRoleEvent } from '../roles/hook-emit.mjs';
19
+
20
+ const STATE_PATH = join(homedir(), '.cx', 'readme-age-state.json');
21
+ const STALE_DAYS = 90;
22
+ const SUPPRESS_DAYS = 7;
23
+
24
+ const cwd = process.cwd();
25
+
26
+ function listReadmes() {
27
+ try {
28
+ const out = execSync(
29
+ "git ls-files | grep -E '(^|/)README\\.md$' | head -50",
30
+ { cwd, timeout: 2000, shell: '/bin/sh' }
31
+ ).toString();
32
+ return out.split('\n').filter(Boolean);
33
+ } catch {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ function lastCommitTs(file) {
39
+ try {
40
+ const out = execSync(`git log -1 --format=%at -- "${file}"`, { cwd, timeout: 2000 }).toString().trim();
41
+ return parseInt(out, 10) * 1000;
42
+ } catch {
43
+ return 0;
44
+ }
45
+ }
46
+
47
+ function loadState() {
48
+ try { return JSON.parse(readFileSync(STATE_PATH, 'utf8')); } catch { return {}; }
49
+ }
50
+ function saveState(state) {
51
+ try { writeFileSync(STATE_PATH, JSON.stringify(state)); } catch { /* best effort */ }
52
+ }
53
+
54
+ const readmes = listReadmes();
55
+ if (readmes.length === 0) process.exit(0);
56
+
57
+ const state = loadState();
58
+ const now = Date.now();
59
+ const staleThreshold = now - STALE_DAYS * 24 * 60 * 60 * 1000;
60
+ const suppressUntil = now - SUPPRESS_DAYS * 24 * 60 * 60 * 1000;
61
+
62
+ for (const file of readmes) {
63
+ const ts = lastCommitTs(file);
64
+ if (!ts || ts >= staleThreshold) continue;
65
+ const prev = state[file] || 0;
66
+ if (prev > suppressUntil) continue;
67
+ emitRoleEvent({
68
+ type: 'readme.stale',
69
+ summary: `${file} last updated ${Math.round((now - ts) / (24 * 60 * 60 * 1000))} days ago`,
70
+ hookInput: { cwd },
71
+ context: { file, lastCommitTs: ts },
72
+ });
73
+ state[file] = now;
74
+ }
75
+
76
+ saveState(state);
77
+ process.exit(0);
@@ -3,21 +3,28 @@
3
3
  * lib/hooks/registry-sync.mjs — Registry sync hook — reminds to run construct sync after registry changes.
4
4
  *
5
5
  * Runs as PostToolUse after edits to agents/registry.json. Checks if the file was modified and emits a reminder to run construct sync to regenerate platform adapters.
6
+ *
7
+ * @p95ms 12000
8
+ * @maxBlockingScope none (PostToolUse, non-blocking)
6
9
  */
7
10
  import { execSync } from 'child_process';
11
+ import path from 'node:path';
12
+ import { logHookFailure } from './_lib/log.mjs';
13
+ import { constructDir } from '../paths.mjs';
8
14
 
9
15
  const filePath = process.env.TOOL_INPUT_FILE_PATH || '';
10
- if (!filePath.endsWith('/agents/registry.json')) process.exit(0);
16
+ if (!filePath.endsWith(path.join('agents', 'registry.json')) && !filePath.endsWith('/agents/registry.json')) process.exit(0);
11
17
 
12
- const toolkitDir = process.env.CX_TOOLKIT_DIR || `${process.env.HOME}/.construct`;
18
+ const toolkitDir = constructDir();
13
19
  try {
14
- execSync('node sync-agents.mjs', {
20
+ execSync('node scripts/sync-agents.mjs', {
15
21
  cwd: toolkitDir,
16
22
  stdio: 'pipe',
17
23
  env: { ...process.env, CX_TOOLKIT_DIR: toolkitDir },
18
24
  timeout: 12_000,
19
25
  });
20
- } catch (e) {
21
- process.stderr.write(`[registry-sync] Sync failed: ${e.message}\n`);
26
+ } catch (err) {
27
+ logHookFailure({ hook: 'registry-sync', err, phase: 'execute', input: { toolkitDir } });
28
+ process.stderr.write(`[registry-sync] Sync failed: ${err.message}\n`);
22
29
  }
23
30
  process.exit(0);
@@ -3,9 +3,13 @@
3
3
  * lib/hooks/scan-secrets.mjs — Scan secrets hook — detects potential secrets in files before they are committed.
4
4
  *
5
5
  * Runs as PostToolUse after Edit/Write. Scans the modified file content against known secret patterns (API keys, tokens, passwords) and blocks (exit 2) on matches.
6
+ *
7
+ * @p95ms 30
8
+ * @maxBlockingScope PostToolUse
6
9
  */
7
10
  import { readFileSync } from 'fs';
8
11
  import { extname } from 'path';
12
+ import { logHookFailure } from './_lib/log.mjs';
9
13
 
10
14
  const SCAN_EXTENSIONS = new Set([
11
15
  '.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx',
@@ -23,15 +27,42 @@ const PLACEHOLDER_PATTERNS = [
23
27
  /__[A-Z_]+__/,
24
28
  ];
25
29
 
30
+ // Patterns are matched per-line so multiple findings in one file are
31
+ // surfaced together. Capture group 1 is included in the JSONL log if
32
+ // present; otherwise the full match is used.
26
33
  const SECRET_PATTERNS = [
27
34
  { name: 'Anthropic API key', pattern: /ANTHROPIC_API_KEY\s*=\s*(sk-ant-[a-zA-Z0-9\-_]{20,})/i },
35
+ { name: 'Anthropic admin key', pattern: /(sk-ant-admin-[a-zA-Z0-9\-_]{20,})/ },
28
36
  { name: 'OpenAI API key', pattern: /OPENAI_API_KEY\s*=\s*(sk-[a-zA-Z0-9]{40,})/i },
37
+ { name: 'OpenAI project key', pattern: /(sk-proj-[a-zA-Z0-9_\-]{40,})/ },
38
+ { name: 'OpenAI organization key', pattern: /(sk-svcacct-[a-zA-Z0-9_\-]{40,})/ },
29
39
  { name: 'OpenRouter key', pattern: /(sk-or-v1-[a-zA-Z0-9]{40,})/ },
30
40
  { name: 'AWS access key', pattern: /(AKIA[0-9A-Z]{16})/ },
31
- { name: 'Private key (PEM)', pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/ },
32
- { name: 'GitHub personal access token', pattern: /(ghp_[a-zA-Z0-9]{36})/ },
41
+ { name: 'AWS secret access key', pattern: /aws[_\-]?secret[_\-]?access[_\-]?key\s*[:=]\s*['"]?([A-Za-z0-9\/\+=]{40})/i },
42
+ { name: 'AWS session token', pattern: /(ASIA[0-9A-Z]{16})/ },
43
+ { name: 'GCP service account JSON key', pattern: /"private_key"\s*:\s*"-----BEGIN PRIVATE KEY-----/ },
44
+ { name: 'GCP API key', pattern: /(AIza[0-9A-Za-z\-_]{35})/ },
45
+ { name: 'Azure storage account key', pattern: /AccountKey\s*=\s*([A-Za-z0-9\+\/=]{88})/ },
46
+ { name: 'Private key (PEM)', pattern: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/ },
47
+ { name: 'SSH private key', pattern: /-----BEGIN OPENSSH PRIVATE KEY-----/ },
48
+ { name: 'GitHub personal access token (classic)', pattern: /(ghp_[a-zA-Z0-9]{36})/ },
49
+ { name: 'GitHub fine-grained PAT', pattern: /(github_pat_[A-Z0-9]{22}_[A-Za-z0-9]{59})/ },
33
50
  { name: 'GitHub Actions token', pattern: /(ghs_[a-zA-Z0-9]{36})/ },
34
- { name: 'Database URL with credentials', pattern: /DATABASE_URL\s*=\s*(postgresql:\/\/[^@]+:[^@]+@)/i },
51
+ { name: 'GitHub OAuth token', pattern: /(gho_[a-zA-Z0-9]{36})/ },
52
+ { name: 'GitHub user-to-server token', pattern: /(ghu_[a-zA-Z0-9]{36})/ },
53
+ { name: 'GitHub server-to-server token', pattern: /(ghr_[a-zA-Z0-9]{36})/ },
54
+ { name: 'Slack bot token', pattern: /(xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,})/ },
55
+ { name: 'Slack user token', pattern: /(xoxp-[0-9]{10,}-[0-9]{10,}-[0-9]{10,}-[a-z0-9]{32,})/ },
56
+ { name: 'Slack webhook URL', pattern: /(https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]{24})/ },
57
+ { name: 'Stripe live secret', pattern: /(sk_live_[A-Za-z0-9]{20,})/ },
58
+ { name: 'Stripe restricted key', pattern: /(rk_live_[A-Za-z0-9]{20,})/ },
59
+ { name: 'Twilio account SID', pattern: /(AC[a-f0-9]{32})/ },
60
+ { name: 'Twilio auth token', pattern: /TWILIO_AUTH_TOKEN\s*=\s*([a-f0-9]{32})/i },
61
+ { name: 'SendGrid API key', pattern: /(SG\.[a-zA-Z0-9_\-]{22}\.[a-zA-Z0-9_\-]{43})/ },
62
+ { name: 'Mailgun API key', pattern: /(key-[a-z0-9]{32})/ },
63
+ { name: 'JWT (signed)', pattern: /(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_\-]{10,})/ },
64
+ { name: 'Database URL with credentials', pattern: /(?:DATABASE|POSTGRES|MYSQL|MONGODB)_URL\s*=\s*([a-z]+:\/\/[^:\s]+:[^@\s]+@)/i },
65
+ { name: 'Generic API key (env-style)', pattern: /(?:^|\s)([A-Z][A-Z0-9_]*(?:API|SECRET|TOKEN|PASSWORD|PRIVATE)_KEY)\s*=\s*['"]?([A-Za-z0-9_\-+/=]{30,})['"]?/, captureGroup: 2 },
35
66
  ];
36
67
 
37
68
  function isPlaceholder(value) {
@@ -48,7 +79,8 @@ if (!SCAN_EXTENSIONS.has(ext)) process.exit(0);
48
79
  let content;
49
80
  try {
50
81
  content = readFileSync(filePath, 'utf8');
51
- } catch {
82
+ } catch (err) {
83
+ logHookFailure({ hook: 'scan-secrets', err, phase: 'read', input: { filePath } });
52
84
  process.exit(0);
53
85
  }
54
86
 
@@ -57,12 +89,12 @@ const findings = [];
57
89
 
58
90
  for (let i = 0; i < lines.length; i++) {
59
91
  const line = lines[i];
60
- for (const { name, pattern } of SECRET_PATTERNS) {
92
+ for (const { name, pattern, captureGroup } of SECRET_PATTERNS) {
61
93
  const match = pattern.exec(line);
62
94
  if (!match) continue;
63
- const captured = match[1] ?? match[0];
95
+ const captured = match[captureGroup ?? 1] ?? match[0];
64
96
  if (isPlaceholder(captured)) continue;
65
- findings.push({ name, line: i + 1, value: captured.slice(0, 20) + '...' });
97
+ findings.push({ name, line: i + 1, value: String(captured).slice(0, 20) + '...' });
66
98
  }
67
99
  }
68
100
 
@@ -73,4 +105,15 @@ for (const { name, line, value } of findings) {
73
105
  process.stderr.write(` Line ${line}: ${name} — matched value starts with: ${value}\n`);
74
106
  }
75
107
  process.stderr.write('Remove or rotate the secret before writing this file.\n');
108
+
109
+ try {
110
+ const { emitRoleEvent } = await import('../roles/hook-emit.mjs');
111
+ emitRoleEvent({
112
+ type: 'secrets.detected',
113
+ summary: `Secret(s) detected in ${filePath}: ${findings.map((f) => f.name).join(', ')}`,
114
+ hookInput: {},
115
+ context: { filePath, findings },
116
+ });
117
+ } catch { /* best effort */ }
118
+
76
119
  process.exit(2);
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/hooks/session-optimize.mjs — Session end optimization hook — triggers agent optimization for low-performers.
4
+ *
5
+ * Runs as a Stop hook at session end. Checks recent performance reviews for agents with avgScore < 0.7
6
+ * and invokes `construct optimize <agent>` to generate and apply improvement patches.
7
+ *
8
+ * @p95ms 300
9
+ * @maxBlockingScope Stop (non-blocking, async)
10
+ */
11
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
12
+ import { join, dirname } from 'path';
13
+ import { homedir } from 'os';
14
+ import { spawnSync } from 'child_process';
15
+ import { fileURLToPath } from 'url';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const CONSTRUCT_BIN = join(__dirname, '..', '..', 'bin', 'construct');
19
+
20
+ const OPTIMIZATION_THRESHOLD = 0.7; // Score below this triggers optimization
21
+ const MIN_TRACES_PER_AGENT = 3; // Minimum traces needed for optimization
22
+ const OPTIMIZATION_DAYS = 7; // Look back 7 days for performance data
23
+ const OPTIMIZATION_LOG_DIR = join(homedir(), '.cx', 'optimization-logs');
24
+
25
+ // Ensure optimization log directory exists
26
+ mkdirSync(OPTIMIZATION_LOG_DIR, { recursive: true });
27
+
28
+ let input = {};
29
+ try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
30
+
31
+ // Only run for Construct sessions
32
+ const cwd = input?.cwd || process.cwd();
33
+ const projectName = cwd.split('/').pop() || 'project';
34
+
35
+ // Skip if this is not a Construct project or in global mode
36
+ if (projectName !== 'construct' && !existsSync(join(cwd, '.cx'))) {
37
+ process.exit(0);
38
+ }
39
+
40
+ // Reviews older than this trigger an auto-refresh on the next session-stop.
41
+ // Six hours keeps optimization signal current without re-running review on
42
+ // every short session. Operators can still run `construct review` manually.
43
+ const REVIEW_REFRESH_MS = 6 * 60 * 60 * 1000;
44
+
45
+ function maybeRefreshReviews() {
46
+ const reviewsDir = join(homedir(), '.cx', 'performance-reviews');
47
+ let needsRefresh = false;
48
+ try {
49
+ if (!existsSync(reviewsDir)) {
50
+ needsRefresh = true;
51
+ } else {
52
+ const files = spawnSync('find', [reviewsDir, '-name', '*.json', '-type', 'f'], {
53
+ encoding: 'utf8', timeout: 5000,
54
+ }).stdout.trim().split('\n').filter(Boolean);
55
+ if (files.length === 0) {
56
+ needsRefresh = true;
57
+ } else {
58
+ const newestMs = files.reduce((acc, f) => {
59
+ try {
60
+ const m = Number(spawnSync('stat', ['-f', '%m', f], { encoding: 'utf8' }).stdout.trim());
61
+ return Number.isFinite(m) ? Math.max(acc, m * 1000) : acc;
62
+ } catch { return acc; }
63
+ }, 0);
64
+ if (Date.now() - newestMs > REVIEW_REFRESH_MS) needsRefresh = true;
65
+ }
66
+ }
67
+ } catch { /* tolerate fs errors silently — refresh is best-effort */ }
68
+
69
+ if (!needsRefresh) return;
70
+
71
+ // Refresh detached so the Stop hook does not block on telemetry latency.
72
+ try {
73
+ spawnSync(process.execPath, [CONSTRUCT_BIN, 'review', '--quiet'], {
74
+ stdio: 'ignore',
75
+ timeout: 60_000,
76
+ detached: true,
77
+ });
78
+ } catch { /* refresh is best-effort; manual `construct review` is always available */ }
79
+ }
80
+
81
+ // Read the most recent performance review
82
+ function getLatestPerformanceReview() {
83
+ const reviewsDir = join(homedir(), '.cx', 'performance-reviews');
84
+ if (!existsSync(reviewsDir)) return null;
85
+
86
+ try {
87
+ const files = spawnSync('find', [reviewsDir, '-name', '*.json', '-type', 'f'], {
88
+ encoding: 'utf8',
89
+ timeout: 5000
90
+ }).stdout.trim().split('\n').filter(Boolean);
91
+
92
+ if (files.length === 0) return null;
93
+
94
+ // Sort by modification time, newest first
95
+ files.sort((a, b) => {
96
+ try {
97
+ const statA = spawnSync('stat', ['-f', '%m', a], { encoding: 'utf8' }).stdout.trim();
98
+ const statB = spawnSync('stat', ['-f', '%m', b], { encoding: 'utf8' }).stdout.trim();
99
+ return parseInt(statB) - parseInt(statA);
100
+ } catch {
101
+ return 0;
102
+ }
103
+ });
104
+
105
+ const latestFile = files[0];
106
+ const content = readFileSync(latestFile, 'utf8');
107
+ return JSON.parse(content);
108
+ } catch (error) {
109
+ console.error(`Error reading performance reviews: ${error.message}`);
110
+ return null;
111
+ }
112
+ }
113
+
114
+ // Check if agent needs optimization
115
+ function needsOptimization(agentStats) {
116
+ return (
117
+ agentStats.scoredInvocations >= MIN_TRACES_PER_AGENT &&
118
+ agentStats.avgScore < OPTIMIZATION_THRESHOLD &&
119
+ agentStats.lowScoreTraces.length > 0
120
+ );
121
+ }
122
+
123
+ // Run optimization for a specific agent
124
+ function optimizeAgent(agentName) {
125
+ console.log(`🔧 Optimizing ${agentName} (score < ${OPTIMIZATION_THRESHOLD})`);
126
+
127
+ const logFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-${agentName}.log`);
128
+ const startTime = Date.now();
129
+
130
+ try {
131
+ // Run construct optimize with auto-apply (no --dry-run)
132
+ const result = spawnSync(CONSTRUCT_BIN, ['optimize', agentName, `--days=${OPTIMIZATION_DAYS}`], {
133
+ cwd,
134
+ encoding: 'utf8',
135
+ timeout: 300000, // 5 minutes timeout for optimization
136
+ stdio: ['pipe', 'pipe', 'pipe']
137
+ });
138
+
139
+ const duration = Date.now() - startTime;
140
+
141
+ // Log the optimization attempt
142
+ const logEntry = {
143
+ timestamp: new Date().toISOString(),
144
+ agent: agentName,
145
+ durationMs: duration,
146
+ success: result.status === 0,
147
+ stdout: result.stdout,
148
+ stderr: result.stderr,
149
+ exitCode: result.status,
150
+ signal: result.signal
151
+ };
152
+
153
+ writeFileSync(logFile, JSON.stringify(logEntry, null, 2) + '\n');
154
+
155
+ if (result.status === 0) {
156
+ console.log(`✅ Optimization for ${agentName} completed in ${duration}ms`);
157
+
158
+ // Record observation about the optimization
159
+ recordOptimizationObservation(agentName, true, duration);
160
+ } else {
161
+ console.log(`❌ Optimization for ${agentName} failed with code ${result.status}`);
162
+ console.log(`Stderr: ${result.stderr.slice(0, 500)}...`);
163
+
164
+ // Record observation about the failure
165
+ recordOptimizationObservation(agentName, false, duration, result.stderr);
166
+ }
167
+
168
+ return result.status === 0;
169
+ } catch (error) {
170
+ console.error(`❌ Optimization for ${agentName} threw error: ${error.message}`);
171
+
172
+ const logEntry = {
173
+ timestamp: new Date().toISOString(),
174
+ agent: agentName,
175
+ durationMs: Date.now() - startTime,
176
+ success: false,
177
+ error: error.message,
178
+ stack: error.stack
179
+ };
180
+
181
+ writeFileSync(logFile, JSON.stringify(logEntry, null, 2) + '\n');
182
+ return false;
183
+ }
184
+ }
185
+
186
+ // Record optimization observation for learning
187
+ function recordOptimizationObservation(agentName, success, durationMs, error = null) {
188
+ const observation = {
189
+ role: 'construct',
190
+ category: success ? 'pattern' : 'anti-pattern',
191
+ summary: success
192
+ ? `Auto-optimized ${agentName} in ${durationMs}ms`
193
+ : `Auto-optimization failed for ${agentName}: ${error?.slice(0, 200) || 'unknown error'}`,
194
+ content: success
195
+ ? `Automatic optimization triggered for ${agentName} after performance review showed avgScore < ${OPTIMIZATION_THRESHOLD}. Optimization completed successfully in ${durationMs}ms.`
196
+ : `Automatic optimization failed for ${agentName}. Error: ${error || 'Unknown'}. System should notify maintainer.`,
197
+ tags: ['auto-optimization', 'performance-review', agentName],
198
+ confidence: 0.9
199
+ };
200
+
201
+ try {
202
+ // Use existing observation recording if available
203
+ const observationFile = join(homedir(), '.cx', 'observations', `${Date.now()}-${agentName}-optimization.json`);
204
+ mkdirSync(dirname(observationFile), { recursive: true });
205
+ writeFileSync(observationFile, JSON.stringify(observation, null, 2) + '\n');
206
+ } catch (error) {
207
+ // Non-critical - observation recording failed
208
+ console.error(`Failed to record optimization observation: ${error.message}`);
209
+ }
210
+ }
211
+
212
+ // Main optimization logic
213
+ function main() {
214
+ // If the most recent review is missing or stale (>6h), kick off a refresh
215
+ // detached so the Stop hook returns promptly. The refresh runs on the next
216
+ // session's Stop if it's still in flight; nothing here blocks user work.
217
+ maybeRefreshReviews();
218
+
219
+ const review = getLatestPerformanceReview();
220
+ if (!review || !review.agentStats || review.agentStats.length === 0) {
221
+ console.log('📊 No performance review data found. Run `construct review` first.');
222
+ process.exit(0);
223
+ }
224
+
225
+ console.log(`📊 Checking ${review.agentStats.length} agents from performance review ${review.generated}`);
226
+
227
+ const agentsNeedingOptimization = review.agentStats.filter(needsOptimization);
228
+
229
+ if (agentsNeedingOptimization.length === 0) {
230
+ console.log(`✅ All ${review.agentStats.length} agents have scores >= ${OPTIMIZATION_THRESHOLD} or insufficient data`);
231
+ process.exit(0);
232
+ }
233
+
234
+ console.log(`🔧 Found ${agentsNeedingOptimization.length} agents needing optimization:`);
235
+ agentsNeedingOptimization.forEach(agent => {
236
+ console.log(` • ${agent.name}: avgScore=${agent.avgScore}, invocations=${agent.invocations}`);
237
+ });
238
+
239
+ // Run optimization for each agent (serial for now)
240
+ let optimizedCount = 0;
241
+ let failedCount = 0;
242
+
243
+ for (const agent of agentsNeedingOptimization) {
244
+ const success = optimizeAgent(agent.name);
245
+ if (success) {
246
+ optimizedCount++;
247
+ } else {
248
+ failedCount++;
249
+ }
250
+
251
+ // Small delay between optimizations to avoid rate limits
252
+ if (agentsNeedingOptimization.length > 1) {
253
+ spawnSync('sleep', ['10'], { encoding: 'utf8' });
254
+ }
255
+ }
256
+
257
+ // Summary
258
+ console.log(`\n📈 Optimization Summary:`);
259
+ console.log(` • Agents checked: ${review.agentStats.length}`);
260
+ console.log(` • Needing optimization: ${agentsNeedingOptimization.length}`);
261
+ console.log(` • Successfully optimized: ${optimizedCount}`);
262
+ console.log(` • Failed: ${failedCount}`);
263
+
264
+ if (optimizedCount > 0) {
265
+ console.log(`\n💡 Optimized agents will be evaluated in the next performance review.`);
266
+ console.log(` Run \`construct review\` after next session to see improvements.`);
267
+ }
268
+
269
+ // Log summary to file
270
+ const summary = {
271
+ timestamp: new Date().toISOString(),
272
+ project: projectName,
273
+ reviewDate: review.generated,
274
+ agentsChecked: review.agentStats.length,
275
+ agentsNeedingOptimization: agentsNeedingOptimization.length,
276
+ optimizedCount,
277
+ failedCount,
278
+ agents: agentsNeedingOptimization.map(a => ({
279
+ name: a.name,
280
+ avgScore: a.avgScore,
281
+ invocations: a.invocations,
282
+ optimized: optimizeAgent.includes ? 'yes' : 'no'
283
+ }))
284
+ };
285
+
286
+ const summaryFile = join(OPTIMIZATION_LOG_DIR, `${new Date().toISOString().split('T')[0]}-summary.json`);
287
+ writeFileSync(summaryFile, JSON.stringify(summary, null, 2) + '\n');
288
+ }
289
+
290
+ // Run main logic
291
+ try {
292
+ main();
293
+ } catch (error) {
294
+ console.error(`❌ Session optimization hook crashed: ${error.message}`);
295
+ console.error(error.stack);
296
+
297
+ // Log the crash
298
+ const crashLog = join(OPTIMIZATION_LOG_DIR, 'crashes', `${Date.now()}-crash.json`);
299
+ mkdirSync(dirname(crashLog), { recursive: true });
300
+ writeFileSync(crashLog, JSON.stringify({
301
+ timestamp: new Date().toISOString(),
302
+ error: error.message,
303
+ stack: error.stack,
304
+ cwd,
305
+ projectName
306
+ }, null, 2) + '\n');
307
+
308
+ process.exit(1);
309
+ }
310
+
311
+ process.exit(0);