@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
@@ -3,10 +3,12 @@
3
3
  * lib/hooks/agent-tracker.mjs — Agent task lifecycle hook — tracks task start, completion, and handoffs.
4
4
  *
5
5
  * Runs as PostToolUse after Agent tool calls. Records agent invocations and their outcomes to ~/.cx/agent-log.json for telemetry and performance review.
6
+ * Also records observations for learning system to identify patterns/anti-patterns.
7
+ *
8
+ * @p95ms 10
9
+ * @maxBlockingScope none (PostToolUse, non-blocking)
6
10
  */
7
- // PostToolUse(Task) records the last dispatched subagent to ~/.cx/last-agent.json.
8
- // stop-notify.mjs reads this to attribute per-turn token costs to the dispatching agent.
9
- import { readFileSync, writeFileSync, mkdirSync } from 'fs';
11
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, appendFileSync } from 'fs';
10
12
  import { join } from 'path';
11
13
  import { homedir } from 'os';
12
14
 
@@ -17,7 +19,7 @@ const toolName = input?.tool_name || '';
17
19
  if (toolName !== 'Task') process.exit(0);
18
20
 
19
21
  const toolInput = input?.tool_input || {};
20
- const cwd = input?.cwd || process.cwd();
22
+ const toolResult = input?.tool_result || {};
21
23
 
22
24
  // Extract agent identity from the Task tool input.
23
25
  // Claude Code passes subagent_type or description in tool_input.
@@ -31,20 +33,175 @@ if (!agentName) {
31
33
  agentName = descMatch ? descMatch[1].toLowerCase() : 'subagent';
32
34
  }
33
35
 
34
- // Load active task key from workflow
35
- let taskKey = null;
36
- try {
37
- const wf = JSON.parse(readFileSync(join(cwd, '.cx', 'workflow.json'), 'utf8'));
38
- taskKey = wf.currentTaskKey || null;
39
- } catch { /* no workflow */ }
36
+ // Extract success/failure indicators from result
37
+ const resultText = toolResult?.result || '';
38
+ const successIndicators = ['success', 'completed', 'finished', 'done', '✅', '✔'];
39
+ const errorIndicators = ['error', 'failed', 'failure', '', '✗', 'exception', 'timed out'];
40
+ const warningIndicators = ['warning', 'warn', '⚠', 'note:', 'attention'];
40
41
 
