@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
@@ -2,41 +2,83 @@
2
2
  /**
3
3
  * lib/hooks/session-start.mjs — Session start hook — emits resumable project context at the start of each session.
4
4
  *
5
- * Runs at session start. Reads .cx/context.json, workflow state, git status, and efficiency log to produce a structured resume message. Non-blocking — always exits 0.
5
+ * Runs at session start. Reads .cx/context.json, git status, and efficiency log to produce a structured resume message. Non-blocking — always exits 0.
6
+ *
7
+ * @p95ms 300
8
+ * @maxBlockingScope SessionStart
6
9
  */
7
- import { readFileSync, existsSync } from 'fs';
10
+ import { readFileSync, existsSync, statSync, writeFileSync } from 'fs';
8
11
  import { execSync } from 'child_process';
9
12
  import { homedir } from 'os';
10
13
  import { dirname, join, resolve } from 'path';
11
14
  import { fileURLToPath } from 'url';
12
- import { loadWorkflow, summarizeWorkflow, alignmentFindings } from '../workflow-state.mjs';
15
+ import { loadConstructEnv } from '../env-config.mjs';
13
16
  import { buildCompactEfficiencyDigest, readEfficiencyLog } from '../efficiency.mjs';
14
17
  import { readContextState, contextSummaryLine } from '../context-state.mjs';
18
+ import { createSession, lastSession, buildResumeContext } from '../session-store.mjs';
19
+ import { listObservations, searchObservations } from '../observation-store.mjs';
20
+ import { countEntities } from '../entity-store.mjs';
21
+ import { logHookFailure } from './_lib/log.mjs';
15
22
 
16
23
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
17
24
  const CONSTRUCT_BIN = resolve(MODULE_DIR, '..', '..', 'bin', 'construct');
18
25
 
19
26
  let input = {};
20
- try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
27
+ try { input = JSON.parse(readFileSync(0, 'utf8')); }
28
+ catch (err) { logHookFailure({ hook: 'session-start', err, phase: 'parse' }); process.exit(0); }
29
+
30
+ // Merge config.env into process.env so provider source hints and embed status
31
+ // reflect the operator's actual configuration, not just the shell environment.
32
+ try {
33
+ const merged = loadConstructEnv({ warn: false });
34
+ for (const [k, v] of Object.entries(merged)) {
35
+ if (!(k in process.env)) process.env[k] = v;
36
+ }
37
+ } catch { /* best effort — non-blocking */ }
21
38
 
22
39
  const cwd = input?.cwd || process.cwd();
23
40
  const projectName = cwd.split('/').pop() || 'project';
24
41
  const date = new Date().toISOString().slice(0, 10);
25
42
 
26
- // Load context — project-local first, global fallback
43
+ // Tiered injection model:
44
+ // Tier 1 — always inject (header, branch, status)
45
+ // Tier 2 — inject only when fresh and meaningful (context.md if <7d old,
46
+ // skill scope, recent drops)
47
+ // Tier 3 — surface as a one-line hint pointing at an MCP tool
48
+ // (memory_recent, efficiency_snapshot) instead of injecting the
49
+ // full payload every session.
50
+ const FRESHNESS_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
51
+ function fileIsFresh(path) {
52
+ try {
53
+ if (!existsSync(path)) return false;
54
+ return Date.now() - statSync(path).mtimeMs < FRESHNESS_WINDOW_MS;
55
+ } catch { return false; }
56
+ }
57
+
58
+ // Load context — project-local first, global fallback. Gate by 7-day freshness:
59
+ // stale context files inject only the digest line as fallback (Tier 2 → Tier 1
60
+ // degradation), full body only when modified within the freshness window.
27
61
  const localCtx = join(cwd, '.cx', 'context.md');
28
62
  const globalCtx = join(homedir(), '.cx', 'context.md');
29
63
  let context = '';
64
+ let contextStale = false;
30
65
  const localState = readContextState(cwd);
31
66
  const globalState = localState ? null : readContextState(homedir());
32
67
  const stateContext = localState || globalState;
