@geraldmaron/construct 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/.env.example +9 -6
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1000 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +523 -36
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. package/rules/zh/testing.md +0 -34
@@ -0,0 +1,11 @@
1
+ # bootstrap policy
2
+ # Controls session bootstrap enforcement (write/bash gating until session grounded).
3
+
4
+ rule: bootstrap
5
+ hook_event: PreToolUse
6
+ blocked_tools: [Write, Edit, MultiEdit, NotebookEdit, TodoWrite, Bash]
7
+ bootstrap_trio: [workflow_status, project_context, memory_search]
8
+ read_threshold: 3
9
+ benign_bash_pattern: "^(git (status|log|diff|branch|show|rev-parse)|ls|cat|head|tail|pwd|wc|file|echo|which|node --version|npm --version)\\b"
10
+ action: block
11
+ message: "Requires session bootstrap. Run workflow_status + project_context + memory_search first (or 3+ read calls)."
@@ -0,0 +1,8 @@
1
+ # drive policy
2
+ # Controls stop-blocking when drive mode is active and acceptance criteria are unmet.
3
+
4
+ rule: drive
5
+ hook_event: Stop
6
+ condition: drive_state.active == true AND drive_state.canStop != true
7
+ action: block
8
+ message: "Drive mode active — unmet criteria remain. Record evidence in .cx/drive-state.json to unblock."
@@ -0,0 +1,9 @@
1
+ # task-complete policy
2
+ # Blocks marking a workflow task as done without verification evidence.
3
+
4
+ rule: task-complete
5
+ hook_event: PreToolUse
6
+ trigger_tools: [mcp__construct-mcp__workflow_update_task]
7
+ condition: task.verification is empty OR task.acceptanceCriteria is empty
8
+ action: block
9
+ message: "Task completion requires owner, acceptance criteria, and verification evidence in .cx/workflow.json."
@@ -0,0 +1,9 @@
1
+ # workflow policy
2
+ # Controls UserPromptSubmit routing — ensures significant work flows through workflow state.
3
+
4
+ rule: workflow
5
+ hook_event: UserPromptSubmit
6
+ condition: workflow.status == "in-progress" AND (open_tasks.length > 0 OR high_severity_findings.length > 0)
7
+ bypass_patterns: ["\\b(stop|pause|halt|enough|abort|cancel)\\b"]
8
+ action: block
9
+ message: "Workflow is active. Continue the current task, update .cx/workflow.json, or say stop/pause to end."
@@ -10,12 +10,8 @@ paths:
10
10
  ---
11
11
  # Python Hooks
12
12
 
13
- > This file extends [common/hooks.md](../common/hooks.md) with Python specific content.
14
-
15
13
  ## PostToolUse Hooks
16
14
 
17
- Configure in `~/.claude/settings.json`:
18
-
19
15
  - **black/ruff**: Auto-format `.py` files after edit
20
16
  - **mypy/pyright**: Run type checking after editing `.py` files
21
17
 
@@ -10,12 +10,8 @@ paths:
10
10
  ---
11
11
  # Swift Hooks
12
12
 
13
- > This file extends [common/hooks.md](../common/hooks.md) with Swift specific content.
14
-
15
13
  ## PostToolUse Hooks
16
14
 
17
- Configure in `~/.claude/settings.json`:
18
-
19
15
  - **SwiftFormat**: Auto-format `.swift` files after edit
20
16
  - **SwiftLint**: Run lint checks after editing `.swift` files
21
17
  - **swift build**: Type-check modified packages after edit
@@ -12,12 +12,8 @@ paths:
12
12
  ---
13
13
  # TypeScript/JavaScript Hooks
14
14
 
15
- > This file extends [common/hooks.md](../common/hooks.md) with TypeScript/JavaScript specific content.
16
-
17
15
  ## PostToolUse Hooks
18
16
 
19
- Configure in `~/.claude/settings.json`:
20
-
21
17
  - **Prettier**: Auto-format JS/TS files after edit
22
18
  - **TypeScript check**: Run `tsc` after editing `.ts`/`.tsx` files
23
19
  - **console.log warning**: Warn about `console.log` in edited files
@@ -3,8 +3,6 @@ rules/web/hooks.md — <one-line purpose>
3
3
 