42
+ let outcome = 'unknown';
43
+ let success = null;
44
+
45
+ const lowerResult = resultText.toLowerCase();
46
+ if (errorIndicators.some(ind => lowerResult.includes(ind))) {
47
+ outcome = 'error';
48
+ success = false;
49
+ } else if (warningIndicators.some(ind => lowerResult.includes(ind))) {
50
+ outcome = 'warning';
51
+ success = null;
52
+ } else if (successIndicators.some(ind => lowerResult.includes(ind))) {
53
+ outcome = 'success';
54
+ success = true;
55
+ }
56
+
57
+ // Record agent invocation
41
58
  try {
42
59
  const home = homedir();
43
- mkdirSync(join(home, '.cx'), { recursive: true });
60
+ const cxDir = join(home, '.cx');
61
+ mkdirSync(cxDir, { recursive: true });
62
+
63
+ // Update last-agent file for coordination
44
64
  writeFileSync(
45
- join(home, '.cx', 'last-agent.json'),
46
- JSON.stringify({ agent: agentName, taskKey, ts: new Date().toISOString() }),
65
+ join(cxDir, 'last-agent.json'),
66
+ JSON.stringify({
67
+ agent: agentName,
68
+ coordination: 'tracker-plus-plan',
69
+ ts: new Date().toISOString(),
70
+ outcome,
71
+ description: description.slice(0, 200)
72
+ }),
73
+ );
74
+
75
+ // Append to agent log for telemetry
76
+ const agentLogFile = join(cxDir, 'agent-log.jsonl');
77
+ const logEntry = {
78
+ timestamp: new Date().toISOString(),
79
+ agent: agentName,
80
+ outcome,
81
+ success,
82
+ description: description.slice(0, 500),
83
+ resultLength: resultText.length,
84
+ toolInputKeys: Object.keys(toolInput).filter(k => !k.includes('secret') && !k.includes('password') && !k.includes('token'))
85
+ };
86
+
87
+ appendFileSync(agentLogFile, JSON.stringify(logEntry) + '\n');
88
+
89
+ // Record observation for pattern learning (only if we have meaningful outcome)
90
+ if (outcome !== 'unknown' && description.length > 10) {
91
+ const observationsDir = join(cxDir, 'observations', 'agent-outcomes');
92
+ mkdirSync(observationsDir, { recursive: true });
93
+
94
+ const observationFile = join(observationsDir, `${Date.now()}-${agentName}.json`);
95
+
96
+ // Categorize as pattern or anti-pattern based on outcome
97
+ const category = success === true ? 'pattern' :
98
+ success === false ? 'anti-pattern' : 'observation';
99
+
100
+ const summary = success === true
101
+ ? `${agentName} successfully completed: ${description.slice(0, 100)}`
102
+ : success === false
103
+ ? `${agentName} failed: ${description.slice(0, 100)}`
104
+ : `${agentName} executed with warnings: ${description.slice(0, 100)}`;
105
+
106
+ const observation = {
107
+ role: 'agent-tracker',
108
+ category,
109
+ summary,
110
+ content: `${agentName} was invoked with description: "${description.slice(0, 500)}"\n\nOutcome: ${outcome}\nResult length: ${resultText.length} chars`,
111
+ tags: ['agent-invocation', agentName, outcome],
112
+ confidence: 0.8,
113
+ timestamp: new Date().toISOString()
114
+ };
115
+
116
+ writeFileSync(observationFile, JSON.stringify(observation, null, 2) + '\n');
117
+
118
+ // Also record in vector-ready format for learning system
119
+ if (success !== null) {
120
+ const learningDir = join(cxDir, 'learning', agentName);
121
+ mkdirSync(learningDir, { recursive: true });
122
+
123
+ const learningFile = join(learningDir, `${Date.now()}-${success ? 'success' : 'failure'}.json`);
124
+ const learningEntry = {
125
+ agent: agentName,
126
+ success,
127
+ description,
128
+ outcome,
129
+ timestamp: new Date().toISOString(),
130
+ // Extract potential patterns from description
131
+ keywords: extractKeywords(description),
132
+ taskType: classifyTaskType(description)
133
+ };
134
+
135
+ writeFileSync(learningFile, JSON.stringify(learningEntry, null, 2) + '\n');
136
+ }
137
+ }
138
+
139
+ } catch (error) {
140
+ // Non-critical - best effort tracking
141
+ console.error(`Agent tracker error: ${error.message}`);
142
+ }
143
+
144
+ // Auto-enqueue handoff when a completing persona's result references
145
+ // `next:cx-<role>` and the target persona is onboarded in the role framework.
146
+ // The next session-start will pick up the queued entry and dispatch via Task.
147
+
148
+ try {
149
+ if (process.env.CONSTRUCT_ROLES !== 'off' && agentName && /^cx-/.test(agentName)) {
150
+ const matches = [...resultText.matchAll(/\bnext:cx-([a-z][a-z-]+)\b/g)].map((m) => m[1]);
151
+ const unique = [...new Set(matches)];
152
+ if (unique.length > 0) {
153
+ const { isOnboarded, loadManifest } = await import('../roles/manifest.mjs');
154
+ const pendingPath = join(homedir(), '.cx', 'role-pending.jsonl');
155
+ const bdMatch = /\b(construct-[a-z0-9]+)\b/i.exec(resultText) || [];
156
+ const bdIssueId = bdMatch[1] || null;
157
+ for (const targetId of unique) {
158
+ if (!isOnboarded(targetId)) continue;
159
+ const manifest = loadManifest(targetId);
160
+ const entry = {
161
+ ts: Date.now(),
162
+ personaId: targetId,
163
+ cxId: `cx-${targetId}`,
164
+ bdIssueId,
165
+ fingerprint: `handoff-${agentName}-${targetId}-${Date.now().toString(36)}`,
166
+ eventType: 'handoff.received',
167
+ summary: `Handoff from ${agentName}${bdIssueId ? ` (re ${bdIssueId})` : ''}`,
168
+ killSwitchEnv: manifest?.killSwitchEnv || '',
169
+ handoffFrom: agentName,
170
+ };
171
+ appendFileSync(pendingPath, JSON.stringify(entry) + '\n');
172
+ }
173
+ }
174
+ }
175
+ } catch { /* best effort — handoff enqueue never blocks */ }
176
+
177
+ // Helper functions for learning system
178
+ function extractKeywords(text) {
179
+ const commonStop = ['the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'];
180
+ const words = text.toLowerCase().split(/[\s\.,;!?]+/).filter(word =>
181
+ word.length > 2 && !commonStop.includes(word) && /^[a-z]+$/.test(word)
47
182
  );
48
- } catch { /* best effort */ }
183
+
184
+ // Count frequency
185
+ const freq = {};
186
+ words.forEach(word => freq[word] = (freq[word] || 0) + 1);
187
+
188
+ // Return top 5 keywords
189
+ return Object.entries(freq)
190
+ .sort((a, b) => b[1] - a[1])
191
+ .slice(0, 5)
192
+ .map(([word]) => word);
193
+ }
194
+
195
+ function classifyTaskType(description) {
196
+ const lower = description.toLowerCase();
197
+ if (lower.includes('fix') || lower.includes('bug') || lower.includes('error')) return 'bug-fix';
198
+ if (lower.includes('implement') || lower.includes('create') || lower.includes('add')) return 'implementation';
199
+ if (lower.includes('refactor') || lower.includes('improve') || lower.includes('optimize')) return 'refactoring';
200
+ if (lower.includes('review') || lower.includes('audit') || lower.includes('check')) return 'review';
201
+ if (lower.includes('test') || lower.includes('verify') || lower.includes('validate')) return 'testing';
202
+ if (lower.includes('document') || lower.includes('write') || lower.includes('readme')) return 'documentation';
203
+ if (lower.includes('research') || lower.includes('analyze') || lower.includes('investigate')) return 'research';
204
+ return 'other';
205
+ }
49
206
 
