@geraldmaron/construct 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/.env.example +10 -7
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1116 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +525 -38
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. package/rules/zh/testing.md +0 -34
@@ -1,14 +1,14 @@
1
1
  /**
2
- * MCP manager — add, remove, list MCPs in ~/.claude/settings.json
3
- * Uses mcp-catalog.json as the source of truth for known integrations.
2
+ * MCP manager — add, remove, list MCPs in host configs.
3
+ * Uses the plugin registry as the source of truth for built-in and external integrations.
4
4
  */
5
5
 
6
6
  import { execSync } from 'node:child_process';
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from 'node:fs';
8
8
  import { createInterface } from 'node:readline';
9
9
  import { homedir, platform } from 'node:os';
10
10
  import { join, dirname } from 'node:path';
11
- import { fileURLToPath } from 'node:url';
11
+ import { selectOption } from './tty-prompts.mjs';
12
12
  import { readOpenCodeConfig, writeOpenCodeConfig } from './opencode-config.mjs';
13
13
  import { getUserEnvPath, loadConstructEnv, writeEnvValues } from './env-config.mjs';
14
14
  import {
@@ -26,29 +26,18 @@ import {
26
26
  buildClaudeMcpEntry,
27
27
  buildOpenCodeMcpEntry,
28
28
  getOpenCodeMcpId,
29
- normalizeInstalledOpenCodeMcpId,
30
29
  } from './mcp-platform-config.mjs';
30
+ import { getMcpById as getMcpByIdFromRegistry, loadPluginRegistry } from './plugin-registry.mjs';
31
31
 
32
- const __dirname = dirname(fileURLToPath(import.meta.url));
33
-
34
- const CATALOG_PATH = join(__dirname, 'mcp-catalog.json');
35
- const CLAUDE_SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
32
+ function getClaudeSettingsPath() {
33
+ return join(homedir(), '.claude', 'settings.json');
34
+ }
36
35
 
37
36
  function openUrl(url) {
38
37
  try {
39
38
  const opener = platform() === 'darwin' ? 'open' : platform() === 'win32' ? 'start' : 'xdg-open';
40
39
  execSync(`${opener} "${url}"`, { stdio: 'ignore' });
41
- } catch {
42
- // best effort
43
- }
44
- }
45
-
46
- function loadCatalog() {
47
- const catalog = JSON.parse(readFileSync(CATALOG_PATH, 'utf8'));
48
- return {
49
- ...catalog,
50
- mcps: (catalog.mcps ?? []).map(normalizeMcp),
51
- };
40
+ } catch { /* browser launch is non-critical */ }
52
41
  }
53
42
 
54
43
  function normalizeMcp(mcp) {
@@ -59,6 +48,7 @@ function normalizeMcp(mcp) {
59
48
  requiredEnv: Array.isArray(mcp.requiredEnv) ? mcp.requiredEnv : [],
60
49
  setupModes: Array.isArray(mcp.setupModes) ? mcp.setupModes : ['manual'],
61
50
  usedBy: Array.isArray(mcp.usedBy) ? mcp.usedBy : [],
51
+ hostSupport: mcp.hostSupport && typeof mcp.hostSupport === 'object' ? mcp.hostSupport : {},
62
52
  };
63
53
  }
64
54
 
@@ -66,6 +56,24 @@ function hasSetupMode(mcp, mode) {
66
56
  return mcp.setupModes.includes(mode);
67
57
  }
68
58
 