4
4
  <2–6 line summary.>
5
5
  -->
6
- > This file extends [common/hooks.md](../common/hooks.md) with web-specific hook recommendations.
7
-
8
6
  # Web Hooks
9
7
 
10
8
  ## Recommended PostToolUse Hooks
@@ -4,12 +4,20 @@
4
4
  *
5
5
  * Reads registry.json, resolves env vars and model tiers, then writes Claude Code,
6
6
  * OpenCode, Codex, Copilot, VS Code, and Cursor adapters. Called by 'construct sync'.
7
- * Protected file — read the CLAUDE.md constraints before editing.
7
+ *
8
+ * Flags:
9
+ * --dry-run Print a diff of what would change without writing anything.
10
+ * --force Bypass prompt word-cap hard stop (still warns).
11
+ * --project Write to the current project's .claude/ directory only.
12
+ * --compress-personas Run the engine's Compressor on every persona prompt
13
+ * before writing platform adapters. The source persona
14
+ * file is unchanged; only the runtime adapter is shorter.
15
+ * Lossy by definition — opt-in only.
8
16
  */
9
17
  import fs from "node:fs";
10
18
  import path from "node:path";
11
19
  import os from "node:os";
12
- import { generateCompletions } from "./lib/completions.mjs";
20
+ import { generateCompletions } from "../lib/completions.mjs";
13
21
  import {
14
22
  buildCodexMcpEntry,
15
23
  getCodexConfigPath,
@@ -20,25 +28,27 @@ import {
20
28
  serializeCodexMcpTable,
21
29
  tomlString,
22
30
  writeCodexConfig,
23
- } from "./lib/codex-config.mjs";
24
- import { findOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig } from "./lib/opencode-config.mjs";
25
- import { resolvePromptContract } from "./lib/prompt-composer.mjs";
31
+ } from "../lib/codex-config.mjs";
32
+ import { findOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig } from "../lib/opencode-config.mjs";
33
+ import { resolvePromptContract } from "../lib/prompt-composer.js";
26
34
  import {
27
35
  buildClaudeMcpEntry,
28
36
  buildOpenCodeMcpEntry,
29
37
  getOpenCodeMcpId,
30
- } from "./lib/mcp-platform-config.mjs";
31
- import { loadConstructEnv } from "./lib/env-config.mjs";
32
- import { inlineRoleAntiPatterns, PROMPT_WORD_CAP } from "./lib/role-preload.mjs";
33
- import { resolveTiersForPrimary } from "./lib/model-router.mjs";
38
+ } from "../lib/mcp-platform-config.mjs";
39
+ import { loadConstructEnv } from "../lib/env-config.mjs";
40
+ import { inlineRoleAntiPatterns, PROMPT_WORD_CAP } from "../lib/role-preload.mjs";
41
+ import { resolveTiersForPrimary } from "../lib/model-router.mjs";
42
+ import { stampFrontmatter } from "../lib/doc-stamp.mjs";
34
43
 
35
44
  const home = os.homedir();
36
- const root = path.resolve(import.meta.dirname);
45
+ const root = path.resolve(import.meta.dirname, "..");
37
46
 
38
47
  const mergedEnv = loadConstructEnv({ rootDir: root, homeDir: home, env: process.env });
39
48
  for (const [key, value] of Object.entries(mergedEnv)) {
40
49
  if (!(key in process.env)) process.env[key] = value;
41
50
  }
51
+ if (!process.env.CX_TOOLKIT_DIR) process.env.CX_TOOLKIT_DIR = root;
42
52
  const registryPath = path.join(root, "agents", "registry.json");
43
53
  const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
44
54
 
@@ -94,8 +104,8 @@ function validateRegistry(registry) {
94
104
  errors.push(`models.${tier}: missing tier object`);
95
105
  continue;
96
106
  }
97
- if (!t.primary || typeof t.primary !== "string") {
98
- errors.push(`models.${tier}: primary model must be a non-empty string`);
107
+ if (t.primary !== null && (typeof t.primary !== "string" || !t.primary)) {
108
+ errors.push(`models.${tier}: primary must be null or a non-empty string`);
99
109
  }
100
110
  }