68
+ const stateContextPath = localState ? localCtx : globalCtx;
33
69
  if (stateContext?.markdown) {
34
- const raw = String(stateContext.markdown);
35
- context = raw.length > 1800 ? `${raw.slice(0, 400)}\n…\n${raw.slice(-1400)}` : raw;
36
- } else if (existsSync(localCtx)) {
70
+ if (fileIsFresh(stateContextPath) || !existsSync(stateContextPath)) {
71
+ const raw = String(stateContext.markdown);
72
+ context = raw.length > 1800 ? `${raw.slice(0, 400)}\n…\n${raw.slice(-1400)}` : raw;
73
+ } else {
74
+ contextStale = true;
75
+ }
76
+ } else if (existsSync(localCtx) && fileIsFresh(localCtx)) {
37
77
  try { const raw = readFileSync(localCtx, 'utf8'); context = raw.length > 1800 ? `${raw.slice(0, 400)}\n…\n${raw.slice(-1400)}` : raw; } catch { /* best effort */ }
38
- } else if (existsSync(globalCtx)) {
78
+ } else if (existsSync(globalCtx) && fileIsFresh(globalCtx)) {
39
79
  try { const raw = readFileSync(globalCtx, 'utf8'); context = raw.length > 1800 ? `${raw.slice(0, 400)}\n…\n${raw.slice(-1400)}` : raw; } catch { /* best effort */ }
80
+ } else if (existsSync(localCtx) || existsSync(globalCtx)) {
81
+ contextStale = true;
40
82
  }
41
83
  const contextSummary = contextSummaryLine(stateContext);
42
84
 
@@ -52,6 +94,68 @@ try {
52
94
  recentCommits = execSync(`git -C "${cwd}" log --oneline -3 2>/dev/null`, { timeout: 5000 }).toString().trim();
53
95
  } catch { /* best effort */ }
54
96
 
97
+
98
+ // Session persistence — create session and load last for resume context.
99
+ // Cap resume note at 600 chars; the full record is available via session_load.
100
+ let sessionResumeNote = '';
101
+ try {
102
+ createSession(cwd, { project: projectName, platform: 'claude-code' });
103
+ const prev = lastSession(cwd, projectName);
104
+ if (prev && prev.status !== 'active') {
105
+ const resumeCtx = buildResumeContext(prev);
106
+ if (resumeCtx) {
107
+ const capped = resumeCtx.length > 600
108
+ ? resumeCtx.slice(0, 580) + '\n…(truncated — call `session_load` for full record)'
109
+ : resumeCtx;
110
+ sessionResumeNote = '\n## Last session context\n' + capped + '\n';
111
+ }
112
+ }
113
+ } catch { /* best effort — session store is non-blocking */ }
114
+
115
+ // Tier 2 — inject top observations that are relevant to the current project.
116
+ // Filter out trivial placeholder summaries. Show at most 2 inline; rest are
117
+ // available on demand via memory_search / memory_recent.
118
+ // Skipped when CONSTRUCT_MEMORY=off (enables A/B comparison with memory disabled).
119
+ let observationsNote = '';
120
+ try {
121
+ if (process.env.CONSTRUCT_MEMORY !== 'off') {
122
+ const PLACEHOLDER_RE = /^(implement|done|completed|session completed|in_progress)[\s:.]*(done|ok|completed)?$/i;
123
+ const searched = searchObservations(cwd, projectName, { project: projectName, limit: 10 });
124
+ const allObs = searched.length > 0 ? searched : listObservations(cwd, { project: projectName, limit: 20 });
125
+ const meaningful = allObs.filter((o) =>
126
+ o.summary && o.summary.length > 10 && !PLACEHOLDER_RE.test(o.summary.trim())
127
+ );
128
+ const entityCount = countEntities(cwd, { project: projectName });
129
+
130
+ if (meaningful.length > 0) {
131
+ const top2 = meaningful.slice(0, 2).map((o) => `- [${o.category}] ${o.summary}`).join('\n');
132
+ const rest = meaningful.length > 2
133
+ ? ` · ${meaningful.length - 2} more via \`memory_search\``
134
+ : '';
135
+ const entitySuffix = entityCount > 0 ? ` · ${entityCount} entit${entityCount === 1 ? 'y' : 'ies'} tracked` : '';
136
+ observationsNote = `\n## Prior observations\n${top2}\n${meaningful.length} total${rest}${entitySuffix}\n`;
137
+ } else {
138
+ const total = allObs.length;
139
+ if (total > 0) {
140
+ const entitySuffix = entityCount > 0 ? ` · ${entityCount} entit${entityCount === 1 ? 'y' : 'ies'} tracked` : '';
141
+ observationsNote = `\n## Prior observations\n${total} observation${total === 1 ? '' : 's'} available — \`memory_recent\` for recency order · \`memory_search\` for semantic lookup${entitySuffix}.\n`;
142
+ }
143
+ }
144
+ } // end CONSTRUCT_MEMORY check
145
+ } catch { /* best effort */ }
146
+
147
+ // Record memory injection stats for the Stop hook to persist into .cx/memory-stats.jsonl.
148
+ try {
149
+ const memStatsPath = join(homedir(), '.cx', 'session-memory-stats.json');
150
+ const injected = observationsNote ? (observationsNote.match(/^- /gm) || []).length : 0;
151
+ writeFileSync(memStatsPath, JSON.stringify({
152
+ project: projectName,
153
+ observationsInjected: injected,
154
+ memoryEnabled: process.env.CONSTRUCT_MEMORY !== 'off',
155
+ at: new Date().toISOString(),
156
+ }));
157
+ } catch { /* best effort */ }
158
+
55
159
  // Pending typecheck warning