50
207
  process.exit(0);
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/hooks/audit-reads.mjs — opt-in tamper-evident log of every Read tool call.
4
+ *
5
+ * Symmetric counterpart to audit-trail.mjs (which records mutations). Reads
6
+ * are off by default because they happen much more often than writes; enable
7
+ * with `CONSTRUCT_AUDIT_READS=1`. When enabled, every Read tool call writes
8
+ * a single JSONL line to ~/.cx/audit-reads.jsonl with:
9
+ *
10
+ * - timestamp ISO
11
+ * - session_id (when supplied by the harness)
12
+ * - tool_name (always Read)
13
+ * - agent (from ~/.cx/last-agent.json — falls back to "construct")
14
+ * - cwd
15
+ * - target (the file_path read)
16
+ * - bytes (size of the file at read time)
17
+ * - content_hash (sha256 of the file's first 64KiB at read time, truncated)
18
+ * - prev_line_hash (chained tamper-evidence — hash of previous JSONL line)
19
+ *
20
+ * The prev_line_hash chain means any after-the-fact reordering or deletion
21
+ * breaks the chain and is detectable by a simple replay (same approach as
22
+ * the mutation audit).
23
+ *
24
+ * @p95ms 8
25
+ * @maxBlockingScope none (PostToolUse, non-blocking)
26
+ */
27
+ import { readFileSync, appendFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
28
+ import { createHash } from 'node:crypto';
29
+ import { join, resolve } from 'node:path';
30
+ import { homedir } from 'node:os';
31
+ import { logHookFailure } from './_lib/log.mjs';
32
+
33
+ const CX_DIR = join(homedir(), '.cx');
34
+ const AUDIT_FILE = join(CX_DIR, 'audit-reads.jsonl');
35
+ const LAST_AGENT = join(CX_DIR, 'last-agent.json');
36
+ const HASH_PREFIX_BYTES = 64 * 1024;
37
+
38
+ if (process.env.CONSTRUCT_AUDIT_READS !== '1') process.exit(0);
39
+
40
+ function sha256(input) { return createHash('sha256').update(input).digest('hex'); }
41
+
42
+ function readLastAgent() {
43
+ try {
44
+ const data = JSON.parse(readFileSync(LAST_AGENT, 'utf8'));
45
+ return data?.agent || data?.name || 'construct';
46
+ } catch { return 'construct'; }
47
+ }
48
+
49
+ function readPrevLineHash() {
50
+ try {
51
+ if (!existsSync(AUDIT_FILE)) return null;
52
+ const size = statSync(AUDIT_FILE).size;
53
+ if (size === 0) return null;
54
+ const readFrom = Math.max(0, size - 2048);
55
+ const tail = readFileSync(AUDIT_FILE, 'utf8').slice(readFrom);
56
+ const lines = tail.split('\n').filter(Boolean);
57
+ if (lines.length === 0) return null;
58
+ return sha256(lines[lines.length - 1]);
59
+ } catch { return null; }
60
+ }
61
+
62
+ let input = {};
63
+ try { input = JSON.parse(readFileSync(0, 'utf8')); }
64
+ catch (err) { logHookFailure({ hook: 'audit-reads', err, phase: 'parse' }); process.exit(0); }
65
+
66
+ if ((input?.tool_name || '') !== 'Read') process.exit(0);
67
+
68
+ const cwd = input?.cwd || process.cwd();
69
+ const filePath = input?.tool_input?.file_path;
70
+ if (!filePath) process.exit(0);
71
+
72
+ const absPath = filePath.startsWith('/') ? filePath : resolve(cwd, filePath);
73
+
74
+ let bytes = null;
75
+ let contentHash = null;
76
+ try {
77
+ if (existsSync(absPath)) {
78
+ bytes = statSync(absPath).size;
79
+ const buf = Buffer.alloc(Math.min(bytes, HASH_PREFIX_BYTES));
80
+ const fd = await import('node:fs').then((m) => m.openSync(absPath, 'r'));
81
+ const fsModule = await import('node:fs');
82
+ fsModule.readSync(fd, buf, 0, buf.length, 0);
83
+ fsModule.closeSync(fd);
84
+ contentHash = sha256(buf).slice(0, 32);
85
+ }
86
+ } catch (err) {
87
+ logHookFailure({ hook: 'audit-reads', err, phase: 'hash', input: { filePath: absPath } });
88
+ }
89
+
90
+ const record = {
91
+ ts: new Date().toISOString(),
92
+ session_id: input?.session_id || null,
93
+ tool: 'Read',
94
+ agent: readLastAgent(),
95
+ cwd,
96
+ target: filePath,
97
+ bytes,
98
+ content_hash: contentHash,
99
+ prev_line_hash: readPrevLineHash(),
100
+ };
101
+
102
+ try {
103
+ mkdirSync(CX_DIR, { recursive: true });
104
+ appendFileSync(AUDIT_FILE, JSON.stringify(record) + '\n', 'utf8');
105
+ } catch (err) {
106
+ logHookFailure({ hook: 'audit-reads', err, phase: 'append' });
107
+ }
108
+
109
+ process.exit(0);
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/hooks/audit-trail.mjs — append-only audit log of every mutation Construct
4
+ * (or any subagent it dispatches) makes.
5
+ *
6
+ * Runs as PostToolUse on Edit | Write | MultiEdit | NotebookEdit | Bash.
7
+ * For Bash, only records commands that mutate state (git/npm/docker/cargo etc.
8
+ * write operations, rm, mv, chmod). Read-only commands are skipped.
9
+ *
10
+ * Each record is a single JSONL line written to ~/.cx/audit-trail.jsonl with:
11
+ * - timestamp ISO
12
+ * - session_id (from hook input when available)
13
+ * - tool_name
14
+ * - agent (from ~/.cx/last-agent.json, falls back to "construct")
15
+ * - task metadata (from the last-agent hint when available)
16
+ * - cwd
17
+ * - target (file_path for edits/writes, command for Bash)
18
+ * - detail (old_string prefix + new_string prefix for Edit; truncated diff
19
+ * for Write; command summary for Bash)
20
+ * - content_hash (sha256 of post-change file for edits, null for Bash)
21
+ * - prev_line_hash (chained tamper-evidence — hash of previous JSONL line)
22
+ *
23
+ * The prev_line_hash chain means any after-the-fact reordering or deletion
24
+ * breaks the chain and is detectable by a simple replay.
25
+ *
26
+ * @p95ms 15
27
+ * @maxBlockingScope none (PostToolUse, non-blocking)
28
+ */
29
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
30
+ import { createHash } from 'node:crypto';
31
+ import { join, resolve } from 'node:path';
32
+ import { homedir } from 'node:os';
33
+
34
+ const CX_DIR = join(homedir(), '.cx');
35
+ const AUDIT_FILE = join(CX_DIR, 'audit-trail.jsonl');
36
+ const LAST_AGENT = join(CX_DIR, 'last-agent.json');
37
+ const MUTATING_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'Bash']);
38
+
39
+ const BASH_MUTATING_PATTERNS = [
40
+ /^\s*git\s+(add|commit|push|rebase|merge|reset|checkout\s+-b|branch\s+-D|tag|cherry-pick|stash\s+(?:push|drop))/,
41
+ /^\s*(rm|mv|cp|chmod|chown|ln|mkdir|rmdir|touch|install)\b/,
42
+ /^\s*(npm|pnpm|yarn|bun|pip|pip3|poetry|cargo|go|brew)\s+(install|add|remove|update|upgrade|uninstall|publish|link)/,
43
+ /^\s*docker\s+(run|build|rm|rmi|push|tag|stop|kill|exec)/,
44
+ /^\s*kubectl\s+(apply|delete|create|replace|scale|rollout)/,
45
+ /^\s*terraform\s+(apply|destroy)/,
46
+ /^\s*psql\b|^\s*sqlite3\b|\bINSERT\s+INTO\b|\bUPDATE\s+\w+\s+SET\b|\bDELETE\s+FROM\b/i,
47
+ /^\s*construct\s+(sync|setup|mcp\s+(add|remove)|init|init-docs)/,
48
+ />\s*[^|&]|>>\s*[^|&]/, // shell redirection that writes
49
+ ];
50
+
51
+ function isMutatingBash(command) {
52
+ if (!command) return false;
53
+ return BASH_MUTATING_PATTERNS.some((re) => re.test(command));
54
+ }
55
+
56
+ function sha256(input) {
57
+ return createHash('sha256').update(input).digest('hex');
58
+ }
59
+
60
+ function readLastAgent() {
61
+ try {
62
+ const raw = readFileSync(LAST_AGENT, 'utf8');
63
+ const data = JSON.parse(raw);
64
+ return data?.agent || data?.name || 'construct';
65
+ } catch {
66
+ return 'construct';
67
+ }
68
+ }
69
+
70
+ function readPrevLineHash() {
71
+ try {
72
+ if (!existsSync(AUDIT_FILE)) return null;
73
+ const size = statSync(AUDIT_FILE).size;
74
+ if (size === 0) return null;
75
+ const readFrom = Math.max(0, size - 2048);
76
+ const fs = readFileSync(AUDIT_FILE, 'utf8').slice(readFrom);
77
+ const lines = fs.split('\n').filter(Boolean);
78
+ if (lines.length === 0) return null;
79
+ return sha256(lines[lines.length - 1]);
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ function truncateSnippet(s, limit = 280) {
86
+ if (!s) return '';
87
+ const str = String(s);
88
+ return str.length <= limit ? str : str.slice(0, limit) + '…';
89
+ }
90
+
91
+ let input = {};
92
+ try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
93
+
94
+ const toolName = input?.tool_name || '';
95
+ if (!MUTATING_TOOLS.has(toolName)) process.exit(0);
96
+
97
+ const cwd = input?.cwd || process.cwd();
98
+ const toolInput = input?.tool_input || {};
99
+
100
+ let target = null;
101
+ let detail = null;
102
+ let contentHash = null;
103
+
104
+ if (toolName === 'Bash') {
105
+ const command = String(toolInput?.command || '');
106
+ if (!isMutatingBash(command)) process.exit(0);
107
+ target = 'bash';
108
+ detail = truncateSnippet(command, 400);
109
+ } else if (toolName === 'Edit') {
110
+ target = toolInput.file_path || null;
111
+ detail = `-- ${truncateSnippet(toolInput.old_string, 140)}\n++ ${truncateSnippet(toolInput.new_string, 140)}`;
112
+ } else if (toolName === 'Write') {
113
+ target = toolInput.file_path || null;
114
+ detail = `WRITE ${String(toolInput.content || '').length} chars`;
115
+ } else if (toolName === 'MultiEdit') {
116
+ target = toolInput.file_path || null;
117
+ const editsCount = Array.isArray(toolInput.edits) ? toolInput.edits.length : 0;
118
+ detail = `MultiEdit ${editsCount} edits`;
119
+ } else if (toolName === 'NotebookEdit') {
120
+ target = toolInput.notebook_path || null;
121
+ detail = `NotebookEdit cell ${toolInput.cell_number ?? '?'}`;
122
+ }
123
+
124
+ if (!target) process.exit(0);
125
+
126
+ if (toolName !== 'Bash' && target) {
127
+ const absPath = target.startsWith('/') ? target : resolve(cwd, target);
128
+ try {
129
+ if (existsSync(absPath)) {
130
+ contentHash = sha256(readFileSync(absPath, 'utf8')).slice(0, 32);
131
+ }
132
+ } catch { /* best effort */ }
133
+ }
134
+
135
+ const record = {
136
+ ts: new Date().toISOString(),
137
+ session_id: input?.session_id || null,
138
+ tool: toolName,
139
+ agent: readLastAgent(),
140
+ task: null,
141
+ cwd,
142
+ target,
143
+ detail,
144
+ content_hash: contentHash,
145
+ prev_line_hash: readPrevLineHash(),
146
+ };
147
+
148
+ try {
149
+ mkdirSync(CX_DIR, { recursive: true });
150
+ appendFileSync(AUDIT_FILE, JSON.stringify(record) + '\n', 'utf8');
151
+ } catch { /* best effort — audit failure must not block the user's work */ }
152
+
153
+ process.exit(0);
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/hooks/bash-output-logger.mjs — persists long Bash outputs to disk and nudges
4
+ * the model to reference the log instead of re-running the command.
5
+ *
6
+ * Runs as PostToolUse on Bash. If stdout exceeds a threshold, writes the full
7
+ * output to ~/.cx/bash-logs/ and emits a short stderr note that Claude sees in
8
+ * the next turn. The current turn's conversation still contains the full output
9
+ * (hooks cannot retroactively edit past tool outputs), but subsequent turns are
10
+ * steered toward disk-backed references instead of re-running the same command.
11
+ *
12
+ * Threshold chosen conservatively at 4000 chars (~100 lines). Below that, the
13
+ * hook is a no-op.
14
+ *
15
+ * @p95ms 20
16
+ * @maxBlockingScope none (PostToolUse, non-blocking)
17
+ */
18
+ import { readFileSync, writeFileSync, mkdirSync, appendFileSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { homedir } from 'node:os';
21
+
22
+ const SIZE_THRESHOLD_CHARS = 4000;
23
+ const LOG_DIR = join(homedir(), '.cx', 'bash-logs');
24
+ const WARN_FLAGS = join(homedir(), '.cx', 'warn-flags.txt');
25
+
26
+ let input = {};
27
+ try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
28
+
29
+ if ((input?.tool_name || '') !== 'Bash') process.exit(0);
30
+
31
+ const stdout = String(input?.tool_response?.stdout ?? '');
32
+ const stderr = String(input?.tool_response?.stderr ?? '');
33
+ const command = String(input?.tool_input?.command ?? '');
34
+ const totalSize = stdout.length + stderr.length;
35
+
36
+ if (totalSize < SIZE_THRESHOLD_CHARS) process.exit(0);
37
+
38
+ try {
39
+ mkdirSync(LOG_DIR, { recursive: true });
40
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
41
+ const logPath = join(LOG_DIR, `bash-${ts}.log`);
42
+ const payload = [
43
+ `# Command`,
44
+ command,
45
+ ``,
46
+ `# Stdout (${stdout.length} chars)`,
47
+ stdout,
48
+ ``,
49
+ `# Stderr (${stderr.length} chars)`,
50
+ stderr,
51
+ ].join('\n');
52
+ writeFileSync(logPath, payload, 'utf8');
53
+
54
+ const approxLines = stdout.split('\n').length;
55
+ const kb = Math.round(totalSize / 1024);
56
+ process.stderr.write(
57
+ `[bash-output-logger] Output was ${approxLines} lines (${kb} KB). ` +
58
+ `Full log saved to ${logPath}. ` +
59
+ `Before re-running this command, reference the log with: grep/sed/head on ${logPath}. ` +
60
+ `Prefer \`| head -N\` or \`| tail -N\` on future runs to keep context lean.\n`
61
+ );
62
+
63
+ try {
64
+ appendFileSync(
65
+ WARN_FLAGS,
66
+ `Bash output ${kb} KB saved to ${logPath} — prefer grepping the log over re-running.\n`,
67
+ );
68
+ } catch { /* best effort */ }
69
+ } catch { /* best effort */ }
70
+
71
+ process.exit(0);
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/hooks/block-no-verify.mjs — refuse `git commit/push/merge --no-verify`.
4
+ *
5
+ * Runs as PreToolUse on Bash. Pre-commit / pre-push hooks exist to keep red
6
+ * code from landing; --no-verify bypasses them entirely. If a hook is
7
+ * failing, fix the underlying issue.
8
+ *
9
+ * Vendored from block-no-verify@1.1.2 (Elastic-2.0) to drop the per-Bash-call
10
+ * npx cold-start tax and remove the third-party dependency for ~30 lines of
11
+ * logic that we already maintain comment + audit policy for.
12
+ *
13
+ * @p95ms 5
14
+ * @maxBlockingScope PreToolUse
15
+ */
16
+ import { readFileSync } from 'node:fs';
17
+
18
+ let input = {};
19
+ try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
20
+
21
+ const command = String(input?.tool_input?.command || '').trim();
22
+ if (!command) process.exit(0);
23
+
24
+ const isGitCommit = /^git\s+commit\b/.test(command);
25
+ const isGitPushMergeEtc = /^git\s+(push|merge|am|rebase|cherry-pick|notes|revert|tag)\b/.test(command);
26
+
27
+ if (!isGitCommit && !isGitPushMergeEtc) process.exit(0);
28
+
29
+ const hasLongFlag = /(?:^|\s)--no-verify(?:\s|=|$)/.test(command);
30
+ const hasShortN = isGitCommit && /(?:^|\s)-n(?:\s|$)/.test(command);
31
+
32
+ if (hasLongFlag || hasShortN) {
33
+ process.stderr.write(
34
+ `[block-no-verify] BLOCKED: --no-verify bypasses pre-commit / pre-push hooks.\n` +
35
+ `Command: ${command.slice(0, 200)}\n` +
36
+ `If a hook is failing, fix the underlying issue instead of skipping it.\n`,
37
+ );
38
+ process.exit(2);
39
+ }
40
+
41
+ process.exit(0);
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/hooks/ci-status-check.mjs — UserPromptSubmit hook: inject remote CI status into agent context.
4
+ *
5
+ * Queries `gh run list --branch=<current> --limit=1` once per user prompt and,
6
+ * if the last run on the current branch failed, prints a stderr line that the
7
+ * agent will see in its next observation. Without this, an agent has no way
8
+ * to know remote CI is red without explicitly running gh — which means failed
9
+ * jobs go unnoticed until the user catches them. Non-blocking.
10
+ *
11
+ * Cached on disk for 60s so frequent prompts don't spam the gh API.
12
+ * Skipped silently if gh is not installed, not authenticated, or the dir is
13
+ * not a git repo.
14
+ *
15
+ * @p95ms 4000
16
+ * @maxBlockingScope none (UserPromptSubmit, non-blocking)
17
+ */
18
+
19
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
20
+ import { execSync } from 'node:child_process';
21
+ import { homedir } from 'node:os';
22
+ import { join, dirname } from 'node:path';
23
+
24
+ const CACHE_PATH = join(homedir(), '.cx', 'ci-status-cache.json');
25
+ const CACHE_TTL_MS = 60_000;
26
+
27
+ let cwd = process.cwd();
28
+ try {
29
+ const input = JSON.parse(readFileSync(0, 'utf8'));
30
+ if (input?.cwd) cwd = input.cwd;
31
+ } catch {}
32
+
33
+ function safeExec(cmd, opts = {}) {
34
+ try { return execSync(cmd, { cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000, ...opts }).toString().trim(); }
35
+ catch { return null; }
36
+ }
37
+
38
+ const branch = safeExec('git branch --show-current');
39
+ if (!branch) process.exit(0);
40
+
41
+ const cacheKey = `${cwd}::${branch}`;
42
+ let cache = {};
43
+ try { cache = JSON.parse(readFileSync(CACHE_PATH, 'utf8')); } catch {}
44
+ const cached = cache[cacheKey];
45
+ const now = Date.now();
46
+
47
+ let conclusion = null;
48
+ let runId = null;
49
+ let runUrl = null;
50
+
51
+ if (cached && (now - cached.fetchedAt) < CACHE_TTL_MS) {
52
+ conclusion = cached.conclusion;
53
+ runId = cached.runId;
54
+ runUrl = cached.runUrl;
55
+ } else {
56
+ const json = safeExec(
57
+ `gh run list --branch=${JSON.stringify(branch)} --limit=1 --json conclusion,databaseId,url`,
58
+ { timeout: 4000 },
59
+ );
60
+ if (!json) process.exit(0);
61
+ try {
62
+ const [run] = JSON.parse(json);
63
+ if (run) {
64
+ conclusion = run.conclusion;
65
+ runId = run.databaseId;
66
+ runUrl = run.url;
67
+ cache[cacheKey] = { conclusion, runId, runUrl, fetchedAt: now };
68
+ try {
69
+ mkdirSync(dirname(CACHE_PATH), { recursive: true });
70
+ writeFileSync(CACHE_PATH, JSON.stringify(cache));
71
+ } catch {}
72
+ }
73
+ } catch {}
74
+ }
75
+
76
+ if (conclusion === 'failure') {
77
+ process.stderr.write(
78
+ `[ci] Last CI run on branch '${branch}' FAILED (run ${runId}).\n ${runUrl || ''}\n Investigate with: gh run view ${runId} --log-failed\n Do not stop the session until the failure is fixed or explicitly acknowledged (CONSTRUCT_STOP_OK_RED_CI=1).\n`,
79
+ );
80
+ }
81
+
82
+ process.exit(0);