101
111
  }
@@ -110,6 +120,95 @@ if (validationErrors.length > 0) {
110
120
  process.exit(1);
111
121
  }
112
122
 
123
+ // --- Dry-run + lockfile + two-phase write infrastructure ---
124
+
125
+ const DRY_RUN = process.argv.includes("--dry-run");
126
+ const COMPRESS_PERSONAS = process.argv.includes("--compress-personas");
127
+ const lockPath = path.join(root, ".cx", "sync.lock");
128
+ const stagingDir = path.join(root, ".cx", "sync-staging");
129
+
130
+ /** Acquire an exclusive lockfile. Aborts if already held by a live process. */
131
+ function acquireLock() {
132
+ if (DRY_RUN) return;
133
+ try {
134
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
135
+ fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
136
+ } catch (err) {
137
+ if (err.code === "EEXIST") {
138
+ const holder = fs.readFileSync(lockPath, "utf8").trim();
139
+ // Check whether the holding process is still alive
140
+ let holderAlive = false;
141
+ try { process.kill(Number(holder), 0); holderAlive = true; } catch { /* dead */ }
142
+ if (holderAlive) {
143
+ console.error(`[sync] Another sync is already running (pid ${holder}). Aborting.`);
144
+ console.error(`[sync] If this is stale, remove .cx/sync.lock and retry.`);
145
+ process.exit(1);
146
+ }
147
+ // Stale lock — steal it
148
+ fs.writeFileSync(lockPath, String(process.pid), { flag: "w" });
149
+ return;
150
+ }
151
+ throw err;
152
+ }
153
+ }
154
+
155
+ /** Release the lockfile. Called in a finally block. */
156
+ function releaseLock() {
157
+ if (DRY_RUN) return;
158
+ try { fs.unlinkSync(lockPath); } catch { /* already gone */ }
159
+ }
160
+
161
+ /**
162
+ * Staging-aware write. In dry-run mode, writes to the staging dir instead of the
163
+ * real destination and records the path pair for diff output. In normal mode,
164
+ * writes to staging first, then the real path is swapped in by commitStaging().
165
+ */
166
+ const _stagedPairs = []; // [{ staging, real, content }]
167
+
168
+ function writeFile(file, content) {
169
+ mkdirp(path.dirname(file));
170
+ const stamped = file.endsWith('.md') ? stampFrontmatter(content, { generator: 'construct/sync-agents' }) : content;
171
+
172
+ if (DRY_RUN) {
173
+ // Stage in memory only — compare against current on-disk content.
174
+ let current = "";
175
+ try { current = fs.readFileSync(file, "utf8"); } catch { /* new file */ }
176
+ if (current !== stamped) _stagedPairs.push({ real: file, staging: null, content: stamped, current });
177
+ return;
178
+ }
179
+
180
+ // Two-phase: write to staging, commit later.
181
+ const rel = path.relative(root, file);
182
+ const stagingPath = path.join(stagingDir, rel);
183
+ mkdirp(path.dirname(stagingPath));
184
+ fs.writeFileSync(stagingPath, stamped);
185
+ _stagedPairs.push({ real: file, staging: stagingPath, content: stamped });
186
+ }
187
+
188
+ /** Atomically rename all staged files into their real destinations. */
189
+ function commitStaging() {
190
+ for (const { real, staging } of _stagedPairs) {
191
+ if (!staging) continue;
192
+ mkdirp(path.dirname(real));
193
+ fs.renameSync(staging, real);
194
+ }
195
+ // Clean up staging dir.
196
+ try { fs.rmSync(stagingDir, { recursive: true, force: true }); } catch { /* ok */ }
197
+ }
198
+
199
+ /** Print a human-readable diff summary for --dry-run mode. */
200
+ function printDryRunDiff() {
201
+ if (_stagedPairs.length === 0) {
202
+ console.log("[sync --dry-run] No changes — all outputs are already up to date.");
203
+ return;
204
+ }
205
+ console.log(`[sync --dry-run] ${_stagedPairs.length} file(s) would change:\n`);
206
+ for (const { real } of _stagedPairs) {
207
+ console.log(` ~ ${path.relative(root, real)}`);
208
+ }
209
+ console.log("\nRe-run without --dry-run to apply.");
210
+ }
211
+
113
212
  const systemName = registry.system;
