@geraldmaron/construct 1.4.2 → 1.5.0

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 (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +55 -38
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +261 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +103 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -21,10 +21,12 @@ import { join } from 'path';
21
21
  import { writeContextState, contextMarkdownPath, contextJsonPath } from '../context-state.mjs';
22
22
  import { flushReadTrackerDeltas } from '../read-tracker-store.mjs';
23
23
  import { doctorRoot } from '../config/xdg.mjs';
24
+ import { isMainModule } from '../roots.mjs';
24
25
 
25
26
  // Section headings that are auto-generated by this hook. Any other heading in
26
27
  // the existing context.md is treated as manually authored and preserved.
27
- const AUTO_SECTION_NAMES = new Set([
28
+ // Exported so tests exercise the real set instead of a re-pasted copy.
29
+ export const AUTO_SECTION_NAMES = new Set([
28
30
  'what was in progress',
29
31
  'files changed this session',
30
32
  'decisions captured',
@@ -38,7 +40,7 @@ const AUTO_SECTION_NAMES = new Set([
38
40
  * Parse a markdown string into `[{heading, level, body}]` sections.
39
41
  * Returns an empty array for empty/missing input.
40
42
  */
41
- function parseSections(md) {
43
+ export function parseSections(md) {
42
44
  if (!md) return [];
43
45
  const lines = md.split('\n');
44
46
  const sections = [];
@@ -60,7 +62,7 @@ function parseSections(md) {
60
62
  * Extract sections from an existing context.md that were NOT auto-generated.
61
63
  * Returns a markdown string (may be empty) to append after the auto content.
62
64
  */
63
- function extractManualSections(mdPath) {
65
+ export function extractManualSections(mdPath) {
64
66
  if (!existsSync(mdPath)) return '';
65
67
  let existing = '';
66
68
  try { existing = readFileSync(mdPath, 'utf8'); } catch { return ''; }
@@ -76,7 +78,7 @@ function extractManualSections(mdPath) {
76
78
  * Merge non-empty arrays from existing context.json into the new state to
77
79
  * prevent the hook from discarding decisions/filesChanged captured manually.
78
80
  */
79
- function mergeExistingJson(jsonPath, newState) {
81
+ export function mergeExistingJson(jsonPath, newState) {
80
82
  if (!existsSync(jsonPath)) return newState;
81
83
  let existing = {};
82
84
  try { existing = JSON.parse(readFileSync(jsonPath, 'utf8')); } catch { return newState; }
@@ -89,198 +91,204 @@ function mergeExistingJson(jsonPath, newState) {
89
91
  return merged;
90
92
  }
91
93
 
92
- let input = {};
93
- try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
94
+ // Guard the hook body so importing this module for its exported helpers
95
+ // (tests, other tools) does not trigger the stdin read + process.exit(0).
96
+ // Mirrors the entrypoint guard already used in lib/hooks/rule-verifier.mjs.
94
97
 
95
- try { flushReadTrackerDeltas({ env: process.env }); } catch { /* best effort */ }
98
+ if (isMainModule(import.meta.url)) {
99
+ let input = {};
100
+ try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
96
101
 
97
- const cwd = input?.cwd || process.cwd();
98
- const transcriptPath = input?.transcript_path || '';
102
+ try { flushReadTrackerDeltas({ env: process.env }); } catch { /* best effort */ }
99
103
 
100
- const filesChanged = [];
101
- const decisions = [];
102
- const pendingTodos = [];
103
- const warnFlagsPath = join(doctorRoot(), 'warn-flags.txt');
104
- const efficiencyPath = join(doctorRoot(), 'session-efficiency.json');
105
- let lastSummary = '';
104
+ const cwd = input?.cwd || process.cwd();
105
+ const transcriptPath = input?.transcript_path || '';
106
106
 
107
- if (transcriptPath && existsSync(transcriptPath)) {
108
- try {
109
- const transcriptRaw = readFileSync(transcriptPath, 'utf8');
110
- let transcript;
107
+ const filesChanged = [];
108
+ const decisions = [];
109
+ const pendingTodos = [];
110
+ const warnFlagsPath = join(doctorRoot(), 'warn-flags.txt');
111
+ const efficiencyPath = join(doctorRoot(), 'session-efficiency.json');
112
+ let lastSummary = '';
113
+
114
+ if (transcriptPath && existsSync(transcriptPath)) {
111
115
  try {
112
- transcript = JSON.parse(transcriptRaw);
113
- } catch {
114
- transcript = transcriptRaw
115
- .split('\n')
116
- .filter(Boolean)
117
- .map((line) => {
118
- try { return JSON.parse(line); } catch { return null; }
119
- })
120
- .filter(Boolean);
121
- }
122
- const messages = Array.isArray(transcript) ? transcript : (transcript.messages || []);
123
- const recent = messages.slice(-60);
116
+ const transcriptRaw = readFileSync(transcriptPath, 'utf8');
117
+ let transcript;
118
+ try {
119
+ transcript = JSON.parse(transcriptRaw);
120
+ } catch {
121
+ transcript = transcriptRaw
122
+ .split('\n')
123
+ .filter(Boolean)
124
+ .map((line) => {
125
+ try { return JSON.parse(line); } catch { return null; }
126
+ })
127
+ .filter(Boolean);
128
+ }
129
+ const messages = Array.isArray(transcript) ? transcript : (transcript.messages || []);
130
+ const recent = messages.slice(-60);
124
131
 
125
- // Track the latest TodoWrite call to get current todo state
126
- let latestTodos = null;
132
+ // Track the latest TodoWrite call to get current todo state
133
+ let latestTodos = null;
127
134
 
128
- for (const msg of recent) {
129
- if (msg.role === 'tool' || msg.type === 'tool_result') continue;
135
+ for (const msg of recent) {
136
+ if (msg.role === 'tool' || msg.type === 'tool_result') continue;
130
137
 
131
- // Handle both array-of-blocks and plain-string content formats
132
- const blocks = Array.isArray(msg.content)
133
- ? msg.content
134
- : (typeof msg.content === 'string' ? [{ type: 'text', text: msg.content }] : []);
138
+ // Handle both array-of-blocks and plain-string content formats
139
+ const blocks = Array.isArray(msg.content)
140
+ ? msg.content
141
+ : (typeof msg.content === 'string' ? [{ type: 'text', text: msg.content }] : []);
135
142
 
136
- for (const block of blocks) {
137
- if (block.type === 'tool_use' && (block.name === 'Write' || block.name === 'Edit')) {
138
- const fp = block.input?.file_path || block.input?.path || '';
139
- if (fp && !filesChanged.includes(fp)) filesChanged.push(fp);
140
- }
141
- if (block.type === 'tool_use' && block.name === 'TodoWrite') {
142
- const todos = block.input?.todos;
143
- if (Array.isArray(todos)) latestTodos = todos;
144
- }
145
- if (block.type === 'text' && msg.role === 'assistant') {
146
- const text = (block.text || '').trim();
147
- // Prefer the last substantive response (>80 chars) over short one-liners
148
- if (text.length > 80 || (!lastSummary && text.length > 0)) {
149
- lastSummary = text.slice(0, 400);
143
+ for (const block of blocks) {
144
+ if (block.type === 'tool_use' && (block.name === 'Write' || block.name === 'Edit')) {
145
+ const fp = block.input?.file_path || block.input?.path || '';
146
+ if (fp && !filesChanged.includes(fp)) filesChanged.push(fp);
147
+ }
148
+ if (block.type === 'tool_use' && block.name === 'TodoWrite') {
149
+ const todos = block.input?.todos;
150
+ if (Array.isArray(todos)) latestTodos = todos;
150
151
  }
151
- const decisionMatches = text.match(/(?:^|\n)(?:DECISION:|Decision:|\*\*Decision\*\*:)\s*([^\n]+)/g) || [];
152
- for (const match of decisionMatches) {
153
- const cleaned = match.replace(/^\n?/, '').replace(/^(?:DECISION:|Decision:|\*\*Decision\*\*:)\s*/, '').trim();
154
- if (cleaned && !decisions.includes(cleaned)) decisions.push(cleaned);
152
+ if (block.type === 'text' && msg.role === 'assistant') {
153
+ const text = (block.text || '').trim();
154
+ // Prefer the last substantive response (>80 chars) over short one-liners
155
+ if (text.length > 80 || (!lastSummary && text.length > 0)) {
156
+ lastSummary = text.slice(0, 400);
157
+ }
158
+ const decisionMatches = text.match(/(?:^|\n)(?:DECISION:|Decision:|\*\*Decision\*\*:)\s*([^\n]+)/g) || [];
159
+ for (const match of decisionMatches) {
160
+ const cleaned = match.replace(/^\n?/, '').replace(/^(?:DECISION:|Decision:|\*\*Decision\*\*:)\s*/, '').trim();
161
+ if (cleaned && !decisions.includes(cleaned)) decisions.push(cleaned);
162
+ }
155
163
  }
156
164
  }
157
165
  }
158
- }
159
166
 
160
- // Extract pending/in_progress todos from latest TodoWrite
161
- if (latestTodos) {
162
- for (const todo of latestTodos) {
163
- if (todo.status === 'pending' || todo.status === 'in_progress') {
164
- pendingTodos.push(todo);
167
+ // Extract pending/in_progress todos from latest TodoWrite
168
+ if (latestTodos) {
169
+ for (const todo of latestTodos) {
170
+ if (todo.status === 'pending' || todo.status === 'in_progress') {
171
+ pendingTodos.push(todo);
172
+ }
165
173
  }
166
174
  }
167
- }
168
175
 
169
- } catch { /* best effort */ }
170
- }
176
+ } catch { /* best effort */ }
177
+ }
178
+
179
+ const warnFlags = (() => {
180
+ try { return readFileSync(warnFlagsPath, 'utf8').trim(); } catch { return ''; }
181
+ })();
171
182
 
172
- const warnFlags = (() => {
173
- try { return readFileSync(warnFlagsPath, 'utf8').trim(); } catch { return ''; }
174
- })();
175
-
176
- const efficiencySummary = (() => {
177
- try {
178
- const stats = JSON.parse(readFileSync(efficiencyPath, 'utf8'));
179
- const parts = [
180
- `${stats.readCount || 0} reads`,
181
- `${stats.uniqueFileCount || 0} unique files`,
182
- ];
183
- if (stats.repeatedReadCount) parts.push(`${stats.repeatedReadCount} repeated reads`);
184
- if (stats.largeReadCount) parts.push(`${stats.largeReadCount} large reads`);
185
- if (stats.totalBytesRead) parts.push(`${Math.round(stats.totalBytesRead / 1024)} KB read`);
186
- return parts.join(' · ');
187
- } catch {
188
- return '';
183
+ const efficiencySummary = (() => {
184
+ try {
185
+ const stats = JSON.parse(readFileSync(efficiencyPath, 'utf8'));
186
+ const parts = [
187
+ `${stats.readCount || 0} reads`,
188
+ `${stats.uniqueFileCount || 0} unique files`,
189
+ ];
190
+ if (stats.repeatedReadCount) parts.push(`${stats.repeatedReadCount} repeated reads`);
191
+ if (stats.largeReadCount) parts.push(`${stats.largeReadCount} large reads`);
192
+ if (stats.totalBytesRead) parts.push(`${Math.round(stats.totalBytesRead / 1024)} KB read`);
193
+ return parts.join(' · ');
194
+ } catch {
195
+ return '';
196
+ }
197
+ })();
198
+
199
+ const projectName = cwd.split('/').pop() || 'project';
200
+ const date = new Date().toISOString().slice(0, 16).replace('T', ' ');
201
+
202
+ const contextLines = [
203
+ `# Session Context`,
204
+ `Last saved: ${date}`,
205
+ ``,
206
+ `## What was in progress`,
207
+ lastSummary || '(no summary available)',
208
+ ``,
209
+ ];
210
+
211
+ if (filesChanged.length) {
212
+ contextLines.push('## Files changed this session');
213
+ for (const f of filesChanged.slice(0, 20)) contextLines.push(`- ${f}`);
214
+ contextLines.push('');
189
215
  }
190
- })();
191
-
192
- const projectName = cwd.split('/').pop() || 'project';
193
- const date = new Date().toISOString().slice(0, 16).replace('T', ' ');
194
-
195
- const contextLines = [
196
- `# Session Context`,
197
- `Last saved: ${date}`,
198
- ``,
199
- `## What was in progress`,
200
- lastSummary || '(no summary available)',
201
- ``,
202
- ];
203
-
204
- if (filesChanged.length) {
205
- contextLines.push('## Files changed this session');
206
- for (const f of filesChanged.slice(0, 20)) contextLines.push(`- ${f}`);
207
- contextLines.push('');
208
- }
209
216
 
210
- if (decisions.length) {
211
- contextLines.push('## Decisions captured');
212
- for (const decision of decisions.slice(-10)) contextLines.push(`- ${decision}`);
213
- contextLines.push('');
214
- }
217
+ if (decisions.length) {
218
+ contextLines.push('## Decisions captured');
219
+ for (const decision of decisions.slice(-10)) contextLines.push(`- ${decision}`);
220
+ contextLines.push('');
221
+ }
215
222
 
216
- if (pendingTodos.length) {
217
- contextLines.push('## Pending TODOs');
218
- for (const todo of pendingTodos) {
219
- const status = todo.status === 'in_progress' ? '[~]' : '[ ]';
220
- contextLines.push(`${status} ${todo.content || todo.title || todo.text || JSON.stringify(todo)}`);
223
+ if (pendingTodos.length) {
224
+ contextLines.push('## Pending TODOs');
225
+ for (const todo of pendingTodos) {
226
+ const status = todo.status === 'in_progress' ? '[~]' : '[ ]';
227
+ contextLines.push(`${status} ${todo.content || todo.title || todo.text || JSON.stringify(todo)}`);
228
+ }
229
+ contextLines.push('');
221
230
  }
222
- contextLines.push('');
223
- }
224
231
 
225
- if (efficiencySummary) {
226
- contextLines.push('## Session efficiency snapshot');
227
- contextLines.push(efficiencySummary);
228
- contextLines.push('');
229
- }
232
+ if (efficiencySummary) {
233
+ contextLines.push('## Session efficiency snapshot');
234
+ contextLines.push(efficiencySummary);
235
+ contextLines.push('');
236
+ }
230
237
 
231
- contextLines.push('## Open issues');
232
- contextLines.push(warnFlags || 'None');
233
-
234
- const content = contextLines.join('\n') + '\n';
235
-
236
- const projectCxDir = join(cwd, '.cx');
237
- try { mkdirSync(projectCxDir, { recursive: true }); } catch { /* exists */ }
238
-
239
- // Preserve manually-written sections from the existing context.md.
240
- const projectMdPath = contextMarkdownPath(cwd);
241
- const manualSections = extractManualSections(projectMdPath);
242
- const finalContent = content + (manualSections ? '\n' + manualSections : '');
243
-
244
- const contextJson = {
245
- source: 'pre-compact',
246
- projectName,
247
- lastSummary,
248
- filesChanged: filesChanged.slice(0, 20),
249
- decisions: decisions.slice(-10),
250
- pendingTodos: pendingTodos.slice(0, 20),
251
- efficiencySummary,
252
- warnFlags: warnFlags || 'None',
253
- };
254
-
255
- // Merge non-empty arrays from the existing JSON so manually recorded state
256
- // is not silently dropped when the transcript scan finds nothing new.
257
- const mergedJson = mergeExistingJson(contextJsonPath(cwd), contextJson);
258
-
259
- try { writeContextState(cwd, { ...mergedJson, contextSummary: lastSummary }, { markdown: finalContent }); } catch { /* best effort */ }
260
- if (decisions.length) {
261
- const decisionsDir = join(projectCxDir, 'decisions');
262
- const slug = `${date.replace(/[^0-9]/g, '')}-session-decisions`;
263
- const decisionDoc = [
264
- '# Session Decisions',
265
- `Captured: ${date}`,
266
- '',
267
- '## Decisions',
268
- ...decisions.slice(-10).map((decision) => `- ${decision}`),
269
- '',
270
- '## Files changed',
271
- ...(filesChanged.length ? filesChanged.slice(0, 20).map((file) => `- ${file}`) : ['- None captured']),
272
- '',
273
- ].join('\n');
274
- try { mkdirSync(decisionsDir, { recursive: true }); } catch { /* exists */ }
275
- try { writeFileSync(join(decisionsDir, `${slug}.md`), decisionDoc); } catch { /* best effort */ }
276
- }
238
+ contextLines.push('## Open issues');
239
+ contextLines.push(warnFlags || 'None');
240
+
241
+ const content = contextLines.join('\n') + '\n';
242
+
243
+ const projectCxDir = join(cwd, '.cx');
244
+ try { mkdirSync(projectCxDir, { recursive: true }); } catch { /* exists */ }
245
+
246
+ // Preserve manually-written sections from the existing context.md.
247
+ const projectMdPath = contextMarkdownPath(cwd);
248
+ const manualSections = extractManualSections(projectMdPath);
249
+ const finalContent = content + (manualSections ? '\n' + manualSections : '');
250
+
251
+ const contextJson = {
252
+ source: 'pre-compact',
253
+ projectName,
254
+ lastSummary,
255
+ filesChanged: filesChanged.slice(0, 20),
256
+ decisions: decisions.slice(-10),
257
+ pendingTodos: pendingTodos.slice(0, 20),
258
+ efficiencySummary,
259
+ warnFlags: warnFlags || 'None',
260
+ };
261
+
262
+ // Merge non-empty arrays from the existing JSON so manually recorded state
263
+ // is not silently dropped when the transcript scan finds nothing new.
264
+ const mergedJson = mergeExistingJson(contextJsonPath(cwd), contextJson);
265
+
266
+ try { writeContextState(cwd, { ...mergedJson, contextSummary: lastSummary }, { markdown: finalContent }); } catch { /* best effort */ }
267
+ if (decisions.length) {
268
+ const decisionsDir = join(projectCxDir, 'decisions');
269
+ const slug = `${date.replace(/[^0-9]/g, '')}-session-decisions`;
270
+ const decisionDoc = [
271
+ '# Session Decisions',
272
+ `Captured: ${date}`,
273
+ '',
274
+ '## Decisions',
275
+ ...decisions.slice(-10).map((decision) => `- ${decision}`),
276
+ '',
277
+ '## Files changed',
278
+ ...(filesChanged.length ? filesChanged.slice(0, 20).map((file) => `- ${file}`) : ['- None captured']),
279
+ '',
280
+ ].join('\n');
281
+ try { mkdirSync(decisionsDir, { recursive: true }); } catch { /* exists */ }
282
+ try { writeFileSync(join(decisionsDir, `${slug}.md`), decisionDoc); } catch { /* best effort */ }
283
+ }
277
284
 
278
- const globalCxDir = join(doctorRoot(), '.cx');
279
- try { mkdirSync(globalCxDir, { recursive: true }); } catch { /* exists */ }
280
- const globalMdPath = contextMarkdownPath(doctorRoot());
281
- const globalManualSections = extractManualSections(globalMdPath);
282
- const globalFinalContent = content + (globalManualSections ? '\n' + globalManualSections : '');
283
- const globalMergedJson = mergeExistingJson(contextJsonPath(doctorRoot()), contextJson);
284
- try { writeContextState(doctorRoot(), { ...globalMergedJson, contextSummary: lastSummary }, { markdown: globalFinalContent }); } catch { /* best effort */ }
285
+ const globalCxDir = join(doctorRoot(), '.cx');
286
+ try { mkdirSync(globalCxDir, { recursive: true }); } catch { /* exists */ }
287
+ const globalMdPath = contextMarkdownPath(doctorRoot());
288
+ const globalManualSections = extractManualSections(globalMdPath);
289
+ const globalFinalContent = content + (globalManualSections ? '\n' + globalManualSections : '');
290
+ const globalMergedJson = mergeExistingJson(contextJsonPath(doctorRoot()), contextJson);
291
+ try { writeContextState(doctorRoot(), { ...globalMergedJson, contextSummary: lastSummary }, { markdown: globalFinalContent }); } catch { /* best effort */ }
285
292
 
286
- process.exit(0);
293
+ process.exit(0);
294
+ }
@@ -29,6 +29,7 @@
29
29
  import { readFileSync, existsSync, appendFileSync, mkdirSync } from 'node:fs';
30
30
  import { dirname, join } from 'node:path';
31
31
  import { logHookFailure } from './_lib/log.mjs';
32
+ import { isMainModule } from '../roots.mjs';
32
33
 
33
34
  const AUDIT_REL = '.cx/audit.jsonl';
34
35
 
@@ -202,7 +203,7 @@ function writeAudit(cwd, payload) {
202
203
  catch (err) { logHookFailure({ hook: 'rule-verifier', err, phase: 'append-audit' }); }
203
204
  }
204
205
 
205
- if (import.meta.url === `file://${process.argv[1]}`) {
206
+ if (isMainModule(import.meta.url)) {
206
207
  let input = {};
207
208
  try { input = JSON.parse(readFileSync(0, 'utf8')); }
208
209
  catch (err) { logHookFailure({ hook: 'rule-verifier', err, phase: 'parse-stdin' }); process.exit(0); }
@@ -21,12 +21,13 @@
21
21
  */
22
22
  import { readFileSync, existsSync } from 'node:fs';
23
23
  import { join } from 'node:path';
24
+ import { resolveNonNegativeSetting } from '../env-config.mjs';
24
25
 
25
26
  // 500ms keeps the per-turn Stop-hook tax small (ADR-0029): reflection is
26
27
  // best-effort and a LanceDB write that overruns the budget is left to the
27
28
  // embed daemon's reconcile to backfill. CONSTRUCT_REFLECT_BUDGET_MS raises the
28
29
  // ceiling where the write must complete deterministically (slow disks, tests).
29
- const HARD_BUDGET_MS = Number(process.env.CONSTRUCT_REFLECT_BUDGET_MS) || 500;
30
+ const HARD_BUDGET_MS = resolveNonNegativeSetting(process.env, 'CONSTRUCT_REFLECT_BUDGET_MS', 500);
30
31
  const startedAt = Date.now();
31
32
 
32
33
  // Opt-out before any heavy work — the env check is cheaper than parsing input.
@@ -192,7 +192,7 @@ const header = `## Resuming — ${projectName} · ${date}
192
192
  const footer = [statusLine, recentLine].filter(Boolean).join(' · ');
193
193
 
194
194
  const body = context || (contextStale
195
- ? '## Stale context — last context.md update >7 days old. Run `construct context refresh` or rely on `memory_recent`.\n'
195
+ ? '## Stale context — last context.md update >7 days old. Edit .cx/context.md directly (or use the remember:context skill) to update it — the session-tracking-refresh hook refreshes it automatically on edit.\n'
196
196
  : '## Fresh start — no prior context found.\n');
197
197
  // Tier 3 hint — efficiency snapshot fires only when status is 'degraded'
198
198
  // (repeated-read ratio or byte threshold exceeded). 'configured' (large reads
@@ -292,8 +292,8 @@ try {
292
292
  const embedStatus = resolveEmbedStatus(process.env);
293
293
  if (embedStatus.level !== 'none') {
294
294
  embedStatusNote = `\n## Embed daemon\n${embedStatus.label} · ${embedStatus.detail}\n`;
295
- if (embedStatus.level === 'stopped' && process.env.CX_AUTO_EMBED === '1') {
296
- const result = await autoStartEmbedIfNeeded(process.env);
295
+ if (embedStatus.level === 'stopped') {
296
+ const result = await autoStartEmbedIfNeeded(process.env, undefined, undefined, process.cwd());
297
297
  if (result.started) {
298
298
  embedStatusNote = `\n## Embed daemon\nembed: auto-started (pid ${result.pid}) · background polling active\n`;
299
299
  }
@@ -13,6 +13,7 @@ import fs from "node:fs";
13
13
  import net from "node:net";
14
14
  import os from "node:os";
15
15
  import path from "node:path";
16
+ import { isMainModule } from './roots.mjs';
16
17
 
17
18
  const LOWEST_PORT = 0;
18
19
  const HIGHEST_PORT = 65535;
@@ -133,9 +134,13 @@ export function detectActiveSessions() {
133
134
  }
134
135
  } catch { /* not logged in or gh cli missing */ }
135
136
 
136
- // Anthropic / Claude Code check
137
+ // Anthropic / Claude Code check. MCP server definitions live in
138
+ // ~/.claude.json's top-level `mcpServers`, not settings.json (construct-ranh);
139
+ // settings.json is still checked for `primaryModel` and any legacy `mcpServers`
140
+ // a user configured by hand before that was confirmed inert.
137
141
  const home = os.homedir();
138
142
  const claudeSettings = path.join(home, ".claude", "settings.json");
143
+ const claudeUserConfig = path.join(home, ".claude.json");
139
144
  try {
140
145
  if (fs.existsSync(claudeSettings)) {
141
146
  const settings = JSON.parse(fs.readFileSync(claudeSettings, "utf8"));
@@ -143,6 +148,12 @@ export function detectActiveSessions() {
143
148
  active.push("anthropic");
144
149
  }
145
150
  }
151
+ if (!active.includes("anthropic") && fs.existsSync(claudeUserConfig)) {
152
+ const userConfig = JSON.parse(fs.readFileSync(claudeUserConfig, "utf8"));
153
+ if (userConfig?.mcpServers) {
154
+ active.push("anthropic");
155
+ }
156
+ }
146
157
  } catch { /* invalid or missing config */ }
147
158
 
148
159
  return active;
@@ -278,7 +289,7 @@ export function printHostCapabilities(hosts = detectHostCapabilities()) {
278
289
  }
279
290
  }
280
291
 
281
- if (import.meta.url === `file://${process.argv[1]}`) {
292
+ if (isMainModule(import.meta.url)) {
282
293
  const args = new Set(process.argv.slice(2));
283
294
  if (args.has('--json')) {
284
295
  process.stdout.write(formatHostCapabilitiesJson());
@@ -1,20 +1,32 @@
1
1
  /**
2
2
  * lib/host-disposition.mjs — single source of truth for the disposition of
3
- * every artifact Construct creates in a host project (ADR-0027 §1).
3
+ * every artifact Construct creates in a host project (ADR-0027 §1, extended
4
+ * by ADR-0066 §"Decision" for the machine-scoped heavy-state split).
4
5
  *
5
6
  * Scope model (see docs/guides/concepts/project-scopes.md):
6
- * templates/, docs/ — package/repo durable sources (committed)
7
- * .construct/ — per-project launcher (ignored)
8
- * .cx/ — per-project runtime: intake, oracle, observations, demos (ignored)
9
- * ~/.construct/ user credentials and doctor state
10
- * ~/.cx/ — user-scope telemetry fallback
7
+ * templates/, docs/ — package/repo durable sources (committed)
8
+ * .construct/ — per-project launcher (ignored)
9
+ * .cx/ — per-project config-layer + small runtime
10
+ * markers: context.md/json, workflow.json,
11
+ * intake, oracle, observations, knowledge,
12
+ * graph, demos (ignored)
13
+ * ~/.construct/projects/<key>/ — per-project machine-scoped heavy state:
14
+ * traces, orchestration runs, worker logs,
15
+ * the LanceDB vector index, docling/whisper
16
+ * bootstrap venvs (ADR-0066)
17
+ * ~/.construct/ — user credentials and doctor state
18
+ * ~/.cx/ — user-scope telemetry fallback
11
19
  *
12
20
  * `IGNORED_PATTERNS` enumerates machine-specific, sync-regenerated artifacts
13
21
  * that must never be committed; the init `.gitignore` writer and the
14
22
  * `gitignore-coverage` reconciliation both read from here, and uninstall /
15
23
  * adapter-prune consume `ADAPTER_DIRS`. Keep this 1:1 with the host-relevant
16
24
  * subset of Construct's own repo .gitignore so forward-fix and backward-repair
17
- * never diverge.
25
+ * never diverge. `.cx/` covers both the config-layer files and the small
26
+ * runtime markers that remain project-local post-ADR-0066 — the heavy
27
+ * categories that moved to `~/.construct/projects/<key>/` are resolved
28
+ * through `lib/state-root.mjs`, not listed here, since they were never
29
+ * project-relative paths to gitignore in the first place.
18
30
  */
19
31
 
20
32
  import fs from 'node:fs';
@@ -0,0 +1,92 @@
1
+ /**
2
+ * lib/identity.mjs — actor and service identity for the control plane.
3
+ *
4
+ * Resolves human vs service identity from request headers (X-Construct-Actor-Id,
5
+ * -Tenant-Id, -Session-Id) or env fallback, and validates it per deployment mode
6
+ * (team/enterprise reject implicit-solo identity). Feeds policy, approval, and audit.
7
+ */
8
+
9
+ import { getDeploymentMode } from './deployment-mode.mjs';
10
+ import os from 'node:os';
11
+
12
+ export class IdentityError extends Error {
13
+ constructor(message) { super(message); this.name = 'IdentityError'; }
14
+ }
15
+
16
+ export function serviceIdentity({ serviceId, deploymentMode, role, source } = {}) {
17
+ return {
18
+ type: 'service',
19
+ serviceId: serviceId || 'construct-mcp',
20
+ deploymentMode: deploymentMode || 'solo',
21
+ pid: process.pid,
22
+ role: role || null,
23
+ source: source || 'implicit-solo',
24
+ };
25
+ }
26
+
27
+ export function humanIdentity({ userId, tenantId, sessionId, role, source } = {}) {
28
+ return {
29
+ type: 'human',
30
+ userId: userId || null,
31
+ tenantId: tenantId || null,
32
+ sessionId: sessionId || null,
33
+ role: role || null,
34
+ source: source || 'headers',
35
+ };
36
+ }
37
+
38
+ export function resolveIdentity(params = {}, { env = process.env, cwd } = {}) {
39
+ const mode = getDeploymentMode(env, { cwd });
40
+ const meta = params?._meta || {};
41
+
42
+ const actorId = meta['X-Construct-Actor-Id'] || '';
43
+ const tenantId = meta['X-Construct-Tenant-Id'] || '';
44
+ const sessionId = meta['X-Construct-Session-Id'] || '';
45
+ const hasHeaders = Boolean(actorId);
46
+
47
+ if ((mode === 'team' || mode === 'enterprise') && !hasHeaders) {
48
+ const role = env.CONSTRUCT_ROLE;
49
+ if (role) {
50
+ return serviceIdentity({ serviceId: `role:${role}`, deploymentMode: mode, role, source: 'env-fallback' });
51
+ }
52
+ throw new IdentityError(`Missing actor identity in ${mode} mode. Set X-Construct-Actor-Id header or CONSTRUCT_ROLE env var.`);
53
+ }
54
+
55
+ if (hasHeaders) {
56
+ const isHuman = actorId.includes('@');
57
+ if (isHuman) {
58
+ return humanIdentity({ userId: actorId, tenantId: tenantId || null, sessionId: sessionId || null, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
59
+ }
60
+ return serviceIdentity({ serviceId: actorId, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
61
+ }
62
+
63
+ return serviceIdentity({ serviceId: `local-${os.hostname()}`, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'implicit-solo' });
64
+ }
65
+
66
+ export function identityRole(identity) {
67
+ return identity?.role || null;
68
+ }
69
+
70
+ export function validateIdentity(identity, mode) {
71
+ if (!identity) return 'Identity is required';
72
+ if (!identity.type) return 'Identity type is required';
73
+ if (identity.type === 'human' && !identity.userId) return 'Human identity requires userId';
74
+ if (identity.type === 'service' && !identity.serviceId) return 'Service identity requires serviceId';
75
+ if ((mode === 'team' || mode === 'enterprise') && identity.source === 'implicit-solo') {
76
+ return `Implicit identity not allowed in ${mode} mode`;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ export function identityToRecord(identity) {
82
+ if (!identity) return {};
83
+ return {
84
+ type: identity.type,
85
+ userId: identity.userId || null,
86
+ serviceId: identity.serviceId || null,
87
+ tenantId: identity.tenantId || null,
88
+ sessionId: identity.sessionId || null,
89
+ role: identity.role || null,
90
+ deploymentMode: identity.deploymentMode || null,
91
+ };
92
+ }