56
160
  const tcPath = join(homedir(), '.cx', 'pending-typecheck.txt');
57
161
  let pendingNote = '';
@@ -68,28 +172,183 @@ const recentLine = recentCommits
68
172
  ? `Recent: ${recentCommits.split('\n').join(' · ')}`
69
173
  : '';
70
174
 
71
- const header = `## Resuming ${projectName} · ${date}\nNote: You are currently working on the **${projectName}** project.`;
175
+ // Working branchshow prominently at session start so it is visible
176
+ // before any mutating operation is even proposed.
177
+ let workingBranch = '(unknown)';
178
+ try {
179
+ workingBranch = execSync(`git -C "${cwd}" rev-parse --abbrev-ref HEAD 2>/dev/null`, { timeout: 5000 }).toString().trim() || '(unknown)';
180
+ } catch { /* not a git repo */ }
181
+ // Branch + project anchor mutating operations; the read-only/destructive
182
+ // policy lives in CLAUDE.md and the persona, so the prelude stays compact.
183
+
184
+ const header = `## Resuming — ${projectName} · ${date}
185
+ ## Working branch: **${workingBranch}**`;
72
186
  const footer = [statusLine, recentLine].filter(Boolean).join(' · ');
73
187
 
74
- let workflowNote = '';
188
+ const body = context || (contextStale
189
+ ? '## Stale context — last context.md update >7 days old. Run `construct context refresh` or rely on `memory_recent`.\n'
190
+ : '## Fresh start — no prior context found.\n');
191
+ // Tier 3 hint — efficiency snapshot fires only when status is 'degraded'
192
+ // (repeated-read ratio or byte threshold exceeded). 'configured' (large reads
193
+ // but within budget) is advisory and not worth a session-start interrupt.
194
+ const efficiency = buildCompactEfficiencyDigest(readEfficiencyLog(homedir()));
195
+ const efficiencyNote = (efficiency?.compact && efficiency?.status === 'degraded')
196
+ ? `\n## Session efficiency\n${efficiency.compact}\nFull snapshot via \`efficiency_snapshot\` tool.\n`
197
+ : '';
198
+ // Context digest is redundant when the full context.md body was already
199
+ // rendered above. Only surface the digest line as a *fallback* when no
200
+ // context body was available — it's a shorter substitute, not an addition.
201
+ const stateNote = (contextSummary && !context) ? `\n## Context digest\n${contextSummary}\n` : '';
202
+
203
+ // Single-line coordination reminder. The full rule (one writer per file,
204
+ // coordinate via tracker + plan.md) is in the persona prompt.
205
+
206
+ const concurrencyNote = '\nCoordination: one writer per file — see tracker and `plan.md` for ownership.\n';
207
+
208
+ // Tier 3 hint — skill scope lists can be large (20+ entries). The model
209
+ // doesn't need the full list on every session; a one-line hint is enough to
210
+ // surface that a classification exists. Full list via `construct skills scope`.
211
+ let skillScopeNote = '';
75
212
  try {
76
- const workflow = loadWorkflow(cwd);
77
- if (workflow) {
78
- const findings = alignmentFindings(workflow);
79
- const high = findings.filter((finding) => finding.severity === 'HIGH').length;
80
- const medium = findings.filter((finding) => finding.severity === 'MEDIUM').length;
81
- workflowNote = `\n## Active Construct Workflow\n${summarizeWorkflow(workflow)}\nAlignment: ${high ? `${high} high issue${high === 1 ? '' : 's'}` : 'no high issues'}${medium ? `, ${medium} warning${medium === 1 ? '' : 's'}` : ''}\n`;
213
+ const profilePath = join(cwd, '.cx', 'skills-profile.json');
214
+ if (existsSync(profilePath)) {
215
+ const profile = JSON.parse(readFileSync(profilePath, 'utf8'));
216
+ const irrelevant = profile?.recommendedDisable || profile?.decision?.irrelevant || [];
217
+ const tags = profile?.profile?.tags || [];
218
+ if (irrelevant.length > 0) {
219
+ skillScopeNote =
220
+ '\n## Project skill scope\n' +
221
+ `Stack: ${tags.join(', ') || 'unspecified'} · ${irrelevant.length} installed skill${irrelevant.length === 1 ? '' : 's'} out of scope — run \`construct skills scope\` for the full list.\n`;
222
+ }
82
223
  }
83
- } catch {
84
- // best effort only
85
- }
224
+ } catch { /* best effort */ }
225
+
226
+ // Recent drop-zone files — surface very recent downloads so the user can
227
+ // ingest them with `construct drop` instead of manually referencing paths.
228
+ // Honors CONSTRUCT_DROP_DIRS; default watches Downloads/Desktop/Documents.
229
+ let dropNote = '';
230
+ try {
231
+ const { collectCandidates } = await import('../drop.mjs');
232
+ const { homedir } = await import('node:os');
233
+ const { join: pathJoin } = await import('node:path');
234
+ const { existsSync: fsExists } = await import('node:fs');
235
+ const watchDirs = (process.env.CONSTRUCT_DROP_DIRS
236
+ ? process.env.CONSTRUCT_DROP_DIRS.split(':').map((s) => s.trim()).filter(Boolean)
237
+ : [pathJoin(homedir(), 'Downloads'), pathJoin(homedir(), 'Desktop'), pathJoin(homedir(), 'Documents')]
238
+ ).filter((d) => fsExists(d));
239
+ const recent = collectCandidates({
240
+ dirs: watchDirs,
241
+ sinceMs: 60 * 60 * 1000, // 1 hour — only very recent drops
242
+ limit: 3,
243
+ });
244
+ if (recent.length > 0) {
245
+ const items = recent.map((r) => `- ${r.name} (${r.ext || 'file'}, ${Math.round((Date.now() - r.mtimeMs) / 60000)}m ago)`).join('\n');
246
+ dropNote = '\n## Recent drop-zone files\n' +
247
+ 'Files saved in the last hour that you may want to reference:\n' +
248
+ items + '\n' +
249
+ 'Run `construct drop` to ingest the most recent, or `construct drop --list` to see more.\n';
250
+ }
251
+ } catch { /* best effort */ }
252
+
253
+ // Single-line recall trigger so Construct-specific questions go through
254
+ // `knowledge_search` instead of training-data recall. Full policy in persona.
255
+
256
+ const selfKnowledgeNote = '\nSelf-knowledge: call `knowledge_search` for any Construct-specific question.\n';
257
+
258
+ // Configured provider sources hint — tell Construct what repos/projects are
259
+ // wired so it knows to call `provider_fetch` instead of saying "no context".
260
+ let sourcesNote = '';
261
+ let embedStatusNote = '';
262
+ try {
263
+ const repos = (process.env.GITHUB_REPOS ?? '').split(',').map(r => r.trim()).filter(Boolean);
264
+ const jiraConfigured = !!(process.env.JIRA_BASE_URL);
265
+ const linearConfigured = !!(process.env.LINEAR_API_KEY);
266
+ const hasSources = repos.length > 0 || jiraConfigured || linearConfigured;
267
+ if (hasSources) {
268
+ const parts = [];
269
+ if (repos.length > 0) parts.push(`GitHub repos: ${repos.join(', ')}`);
270
+ if (jiraConfigured) parts.push(`Jira: ${process.env.JIRA_BASE_URL}`);
271
+ if (linearConfigured) parts.push('Linear: configured');
272
+ // Surface which sources are wired so `provider_fetch` is the obvious
273
+ // tool for questions about them. The "fetch first, don't answer from
274
+ // memory" policy lives in the persona.
275
+ sourcesNote = '\nProvider sources wired: ' + parts.join(' · ') +
276
+ '. Use `provider_fetch` for any question about them.\n';
277
+ }
278
+
279
+ // Embed daemon status — surface as a one-liner so operator always knows state
280
+ const { resolveEmbedStatus, autoStartEmbedIfNeeded } = await import('../embed/cli.mjs');
281
+ const embedStatus = resolveEmbedStatus(process.env);
282
+ if (embedStatus.level !== 'none') {
283
+ embedStatusNote = `\n## Embed daemon\n${embedStatus.label} · ${embedStatus.detail}\n`;
284
+ // Auto-start if CX_AUTO_EMBED=1 and daemon is stopped
285
+ if (embedStatus.level === 'stopped' && process.env.CX_AUTO_EMBED === '1') {
286
+ const result = await autoStartEmbedIfNeeded(process.env);
287
+ if (result.started) {
288
+ embedStatusNote = `\n## Embed daemon\nembed: auto-started (pid ${result.pid}) · background polling active\n`;
289
+ }
290
+ }
291
+ }
292
+ } catch { /* best effort */ }
293
+
294
+ // Role framework: drain queued events into bd issues and surface pending
295
+ // invocations. Skipped when CONSTRUCT_ROLES=off; bounded so session start
296
+ // stays fast.
297
+
298
+ let rolesNote = '';
299
+ try {
300
+ if (process.env.CONSTRUCT_ROLES !== 'off') {
301
+ const { processBacklog, listPending } = await import('../roles/gateway.mjs');
302
+ await processBacklog({ sinceMs: 60 * 60 * 1000, maxProcess: 5 });
303
+ const pendingRoles = listPending({ unresolved: true });
304
+ if (pendingRoles.length > 0) {
305
+ const lines = pendingRoles.slice(-5).map(
306
+ (p) => `- ${p.cxId} · ${p.bdIssueId || '(no-bd)'} · ${p.eventType} — ${p.summary || ''}`
307
+ );
308
+ rolesNote = `\n## Pending role invocations (${pendingRoles.length})\n${lines.join('\n')}\nDispatch via Task: see \`construct role latest\` for the brief.\n`;
309
+ }
310
+ }
311
+ } catch { /* best effort */ }
312
+
313
+ // R&D intake queue + MCP broker status. Same builders feed the opencode
314
+ // plugin so both platforms surface the same prelude on session start.
315
+
316
+ let intakeReviewNote = '';
317
+ let brokerStatusNote = '';
318
+ try {
319
+ const { buildIntakePrelude, buildBrokerStatusLine } = await import('../intake/session-prelude.mjs');
320
+ intakeReviewNote = buildIntakePrelude({ cwd, env: process.env });
321
+ brokerStatusNote = `\n${buildBrokerStatusLine({ env: process.env })}\n`;
322
+ } catch { /* best effort */ }
323
+
324
+ // Permission posture: surface gaps in ~/.claude/settings.json permissions.allow
325
+ // so the agent can ask the user to close them instead of stumbling into the
326
+ // classifier mid-task. Empty string when no gaps — keeps the banner small.
327
+
328
+ let permissionPostureNote = '';
329
+ try {
330
+ const { buildPermissionPostureLine } = await import('../claude-allow.mjs');
331
+ permissionPostureNote = buildPermissionPostureLine({ cwd });
332
+ } catch { /* best effort */ }
333
+
334
+ process.stdout.write(`${header}\n${body}${stateNote}${efficiencyNote}${observationsNote}${concurrencyNote}${skillScopeNote}${dropNote}${embedStatusNote}${sourcesNote}${selfKnowledgeNote}${rolesNote}${intakeReviewNote}${brokerStatusNote}${permissionPostureNote}\n${footer}${pendingNote}\n`);
335
+ // Auto-bootstrap the policy-engine gate. session-start has just emitted
336
+ // branch + recent commits + prior observations + context-state — that is
337
+ // already grounding; mark the session bootstrapped so the PreToolUse rule
338
+ // stops asking for redundant ceremony before the first mutating tool call.
339
+ try {
340
+ const sessionId = input?.session_id || input?.sessionId;
341
+ if (sessionId) {
342
+ const bootstrapPath = join(homedir(), ".cx", "bootstrap-state.json");
343
+ let state = {};
344
+ try { state = JSON.parse(readFileSync(bootstrapPath, "utf8")) || {}; } catch {}
345
+ const now = Date.now();
346
+ for (const [k, v] of Object.entries(state)) {
347
+ if (!v?.ts || now - v.ts > 24 * 60 * 60 * 1000) delete state[k];
348
+ }
349
+ state[sessionId] = { ts: now, reads: 0, bootstrap: ["session-start"], done: true };
350
+ try { writeFileSync(bootstrapPath, JSON.stringify(state)); } catch {}
351
+ }
352
+ } catch {}
86
353
 
87
- const body = context || '## Fresh start — no prior context found.\n';
88
- const efficiency = buildCompactEfficiencyDigest(readEfficiencyLog(homedir()));
89
- const efficiencyNote = efficiency?.compact
90
- ? `\n## Session efficiency\n${efficiency.compact}\n`
91
- : '';
92
- const stateNote = contextSummary ? `\n## Context digest\n${contextSummary}\n` : '';
93
- const memoryDirective = `\n## Required first actions\nBefore responding: call memory_search("${projectName}") to retrieve prior session context and user preferences. Apply any relevant results immediately. If context is degraded, compact before broad rereads.\n`;
94
- process.stdout.write(`${header}\n${body}${stateNote}${workflowNote}${efficiencyNote}${memoryDirective}\n${footer}${pendingNote}\n`);
95
354
  process.exit(0);