114
213
  const agentPrefix = `${registry.prefix}-`;
115
214
  const sharedGuidance = registry.sharedGuidance ?? [];
@@ -248,7 +347,6 @@ function resolveModelChain(entry) {
248
347
  }
249
348
 
250
349
  function mkdirp(dir) { fs.mkdirSync(dir, { recursive: true }); }
251
- function writeFile(file, content) { mkdirp(path.dirname(file)); fs.writeFileSync(file, content); }
252
350
 
253
351
  function adapterName(entry) {
254
352
  return entry.isPersona ? entry.name : `${agentPrefix}${entry.name}`;
@@ -269,8 +367,8 @@ function loadPersonaPrompt(persona) {
269
367
  return prompt;
270
368
  }
271
369
 
272
- function buildAgentRoster(allEntries) {
273
- return allEntries.map((e) => `- ${adapterName(e)}: ${e.description}`).join("\n");
370
+ export function buildAgentRoster(allEntries) {
371
+ return allEntries.map((e) => `- ${adapterName(e)}: ${e.when_to_use || e.description}`).join("\n");
274
372
  }
275
373
 
276
374
  function buildModelGuidanceBlock(entry) {
@@ -304,7 +402,7 @@ function buildPrompt(entry, allEntries, platform) {
304
402
  fallback: entry.prompt || '',
305
403
  }).prompt;
306
404
 
307
- prompt = inlineRoleAntiPatterns(prompt, root, entry.name);
405
+ prompt = inlineRoleAntiPatterns(prompt, root, entry.name, console.warn, { preload: entry.preloadRoleGuidance === true });
308
406
 
309
407
  if (entry.injectAgentRoster && allEntries) {
310
408
  const roster = buildAgentRoster(allEntries);
@@ -325,7 +423,20 @@ function buildPrompt(entry, allEntries, platform) {
325
423
  const wordCount = prompt.split(/\s+/).filter(Boolean).length;
326
424
  const effectiveCap = Number(entry.wordCapOverride) > 0 ? entry.wordCapOverride : PROMPT_WORD_CAP;
327
425
  if (wordCount > effectiveCap) {
328
- console.warn(`[sync] ${entry.name}: prompt is ${wordCount} words (cap ${effectiveCap})`);
426
+ const msg = `[sync] ${entry.name}: prompt is ${wordCount} words (cap ${effectiveCap})`;
427
+ if (process.env.CONSTRUCT_SYNC_FORCE === '1' || process.argv.includes('--force')) {
428
+ console.warn(`${msg} — proceeding due to --force / CONSTRUCT_SYNC_FORCE=1.`);
429
+ } else {
430
+ console.error(`${msg}`);
431
+ console.error(
432
+ `[sync] Hard cap exceeded. Options:\n` +
433
+ ` - trim the prompt body or move detail to a skill (preferred)\n` +
434
+ ` - set "wordCapOverride": <N> on this entry in agents/registry.json with a written reason\n` +
435
+ ` - re-run with --force or CONSTRUCT_SYNC_FORCE=1 as a temporary escape hatch\n` +
436
+ `Prompt budget is a hard contract because every over-cap agent degrades every session that dispatches it.`,
437
+ );
438
+ process.exit(1);
439
+ }
329
440
  }
330
441
 
331
442
  return prompt;
@@ -349,6 +460,7 @@ function readManifest(dir) {
349
460
  }
350
461
 
351
462
  function writeManifest(dir, files) {
463
+ if (DRY_RUN) return;
352
464
  fs.writeFileSync(path.join(dir, MANIFEST_FILE), [...files].sort().join("\n") + "\n");
353
465
  }
354
466
 
@@ -360,7 +472,7 @@ function removeStaleAdapters(dir, ext, entries) {
360
472
  expected.add(`${adapterName(e)}${ext}`);
361
473
  }
362
474
 
363
- // Read what construct wrote last time delete anything no longer expected
475
+ // Stale manifest entries delete files not in the current expected set.
364
476
  const previouslyWritten = readManifest(dir);
365
477
  for (const file of previouslyWritten) {
366
478
  if (!expected.has(file) && fs.existsSync(path.join(dir, file))) {
@@ -421,11 +533,84 @@ ${buildPrompt(entry, allEntries, "claude")}
421
533
  `;
422
534
  }
423
535
 
536
+ /**
537
+ * Rewrite the home-mode hook command pattern
538
+ * node "$HOME/.construct/lib/hooks/<name>.mjs"
539
+ * into the project-portable form
540
+ * node .construct/run.mjs hook <name>
541
+ * so the resulting settings.json works on any clone where the project ships
542
+ * the .construct/ launcher (committed by `npm install`'s postinstall or by
543
+ * `construct init`). The launcher resolves Construct via node_modules → npx
544
+ * → globally-installed CLI → cached binary → docker, in that order, so it
545
+ * works for non-Node ecosystems too. Other commands (inline node -e
546
+ * snippets, npx block-no-verify@…) are left untouched.
547
+ */
548
+ function makeHooksPortable(hooksJson) {
549
+ // Operate on the in-memory object so we don't fight JSON string escaping.
550
+ const replaceCommand = (cmd) => {
551
+ if (typeof cmd !== 'string') return cmd;
552
+ const m = cmd.match(/^node\s+"?\$HOME\/\.construct\/lib\/hooks\/([a-z0-9-]+)\.mjs"?\s*(.*)$/);
553
+ if (!m) return cmd;
554
+ const [, name, rest] = m;
555
+ return `node .construct/run.mjs hook ${name}${rest ? ' ' + rest.trim() : ''}`;
556
+ };
557
+
558
+ const walk = (node) => {
559
+ if (Array.isArray(node)) return node.map(walk);
560
+ if (node && typeof node === 'object') {
561
+ const out = {};
562
+ for (const [k, v] of Object.entries(node)) {
563
+ out[k] = k === 'command' ? replaceCommand(v) : walk(v);
564
+ }
565
+ return out;
566
+ }
567
+ return node;
568
+ };
569
+
570
+ return JSON.stringify(walk(hooksJson));
571
+ }
572
+
573
+ /**
574
+ * Materialise a project-local `.claude/settings.json` from the home template,
575
+ * with hook commands rewritten to be path-relative to whatever Construct
576
+ * install the project carries. Merges into an existing settings.json
577
+ * if one is already in the project; otherwise creates a fresh one.
578
+ */
579
+ function writeProjectClaudeSettings(targetDir) {
580
+ const settingsPath = path.join(targetDir, ".claude", "settings.json");
581
+ const templatePath = path.join(root, "platforms", "claude", "settings.template.json");
582
+ if (!fs.existsSync(templatePath)) return;
583
+
584
+ const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
585
+
586
+ const existing = fs.existsSync(settingsPath)
587
+ ? JSON.parse(fs.readFileSync(settingsPath, "utf8"))
588
+ : {};
589
+
590
+ if (template.hooks) {
591
+ existing.hooks = JSON.parse(makeHooksPortable(template.hooks));
592
+ }
593
+ if (template.permissions) {
594
+ existing.permissions ??= template.permissions;
595
+ }
596
+ if (template.mcpServers) {
597
+ existing.mcpServers ??= {};
598
+ for (const [id, mcpDef] of Object.entries(template.mcpServers)) {
599
+ if (existing.mcpServers[id]) continue;
600
+ existing.mcpServers[id] = mcpDef;
601
+ }
602
+ }
603
+
604
+ if (DRY_RUN) return;
605
+ mkdirp(path.dirname(settingsPath));
606
+ writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n");
607
+ }
608
+
424
609
  function syncClaude(entries, targetDir = null) {
425
610
  const claudeAgentsDir = targetDir
426
611
  ? path.join(targetDir, ".claude", "agents")
427
612
  : path.join(home, ".claude", "agents");
428
- mkdirp(claudeAgentsDir);
613
+ if (!DRY_RUN) mkdirp(claudeAgentsDir);
429
614
 
430
615
  for (const entry of entries) {
431
616
  const name = adapterName(entry);
@@ -434,6 +619,11 @@ function syncClaude(entries, targetDir = null) {
434
619
  }
435
620
  removeStaleAdapters(claudeAgentsDir, ".md", entries);
436
621
 
622
+ if (targetDir) {
623
+ writeProjectClaudeSettings(targetDir);
624
+ return;
625
+ }
626
+
437
627
  if (!targetDir) {
438
628
  const claudeMdPath = path.join(home, ".claude", "CLAUDE.md");
439
629
  const existing = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, "utf8") : "# Claude Global Instructions\n";
@@ -475,7 +665,7 @@ ${agentList || "(all specialists are internal — routed through Construct)"}`;
475
665
  if (settings.mcpServers[id] && !hasPlaceholder) continue;
476
666
  settings.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
477
667
  }
478
- fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + "\n");
668
+ if (!DRY_RUN) fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + "\n");
479
669
  }
480
670
  }
481
671
  }