59
+ function getHostSupport(mcp, host) {
60
+ const support = mcp?.hostSupport?.[host];
61
+ if (!support) return { mode: 'managed' };
62
+ if (typeof support === 'string') return { mode: support };
63
+ if (typeof support === 'object') return { mode: support.mode ?? 'managed', ...support };
64
+ return { mode: 'managed' };
65
+ }
66
+
67
+ function isManagedOnHost(mcp, host) {
68
+ return getHostSupport(mcp, host).mode === 'managed';
69
+ }
70
+
71
+ function collectPluginBackedMcpIds(catalog) {
72
+ return (catalog.mcps ?? [])
73
+ .filter((mcp) => ['claude', 'opencode', 'codex'].some((host) => getHostSupport(mcp, host).mode === 'plugin'))
74
+ .map((mcp) => mcp.id);
75
+ }
76
+
69
77
  function parseAddFlags() {
70
78
  const args = new Set(process.argv.slice(2));
71
79
  return {
@@ -86,13 +94,22 @@ async function chooseSetupMode(mcp, flags) {
86
94
  if (!canManual) return 'auto';
87
95
  if (!process.stdin.isTTY) return 'manual';
88
96
 
89
- const rl = createInterface({ input: process.stdin, output: process.stdout });
90
- console.log('\nSetup mode:');
91
- console.log(' 1) Auto-configure what Construct can detect or provision');
92
- console.log(' 2) Manual setup (I will provide credentials/URLs myself)');
93
- const answer = await prompt(rl, ' Choice [1/2]: ');
94
- rl.close();
95
- return answer.trim() === '2' ? 'manual' : 'auto';
97
+ return selectOption({
98
+ title: 'Choose setup mode',
99
+ instructions: 'Pick how Construct should configure this integration.',
100
+ options: [
101
+ {
102
+ value: 'auto',
103
+ label: 'Auto-configure',
104
+ description: 'Let Construct detect, provision, or prefill what it can.',
105
+ },
106
+ {
107
+ value: 'manual',
108
+ label: 'Manual setup',
109
+ description: 'Provide credentials, URLs, and config values yourself.',
110
+ },
111
+ ],
112
+ });
96
113
  }
97
114
 
98
115
  function getConfiguredEnvValue(name) {
@@ -130,17 +147,19 @@ function getConfiguredEnvValues(mcp) {
130
147
  }
131
148
 
132
149
  function loadSettings() {
133
- if (!existsSync(CLAUDE_SETTINGS_PATH)) return {};
150
+ const claudeSettingsPath = getClaudeSettingsPath();
151
+ if (!existsSync(claudeSettingsPath)) return {};
134
152
  try {
135
- return JSON.parse(readFileSync(CLAUDE_SETTINGS_PATH, 'utf8'));
153
+ return JSON.parse(readFileSync(claudeSettingsPath, 'utf8'));
136
154
  } catch {
137
155
  return {};
138
156
  }
139
157
  }
140
158
 
141
159
  function saveSettings(settings) {
142
- mkdirSync(dirname(CLAUDE_SETTINGS_PATH), { recursive: true });
143
- writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
160
+ const claudeSettingsPath = getClaudeSettingsPath();
161
+ mkdirSync(dirname(claudeSettingsPath), { recursive: true });
162
+ writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + '\n');
144
163
  }
145
164
 
146
165
  function loadOpenCodeConfig() {
@@ -152,20 +171,23 @@ function saveOpenCodeConfig(config) {
152
171
  }
153
172
 
154
173
  function getMcpById(id) {
155
- const catalog = loadCatalog();
156
- return catalog.mcps.find(m => m.id === id) ?? null;
174
+ return getMcpByIdFromRegistry(id, { cwd: process.cwd(), homeDir: homedir() });
157
175
  }
158
176
 
159
177
  function getInstalledMcps() {
178
+ const registry = loadPluginRegistry({ cwd: process.cwd(), homeDir: homedir() });
160
179
  const settings = loadSettings();
161
180
  const oc = loadOpenCodeConfig();
162
181
  const codexConfig = readCodexConfig();
163
182
  const codexServers = Array.from(codexConfig.matchAll(/^\[mcp_servers\.("?)([^"\]\n]+)\1\]/gm)).map((match) => match[2]);
164
183
  const set = new Set([
165
184
  ...Object.keys(settings.mcpServers ?? {}),
166
- ...Object.keys(oc.mcp ?? {}).map(normalizeInstalledOpenCodeMcpId),
185
+ ...Object.keys(oc.mcp ?? {}),
167
186
  ...codexServers,
168
187
  ]);
188
+ for (const id of collectPluginBackedMcpIds({ mcps: registry.mcps.map(normalizeMcp) })) {
189
+ set.add(id);
190
+ }
169
191
  return Array.from(set);
170
192
  }
171
193
 
@@ -196,14 +218,20 @@ function removeCodexMcp(id) {
196
218
  * Show all MCPs from catalog with installed/missing status.
197
219
  */
198
220
  export function cmdMcpList() {
199
- const catalog = loadCatalog();
221
+ const registry = loadPluginRegistry({ cwd: process.cwd(), homeDir: homedir() });
200
222
  const installed = new Set(getInstalledMcps());
201
223
 
224
+ if (!registry.valid) {
225
+ console.log('Plugin registry errors:');
226
+ for (const error of registry.errors) console.log(` ✗ ${error}`);
227
+ console.log('');
228
+ }
229
+
202
230
  const categories = ['core', 'optional', 'integration'];
203
231
  const labels = { core: 'Core (Essential)', optional: 'Enhancements', integration: 'Third-Party' };
204
232
 
205
233
  for (const cat of categories) {
206
- const mcps = catalog.mcps.filter(m => m.category === cat);
234
+ const mcps = registry.mcps.filter(m => m.category === cat);
207
235
  if (!mcps.length) continue;
208
236
 
209
237
  console.log(`\n${labels[cat]}:`);
@@ -212,7 +240,8 @@ export function cmdMcpList() {
212
240
  const status = installed.has(mcp.id) ? '✓' : '·';
213
241
  const name = mcp.name.padEnd(20);
214
242
  const req = mcp.requiredEnv.length ? ` [needs: ${mcp.requiredEnv.join(', ')}]` : '';
215
- console.log(` ${status} ${name} ${mcp.description.substring(0, 50)}${req}`);
243
+ const source = mcp.pluginId !== 'construct-builtins' ? ` [plugin: ${mcp.pluginId}]` : '';
244
+ console.log(` ${status} ${name} ${mcp.description.substring(0, 50)}${req}${source}`);
216
245
  }
217
246
  }
218
247
 
@@ -338,7 +367,11 @@ export async function cmdMcpAdd(id) {
338
367
  // Write to Claude settings
339
368
  const settings = loadSettings();
340
369
  if (!settings.mcpServers) settings.mcpServers = {};
341
- settings.mcpServers[id] = buildClaudeMcpEntry(id, mcp, resolvedValues);
370
+ if (isManagedOnHost(mcp, 'claude')) {
371
+ settings.mcpServers[id] = buildClaudeMcpEntry(id, mcp, resolvedValues);
372
+ } else {
373
+ delete settings.mcpServers[id];
374
+ }
342
375
  saveSettings(settings);
343
376
 
344
377
  // Write to OpenCode config
@@ -346,13 +379,19 @@ export async function cmdMcpAdd(id) {
346
379
  if (!oc.mcp) oc.mcp = {};
347
380
  const openCodeId = getOpenCodeMcpId(id);
348
381
  delete oc.mcp[id];
349
- oc.mcp[openCodeId] = buildOpenCodeMcpEntry(id, mcp, resolvedValues).entry;
382
+ if (id === 'memory') delete oc.mcp.cass;
383
+ if (isManagedOnHost(mcp, 'opencode')) {
384
+ oc.mcp[openCodeId] = buildOpenCodeMcpEntry(id, mcp, resolvedValues).entry;
385
+ } else {
386
+ delete oc.mcp[openCodeId];
387
+ }
350
388
  saveOpenCodeConfig(oc);
351
389
 
352
- // Write to Codex config. GitHub uses the official remote server URL so Codex
353
- // can authenticate via `codex mcp login github` instead of launching stale
354
- // local github-mcp-server binaries.
355
- writeCodexMcp(id, mcp, resolvedValues);
390
+ if (isManagedOnHost(mcp, 'codex')) {
391
+ writeCodexMcp(id, mcp, resolvedValues);
392
+ } else {
393
+ removeCodexMcp(id);
394
+ }
356
395
 
357
396
  // --- SYNC TO .ENV ---
358
397
  const envPath = getUserEnvPath(homedir());
@@ -364,8 +403,14 @@ export async function cmdMcpAdd(id) {
364
403
  console.log(`✓ Setup mode: ${setupMode}`);
365
404
  console.log(`✓ Services synchronized.`);
366
405
  console.log(`\nNext: Restart OpenCode, Claude Code, or Codex to activate the new tools.`);
367
- if (id === 'github') {
368
- console.log(' For Codex OAuth, run: codex mcp login github');
406
+ for (const host of ['claude', 'opencode', 'codex']) {
407
+ const support = getHostSupport(mcp, host);
408
+ const hostLabel = host === 'claude' ? 'Claude Code' : host === 'opencode' ? 'OpenCode' : 'Codex';
409
+ if (support.mode === 'plugin') {
410
+ console.log(` ${hostLabel} uses ${support.label ?? 'a native plugin/app path'} for ${mcp.name}; Construct does not install a standalone MCP there.`);
411
+ } else if (support.mode === 'unsupported') {
412
+ console.log(` ${hostLabel} does not support managed ${mcp.name} setup.`);
413
+ }
369
414
  }
370
415
 
371
416
  if (mcp.usedBy.length) {
@@ -382,11 +427,11 @@ export function cmdMcpRemove(id) {
382
427
  const oc = openCodeState.config ?? {};
383
428
  const openCodeId = getOpenCodeMcpId(id);
384
429
  const hasClaudeEntry = Boolean(settings.mcpServers?.[id]);
385
- const hasOpenCodeEntry = Boolean(oc.mcp?.[openCodeId] || oc.mcp?.[id]);
430
+ const hasOpenCodeEntry = Boolean(oc.mcp?.[openCodeId] || oc.mcp?.[id] || (id === 'memory' && oc.mcp?.cass));
386
431
  const hasCodexEntry = readCodexConfig().includes(`[mcp_servers.${id}]`) || readCodexConfig().includes(`[mcp_servers.${tomlString(id)}]`);
387
432
  if (!hasClaudeEntry && !hasOpenCodeEntry && !hasCodexEntry) {
388
- console.error(`${id} is not installed.`);
389
- process.exit(1);
433
+ console.log(`${id} is not installed. Nothing to remove.`);
434
+ return;
390
435
  }
391
436
 
392
437
  const mcp = getMcpById(id);
@@ -398,8 +443,10 @@ export function cmdMcpRemove(id) {
398
443
  if (oc.mcp) {
399
444
  delete oc.mcp[openCodeId];
400
445
  delete oc.mcp[id];
446
+ if (id === 'memory') delete oc.mcp.cass;
401
447
  }
402
448
  if (openCodeState.config || hasOpenCodeEntry) saveOpenCodeConfig(oc);
449
+ else if (openCodeState.file && existsSync(openCodeState.file)) rmSync(openCodeState.file, { force: true });
403
450
  removeCodexMcp(id);
404
451
 
405
452
  console.log(`✓ ${name} removed from Claude Code, OpenCode, and Codex config`);
@@ -422,6 +469,7 @@ export function cmdMcpInfo(id) {
422
469
  console.log('─'.repeat(48));
423
470
  console.log(`Status: ${installed ? '✓ installed' : '· not installed'}`);
424
471
  console.log(`Category: ${mcp.category}`);
472
+ console.log(`Plugin: ${mcp.pluginName} (${mcp.pluginId})`);
425
473
  if (mcp.package) console.log(`Package: ${mcp.package}`);
426
474
  console.log(`\n${mcp.description}`);
427
475
  if (mcp.setupUrl) console.log(`\nSetup: ${mcp.setupUrl}`);
@@ -1,14 +1,17 @@
1
1
  /**
2
- * lib/mcp-platform-config.mjs — <one-line purpose>
2
+ * lib/mcp-platform-config.mjs — Resolve MCP server config paths for Claude and OpenCode.
3
3
  *
4
- * <2–6 line summary: what it does, who calls it, key side effects.>
4
+ * Provides platform-aware path resolution for ~/.claude/claude_desktop_config.json
5
+ * and the OpenCode settings file, abstracting OS and install-location differences.
6
+ * Backs mcp-manager and setup when they read and write MCP server registrations.
5
7
  */
6
- export function getOpenCodeMcpId(id) {
7
- return id === "memory" ? "cass" : id;
8
- }
8
+ import { dirname, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
9
10
 
10
- export function normalizeInstalledOpenCodeMcpId(id) {
11
- return id === "cass" ? "memory" : id;
11
+ const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
+
13
+ export function getOpenCodeMcpId(id) {
14
+ return id;
12
15
  }
13
16
 
14
17
  function resolveTemplateString(value, resolvedValues) {
@@ -68,36 +71,33 @@ export function buildClaudeMcpEntry(id, mcpDef, resolvedValues = {}) {
68
71
  }
69
72
 
70
73
  export function buildOpenCodeMcpEntry(id, mcpDef, resolvedValues = {}) {
74
+ const runtimeValues = {
75
+ CX_TOOLKIT_DIR: ROOT_DIR,
76
+ ...resolvedValues,
77
+ };
71
78
  const openCodeId = getOpenCodeMcpId(id);
72
79
 
73
- if (id === "memory") {
74
- return {
75
- id: openCodeId,
76
- entry: {
77
- type: "local",
78
- command: ["npx", "-y", "@modelcontextprotocol/server-memory@latest"],
79
- },
80
- };
81
- }
82
-
83
80
  if (mcpDef.type === "url") {
84
- const headers = buildRemoteHeaders(mcpDef, resolvedValues);
81
+ const url =
82
+ id === "memory" && runtimeValues.MEMORY_PORT
83
+ ? `http://127.0.0.1:${runtimeValues.MEMORY_PORT}/`
84
+ : resolveTemplateString(mcpDef.url, runtimeValues);
85
85
  return {
86
86
  id: openCodeId,
87
87
  entry: {
88
88
  type: "remote",
89
- url: resolveTemplateString(mcpDef.url, resolvedValues),
90
- ...(Object.keys(headers).length > 0 ? { headers } : {}),
89
+ url,
90
+ ...(Object.keys(buildRemoteHeaders(mcpDef, runtimeValues)).length > 0 ? { headers: buildRemoteHeaders(mcpDef, runtimeValues) } : {}),
91
91
  },
92
92
  };
93
93
  }
94
94
 
95
- const environment = buildLocalEnvironment(mcpDef, resolvedValues);
95
+ const environment = buildLocalEnvironment(mcpDef, runtimeValues);
96
96
  return {
97
97
  id: openCodeId,
98
98
  entry: {
99
99
  type: "local",
100
- command: [mcpDef.command, ...resolveArgs(mcpDef.args, resolvedValues)],
100
+ command: [mcpDef.command, ...resolveArgs(mcpDef.args, runtimeValues)],
101
101
  ...(Object.keys(environment).length > 0 ? { environment } : {}),
102
102
  },
103
103
  };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * lib/memory-stats.mjs — memory layer usage statistics and JSONL snapshot appender.
3
+ *
4
+ * Reads .cx/memory-stats.jsonl (one JSON line per session) and computes aggregates:
5
+ * queries/session, avg observations injected, p95 retrieval latency, hit rate.
6
+ * appendSessionStats() is called at session end to record the current session.
7
+ *
8
+ * Stats are approximated from session-start injection counts and search call counts
9
+ * recorded by the observation store instrumentation. CONSTRUCT_MEMORY=off skips injection
10
+ * but still records a zero-hit entry so A/B comparisons are possible.
11
+ */
12
+
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+
16
+ const STATS_FILE = '.cx/memory-stats.jsonl';
17
+
18
+ function statsPath(rootDir) {
19
+ return path.join(rootDir, STATS_FILE);
20
+ }
21
+
22
+ function ensureDir(p) {
23
+ fs.mkdirSync(path.dirname(p), { recursive: true });
24
+ }
25
+
26
+ function readLines(rootDir) {
27
+ const p = statsPath(rootDir);
28
+ if (!fs.existsSync(p)) return [];
29
+ return fs.readFileSync(p, 'utf8')
30
+ .split('\n')
31
+ .filter(Boolean)
32
+ .map((line) => { try { return JSON.parse(line); } catch { return null; } })
33
+ .filter(Boolean);
34
+ }
35
+
36
+ /**
37
+ * Append one session's memory stats to the JSONL log.
38
+ * Fields: sessionId, project, at, queriesIssued, observationsInjected,
39
+ * retrievalMs (p50 estimate), memoryEnabled.
40
+ */
41
+ export function appendSessionStats(rootDir, {
42
+ sessionId = null,
43
+ project = null,
44
+ queriesIssued = 0,
45
+ observationsInjected = 0,
46
+ retrievalMs = null,
47
+ memoryEnabled = true,
48
+ } = {}) {
49
+ const entry = {
50
+ sessionId,
51
+ project,
52
+ at: new Date().toISOString(),
53
+ queriesIssued,
54
+ observationsInjected,
55
+ retrievalMs,
56
+ memoryEnabled,
57
+ };
58
+ const p = statsPath(rootDir);
59
+ ensureDir(p);
60
+ fs.appendFileSync(p, JSON.stringify(entry) + '\n');
61
+ return entry;
62
+ }
63
+
64
+ /**
65
+ * Compute aggregate stats from the JSONL log.
66
+ * Returns { sessions, avgQueriesPerSession, avgInjectedPerSession,
67
+ * p95RetrievalMs, hitRate, memoryEnabledPct }.
68
+ */
69
+ export function computeStats(rootDir, { project = null, lastN = 50 } = {}) {
70
+ let lines = readLines(rootDir);
71
+ if (project) lines = lines.filter((l) => l.project === project);
72
+ lines = lines.slice(-lastN);
73
+
74
+ if (!lines.length) {
75
+ return { sessions: 0, avgQueriesPerSession: 0, avgInjectedPerSession: 0, p95RetrievalMs: null, hitRate: 0, memoryEnabledPct: 100 };
76
+ }
77
+
78
+ const sessions = lines.length;
79
+ const totalQueries = lines.reduce((s, l) => s + (l.queriesIssued || 0), 0);
80
+ const totalInjected = lines.reduce((s, l) => s + (l.observationsInjected || 0), 0);
81
+ const enabledCount = lines.filter((l) => l.memoryEnabled !== false).length;
82
+
83
+ const latencies = lines
84
+ .map((l) => l.retrievalMs)
85
+ .filter((v) => typeof v === 'number' && v > 0)
86
+ .sort((a, b) => a - b);
87
+
88
+ const p95RetrievalMs = latencies.length
89
+ ? latencies[Math.floor(latencies.length * 0.95)]
90
+ : null;
91
+
92
+ const sessionsWithHits = lines.filter((l) => (l.observationsInjected || 0) > 0).length;
93
+ const hitRate = sessions > 0 ? Math.round((sessionsWithHits / sessions) * 100) : 0;
94
+
95
+ return {
96
+ sessions,
97
+ avgQueriesPerSession: Math.round((totalQueries / sessions) * 10) / 10,
98
+ avgInjectedPerSession: Math.round((totalInjected / sessions) * 10) / 10,
99
+ p95RetrievalMs,
100
+ hitRate,
101
+ memoryEnabledPct: Math.round((enabledCount / sessions) * 100),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Format stats for terminal display.
107
+ */
108
+ export function formatStats(stats) {
109
+ if (stats.sessions === 0) {
110
+ return ' No memory stats recorded yet. Run a few sessions and try again.\n';
111
+ }
112
+ const lines = [
113
+ ` Sessions tracked: ${stats.sessions}`,
114
+ ` Avg queries/session: ${stats.avgQueriesPerSession}`,
115
+ ` Avg injected/session: ${stats.avgInjectedPerSession}`,
116
+ ` Hit rate: ${stats.hitRate}%`,
117
+ ` p95 retrieval latency: ${stats.p95RetrievalMs != null ? `${stats.p95RetrievalMs}ms` : 'n/a'}`,
118
+ ` Memory enabled: ${stats.memoryEnabledPct}% of sessions`,
119
+ ];
120
+ return lines.join('\n') + '\n';
121
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * lib/mode-commands.mjs — Standardized command contracts across deployment modes.
3
+ *
4
+ * Provides mode-aware command execution for intake, memory, and workflow operations.
5
+ * Routes to file-based implementations in solo mode, Postgres in team/enterprise mode.
6
+ */
7
+
8
+ // lib/mode-commands.mjs
9
+ // Standardized command contracts across deployment modes
10
+
11
+ import { spawnSync } from 'node:child_process';
12
+
13
+ export const MODE_COMMANDS = {
14
+ // Intake queue operations
15
+ intake: {
16
+ list: async (options = {}) => {
17
+ const mode = getDeploymentMode();
18
+
19
+ if (mode === 'solo') {
20
+ // File-based intake
21
+ const result = spawnSync('find', ['.cx/intake/pending', '-name', '*.json', '-type', 'f'],
22
+ { encoding: 'utf8', cwd: options.cwd });
23
+ return parseIntakeFiles(result.stdout);
24
+ } else {
25
+ // Postgres-based intake
26
+ return queryPostgresIntake(options);
27
+ }
28
+ },
29
+
30
+ show: async (id, options = {}) => {
31
+ const mode = getDeploymentMode();
32
+
33
+ if (mode === 'solo') {
34
+ return readIntakeFile(id, options);
35
+ } else {
36
+ return queryPostgresIntakeItem(id, options);
37
+ }
38
+ },
39
+ },
40
+
41
+ // Storage operations
42
+ storage: {
43
+ sync: async (options = {}) => {
44
+ const mode = getDeploymentMode();
45
+
46
+ // Same interface, different implementation
47
+ if (mode === 'solo') {
48
+ return syncToFileStorage(options);
49
+ } else {
50
+ return syncToPostgresStorage(options);
51
+ }
52
+ },
53
+
54
+ query: async (criteria, options = {}) => {
55
+ // Unified query interface
56
+ return queryStorage(criteria, options);
57
+ },
58
+ },
59
+ };
60
+
61
+ function getDeploymentMode() {
62
+ return process.env.CONSTRUCT_DEPLOYMENT_MODE || 'solo';
63
+ }
64
+
65
+ function parseIntakeFiles(stdout) {
66
+ return stdout.split('\n')
67
+ .filter(Boolean)
68
+ .map(f => ({ id: path.basename(f, '.json'), source: 'file' }));
69
+ }
70
+
71
+ function readIntakeFile(id, options) {
72
+ const fs = require('fs');
73
+ const path = require('path');
74
+ const filePath = path.join(options.cwd || process.cwd(), '.cx/intake/pending', `${id}.json`);
75
+
76
+ if (!fs.existsSync(filePath)) {
77
+ return { error: `Intake item ${id} not found` };
78
+ }
79
+
80
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
81
+ }
82
+
83
+ async function queryPostgresIntake(options) {
84
+ // Would use SQL client
85
+ return { items: [], mode: 'postgres' };
86
+ }
87
+
88
+ async function queryPostgresIntakeItem(id, options) {
89
+ return { id, mode: 'postgres' };
90
+ }
91
+
92
+ async function syncToFileStorage(options) {
93
+ return {
94
+ success: true,
95
+ mode: 'solo',
96
+ backend: 'file',
97
+ };
98
+ }
99
+
100
+ async function syncToPostgresStorage(options) {
101
+ return {
102
+ success: true,
103
+ mode: 'team',
104
+ backend: 'postgres',
105
+ };
106
+ }
107
+
108
+ async function queryStorage(criteria, options) {
109
+ // Unified query - works across modes
110
+ return {
111
+ results: [],
112
+ mode: getDeploymentMode(),
113
+ };
114
+ }
115
+
116
+ export function getCommandInterface(command, operation) {
117
+ const cmd = MODE_COMMANDS[command];
118
+ if (!cmd) return null;
119
+
120
+ const fn = cmd[operation];
121
+ if (!fn) return null;
122
+
123
+ return fn;
124
+ }