@@ -510,10 +700,14 @@ function hasCodexMcpTable(text, id) {
510
700
  return new RegExp(`^\\[mcp_servers\\.(?:${escapeRegExp(id)}|${escapeRegExp(tomlString(id))})\\]`, "m").test(text);
511
701
  }
512
702
 
703
+ function isCodexMcpSupported(id) {
704
+ return id !== "memory";
705
+ }
706
+
513
707
  function syncCodex(entries) {
514
708
  const codexDir = path.join(home, ".codex");
515
709
  const codexAgentsDir = path.join(codexDir, "agents");
516
- mkdirp(codexAgentsDir);
710
+ if (!DRY_RUN) mkdirp(codexAgentsDir);
517
711
 
518
712
  for (const entry of entries) {
519
713
  writeFile(path.join(codexAgentsDir, `${adapterName(entry)}.toml`), codexAgentToml(entry, entries));
@@ -524,10 +718,11 @@ function syncCodex(entries) {
524
718
  const existing = removeDanglingConstructMcpMarkers(removeDanglingConstructMcpTimeouts(readCodexConfig(configPath)));
525
719
  const entryNames = entries.map(adapterName);
526
720
  const registryMcp = registry.mcpServers ?? {};
527
- const mcpIds = Object.keys(registryMcp).filter((id) => hasCodexMcpTable(existing, id));
721
+ const existingMcpIds = Object.keys(registryMcp).filter((id) => hasCodexMcpTable(existing, id));
722
+ const mcpIds = existingMcpIds.filter(isCodexMcpSupported);
528
723
  const withoutManagedTables = removeDanglingConstructMcpMarkers(removeTomlTables(
529
724
  removeCodexAgentTables(existing, entryNames),
530
- mcpIds.flatMap((id) => [`mcp_servers.${id}`, `mcp_servers.${tomlString(id)}`]),
725
+ existingMcpIds.flatMap((id) => [`mcp_servers.${id}`, `mcp_servers.${tomlString(id)}`]),
531
726
  ));
532
727
  const hasAgentsRoot = /^\[agents\]\s*$/m.test(withoutManagedTables);
533
728
  const rootBlock = hasAgentsRoot ? "" : "[agents]\nmax_threads = 6\nmax_depth = 1\n\n";
@@ -570,7 +765,7 @@ When using this prompt, stay within the role above and adapt to the current repo
570
765
 
571
766
  function syncCopilot(entries) {
572
767
  const promptsDir = path.join(home, ".github", "prompts");
573
- mkdirp(promptsDir);
768
+ if (!DRY_RUN) mkdirp(promptsDir);
574
769
  for (const entry of entries) {
575
770
  writeFile(path.join(promptsDir, `${adapterName(entry)}.prompt.md`), copilotPrompt(entry, entries));
576
771
  }
@@ -634,7 +829,7 @@ function syncVSCode() {
634
829
  mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
635
830
  }
636
831
  settings["github.copilot.mcpServers"] = mcpServers;
637
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
832
+ if (!DRY_RUN) fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
638
833
  synced = true;
639
834
  } catch {
640
835
  // Skip unreadable settings files
@@ -661,7 +856,7 @@ function syncCursor() {
661
856
  if (config.mcpServers[id] && !hasPlaceholder) continue;
662
857
  config.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
663
858
  }
664
- fs.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + "\n");
859
+ if (!DRY_RUN) fs.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + "\n");
665
860
  return true;
666
861
  } catch {
667
862
  return false;
@@ -757,6 +952,10 @@ function syncOpencode(entries) {
757
952
  for (const [id, mcpDef] of Object.entries(registryMcp)) {
758
953
  const openCodeId = getOpenCodeMcpId(id);
759
954
  if (openCodeId !== id) delete config.mcp[id];
955
+ if (id === 'memory' && config.mcp.cass && !config.mcp[openCodeId]) {
956
+ config.mcp[openCodeId] = config.mcp.cass;
957
+ }
958
+ if (id === 'memory') delete config.mcp.cass;
760
959
  const existing = JSON.stringify(config.mcp[openCodeId] ?? "");
761
960
  const hasPlaceholder = existing.includes("__") && existing.includes("__");
762
961
  const argsHaveTemplates = (mcpDef.args ?? []).some((a) => typeof a === 'string' && a.includes('__'));
@@ -796,7 +995,7 @@ function syncOpencode(entries) {
796
995
  writeOpenCodeConfig(config, configPath);
797
996
 
798
997
  const sourcePluginsDir = path.join(root, "platforms", "opencode", "plugins");
799
- if (fs.existsSync(sourcePluginsDir)) {
998
+ if (fs.existsSync(sourcePluginsDir) && !DRY_RUN) {
800
999
  mkdirp(pluginsDir);
801
1000
  for (const file of fs.readdirSync(sourcePluginsDir)) {
802
1001
  if (!file.endsWith(".js") && !file.endsWith(".ts")) continue;
@@ -808,7 +1007,7 @@ function syncOpencode(entries) {
808
1007
  }
809
1008
 
810
1009
  config.plugin = [...config.plugin, managedPluginPath];
811
- writeOpenCodeConfig(config, configPath);
1010
+ if (!DRY_RUN) writeOpenCodeConfig(config, configPath);
812
1011
 
813
1012
  return true;
814
1013
  }
@@ -828,19 +1027,20 @@ function syncCommands(targetDir = null) {
828
1027
  if (!domain.isDirectory()) continue;
829
1028
  const domainDir = path.join(sourceCommandsDir, domain.name);
830
1029
  const targetDomainDir = path.join(claudeCommandsDir, domain.name);
831
- mkdirp(targetDomainDir);
1030
+ if (!DRY_RUN) mkdirp(targetDomainDir);
832
1031
 
833
1032
  for (const file of fs.readdirSync(domainDir)) {
834
1033
  if (!file.endsWith(".md")) continue;
1034
+ count++;
1035
+ if (DRY_RUN) continue;
835
1036
  const source = path.join(domainDir, file);
836
1037
  const target = path.join(targetDomainDir, file);
837
1038
  fs.copyFileSync(source, target);
838
- count++;
839
1039
  }
840
1040
  }
841
1041
 
842
1042
  // Clean up stale command files not in source
843
- if (fs.existsSync(claudeCommandsDir)) {
1043
+ if (!DRY_RUN && fs.existsSync(claudeCommandsDir)) {
844
1044
  for (const domain of fs.readdirSync(claudeCommandsDir, { withFileTypes: true })) {
845
1045
  if (!domain.isDirectory()) continue;
846
1046
  const sourceDomainDir = path.join(sourceCommandsDir, domain.name);
@@ -865,35 +1065,80 @@ function syncCommands(targetDir = null) {
865
1065
  const projectDir = process.argv.includes("--project") ? process.cwd() : null;
866
1066
  const entries = buildEntries();
867
1067
 
868
- if (projectDir) {
869
- syncClaude(entries, projectDir);
870
- const cmdCount = syncCommands(projectDir);
871
- console.log(`Synced ${entries.length} agents + ${cmdCount} commands to ${path.join(projectDir, ".claude/")} (project mode).`);
872
- } else {
873
- const personaCount = entries.filter((e) => e.isPersona).length;
874
- const agentCount = entries.filter((e) => !e.isPersona).length;
875
-
876
- syncCodex(entries);
877
- syncClaude(entries);
878
- syncCopilot(entries);
879
- const opencodeOk = syncOpencode(entries);
880
- const vscodeOk = syncVSCode();
881
- const cursorOk = syncCursor();
882
- const cmdCount = syncCommands();
883
-
884
- const targets = [
885
- "Codex",
886
- "Claude Code",
887
- "Copilot",
888
- opencodeOk && "OpenCode",
889
- vscodeOk && "VS Code",
890
- cursorOk && "Cursor",
891
- ].filter(Boolean).join(", ");
892
- console.log(`Synced ${personaCount} personas + ${agentCount} specialists + ${cmdCount} commands to ${targets}.`);
893
-
894
- // Regenerate shell completions so new commands are immediately tab-completable
895
- const completionsDir = generateCompletions();
896
- if (completionsDir) {
897
- console.log(`Completions updated → ${completionsDir}`);
1068
+ if (COMPRESS_PERSONAS) {
1069
+ // Run the engine's Compressor on every persona prompt so the runtime
1070
+ // adapter file is shorter than the source persona on disk. The source
1071
+ // file stays unchanged so authors keep editing the readable version.
1072
+ const { getEngine } = await import('./lib/engine/index.mjs');
1073
+ const engine = await getEngine({ rootDir: root });
1074
+ const compressor = engine.layers.compressor;
1075
+ let totalIn = 0;
1076
+ let totalOut = 0;
1077
+ for (const entry of entries) {
1078
+ if (!entry.isPersona || !entry.prompt) continue;
1079
+ const before = entry.prompt;
1080
+ try {
1081
+ const after = await compressor.compress(before, { ratio: 0.6 });
1082
+ if (typeof after === 'string' && after.length > 0) {
1083
+ entry.prompt = after;
1084
+ totalIn += before.length;
1085
+ totalOut += after.length;
1086
+ }
1087
+ } catch (err) {
1088
+ console.warn(`compress-personas: skipping ${entry.name}: ${err.message}`);
1089
+ }
1090
+ }
1091
+ if (totalIn > 0) {
1092
+ const ratio = ((totalOut / totalIn) * 100).toFixed(0);
1093
+ console.log(`compress-personas: ${totalIn} → ${totalOut} chars (${ratio}%) across ${entries.filter((e) => e.isPersona).length} personas`);
1094
+ }
1095
+ }
1096
+
1097
+ acquireLock();
1098
+ try {
1099
+ if (projectDir) {
1100
+ syncClaude(entries, projectDir);
1101
+ const cmdCount = syncCommands(projectDir);
1102
+ if (DRY_RUN) {
1103
+ printDryRunDiff();
1104
+ } else {
1105
+ commitStaging();
1106
+ console.log(`Synced ${entries.length} agents + ${cmdCount} commands to ${path.join(projectDir, ".claude/")} (project mode).`);
1107
+ }
1108
+ } else {
1109
+ const personaCount = entries.filter((e) => e.isPersona).length;
1110
+ const agentCount = entries.filter((e) => !e.isPersona).length;
1111
+
1112
+ syncCodex(entries);
1113
+ syncClaude(entries);
1114
+ syncCopilot(entries);
1115
+ const opencodeOk = syncOpencode(entries);
1116
+ const vscodeOk = syncVSCode();
1117
+ const cursorOk = syncCursor();
1118
+ const cmdCount = syncCommands();
1119
+
1120
+ if (DRY_RUN) {
1121
+ printDryRunDiff();
1122
+ } else {
1123
+ commitStaging();
1124
+
1125
+ const targets = [
1126
+ "Codex",
1127
+ "Claude Code",
1128
+ "Copilot",
1129
+ opencodeOk && "OpenCode",
1130
+ vscodeOk && "VS Code",
1131
+ cursorOk && "Cursor",
1132
+ ].filter(Boolean).join(", ");
1133
+ console.log(`Synced ${personaCount} personas + ${agentCount} specialists + ${cmdCount} commands to ${targets}.`);
1134
+
1135
+ // Regenerate shell completions so new commands are immediately tab-completable
1136
+ const completionsDir = generateCompletions();
1137
+ if (completionsDir) {
1138
+ console.log(`Completions updated → ${completionsDir}`);
1139
+ }
1140
+ }
898
1141
  }
1142
+ } finally {
1143
+ releaseLock();
899
1144
  }