@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
package/lib/cost.mjs CHANGED
@@ -1,7 +1,10 @@
1
1
  /**
2
- * lib/cost.mjs — <one-line purpose>
2
+ * lib/cost.mjs — LLM token usage cost estimation and session budget tracking.
3
3
  *
4
- * <2–6 line summary: what it does, who calls it, key side effects.>
4
+ * Maps model IDs to per-token pricing from a static table (with OpenRouter
5
+ * and LiteLLM fallbacks), estimates call cost from usage objects, and
6
+ * accumulates session spend in ~/.cx/session-cost.json. Called by the
7
+ * telemetry pipeline and the MCP session_usage tool.
5
8
  */
6
9
  import { readFileSync, existsSync, writeFileSync } from 'node:fs';
7
10
  import { join } from 'node:path';
@@ -47,17 +50,33 @@ export function normalizeCostEntry(entry = {}) {
47
50
  entry.completion_tokens,
48
51
  entry.completionTokens,
49
52
  );
53
+ const reasoningTokens = numberFrom(
54
+ entry.reasoning_tokens,
55
+ entry.reasoningTokens,
56
+ entry.completion_tokens_details?.reasoning_tokens,
57
+ );
58
+ const providerTotalTokens = numberFrom(
59
+ entry.total_tokens,
60
+ entry.totalTokens,
61
+ inputTokens + outputTokens + reasoningTokens,
62
+ );
63
+ const billedOutputTokens = outputTokens + reasoningTokens;
64
+ const billedTotalTokens = inputTokens + cacheReadInputTokens + cacheCreationInputTokens + billedOutputTokens;
50
65
 
51
66
  return {
52
67
  ...entry,
53
68
  inputTokens,
54
69
  outputTokens,
70
+ reasoningTokens,
55
71
  cacheReadInputTokens,
56
72
  cacheCreationInputTokens,
57
73
  cacheCreation5mInputTokens,
58
74
  cacheCreation1hInputTokens,
59
75
  processedInputTokens: inputTokens + cacheReadInputTokens + cacheCreationInputTokens,
60
- totalTokens: inputTokens + outputTokens,
76
+ providerTotalTokens,
77
+ billedOutputTokens,
78
+ billedTotalTokens,
79
+ totalTokens: providerTotalTokens,
61
80
  costUsd: Number(entry.cost_usd ?? entry.costUsd ?? 0),
62
81
  };
63
82
  }
@@ -92,17 +111,23 @@ export function aggregateCostByAgent(entries) {
92
111
  interactions: 0,
93
112
  inputTokens: 0,
94
113
  outputTokens: 0,
114
+ reasoningTokens: 0,
95
115
  cacheReadInputTokens: 0,
96
116
  cacheCreationInputTokens: 0,
97
117
  processedInputTokens: 0,
118
+ providerTotalTokens: 0,
119
+ billedTotalTokens: 0,
98
120
  costUsd: 0,
99
121
  };
100
122
  acc.interactions += 1;
101
123
  acc.inputTokens += entry.inputTokens;
102
124
  acc.outputTokens += entry.outputTokens;
125
+ acc.reasoningTokens += entry.reasoningTokens;
103
126
  acc.cacheReadInputTokens += entry.cacheReadInputTokens;
104
127
  acc.cacheCreationInputTokens += entry.cacheCreationInputTokens;
105
128
  acc.processedInputTokens += entry.processedInputTokens;
129
+ acc.providerTotalTokens += entry.providerTotalTokens;
130
+ acc.billedTotalTokens += entry.billedTotalTokens;
106
131
  acc.costUsd += entry.costUsd;
107
132
  acc.cacheReadRate = acc.processedInputTokens > 0
108
133
  ? Number((acc.cacheReadInputTokens / acc.processedInputTokens).toFixed(3))
@@ -132,7 +157,47 @@ export function computeCacheStats(entries) {
132
157
  };
133
158
  }
134
159
 
135
- export function summarizeCostData(entries, { days, agent } = {}) {
160
+ /**
161
+ * Map a model id to its provider id. Reads the model string the same way the
162
+ * model router does — prefix matches win, otherwise infer from naming family.
163
+ * Callers use this to look up per-provider billing-mode overrides so that
164
+ * subscription providers (Claude Pro, e.g.) contribute $0 to the headline
165
+ * "actual spend" while still reporting metered-equivalent for context.
166
+ */
167
+ export function providerForModel(modelId) {
168
+ const id = String(modelId ?? '').toLowerCase();
169
+ if (!id) return 'unknown';
170
+ if (id.startsWith('openrouter/')) return 'openrouter';
171
+ if (id.startsWith('github-copilot/')) return 'github-copilot';
172
+ if (id.startsWith('ollama/')) return 'ollama';
173
+ if (id.startsWith('local/')) return 'local';
174
+ if (id.startsWith('anthropic/') || id.startsWith('claude-') || id.includes('/claude-')) return 'anthropic';
175
+ if (id.startsWith('openai/') || id.startsWith('gpt-') || /^o[3-9](-|$)/.test(id)) return 'openai';
176
+ if (id.startsWith('google/') || id.startsWith('gemini-')) return 'gemini';
177
+ if (id.startsWith('mistral/') || id.startsWith('mistralai/')) return 'mistral';
178
+ if (id.startsWith('groq/')) return 'groq';
179
+ return 'unknown';
180
+ }
181
+
182
+ /**
183
+ * Resolve billing mode for a given provider id. Per-provider override in
184
+ * `costs.providers.<id>.billingMode` wins; otherwise fall back to the global
185
+ * `costs.billingMode`. Default is 'metered' (treat $ figures as real spend)
186
+ * which is the safe assumption when nothing is declared.
187
+ */
188
+ export function billingModeForProvider(providerId, costsConfig = {}) {
189
+ const perProvider = costsConfig?.providers?.[providerId]?.billingMode;
190
+ if (perProvider === 'subscription' || perProvider === 'metered' || perProvider === 'mixed') {
191
+ return perProvider;
192
+ }
193
+ const globalMode = costsConfig?.billingMode;
194
+ if (globalMode === 'subscription' || globalMode === 'metered' || globalMode === 'mixed') {
195
+ return globalMode;
196
+ }
197
+ return 'metered';
198
+ }
199
+
200
+ export function summarizeCostData(entries, { days, agent, costsConfig } = {}) {
136
201
  const cutoff = days ? new Date(Date.now() - days * 86_400_000).toISOString() : null;
137
202
  let filtered = cutoff ? entries.filter((e) => e.ts && e.ts >= cutoff) : entries;
138
203
  if (agent) filtered = filtered.filter((e) => (e.agent || 'orchestrator') === agent);
@@ -140,16 +205,53 @@ export function summarizeCostData(entries, { days, agent } = {}) {
140
205
  const normalized = filtered.map((entry) => normalizeCostEntry(entry));
141
206
  const totalInputTokens = normalized.reduce((s, e) => s + e.inputTokens, 0);
142
207
  const totalOutputTokens = normalized.reduce((s, e) => s + e.outputTokens, 0);
143
- const totalTokens = totalInputTokens + totalOutputTokens;
208
+ const totalReasoningTokens = normalized.reduce((s, e) => s + e.reasoningTokens, 0);
209
+ const providerTotalTokens = normalized.reduce((s, e) => s + e.providerTotalTokens, 0);
210
+ const billedTotalTokens = normalized.reduce((s, e) => s + e.billedTotalTokens, 0);
144
211
  const totalCostUsd = normalized.reduce((s, e) => s + e.costUsd, 0);
145
212
  const cacheStats = computeCacheStats(normalized);
146
213
 
214
+ // Per-provider rollup. byProvider[id] = { providerId, billingMode, costUsd, ... }.
215
+ // meteredActualUsd sums only metered/mixed providers; meteredEquivalentUsd is
216
+ // the unconditional total (what the bill would be if everything were metered).
217
+ const byProvider = new Map();
218
+ for (const e of normalized) {
219
+ const providerId = providerForModel(e.model);
220
+ const bucket = byProvider.get(providerId) || {
221
+ providerId,
222
+ billingMode: billingModeForProvider(providerId, costsConfig),
223
+ interactions: 0,
224
+ costUsd: 0,
225
+ inputTokens: 0,
226
+ outputTokens: 0,
227
+ cacheReadInputTokens: 0,
228
+ cacheCreationInputTokens: 0,
229
+ };
230
+ bucket.interactions += 1;
231
+ bucket.costUsd += e.costUsd;
232
+ bucket.inputTokens += e.inputTokens;
233
+ bucket.outputTokens += e.outputTokens;
234
+ bucket.cacheReadInputTokens += e.cacheReadInputTokens;
235
+ bucket.cacheCreationInputTokens += e.cacheCreationInputTokens;
236
+ byProvider.set(providerId, bucket);
237
+ }
238
+ const providerRollup = [...byProvider.values()]
239
+ .map((b) => ({ ...b, costUsd: Number(b.costUsd.toFixed(4)) }))
240
+ .sort((a, b) => b.costUsd - a.costUsd);
241
+ const meteredActualUsd = providerRollup
242
+ .filter((p) => p.billingMode !== 'subscription')
243
+ .reduce((s, p) => s + p.costUsd, 0);
244
+
147
245
  return {
148
246
  interactions: normalized.length,
149
247
  totalInputTokens,
150
248
  totalOutputTokens,
151
- totalTokens,
249
+ totalReasoningTokens,
250
+ providerTotalTokens,
251
+ billedTotalTokens,
152
252
  totalCostUsd: Number(totalCostUsd.toFixed(4)),
253
+ meteredActualUsd: Number(meteredActualUsd.toFixed(4)),
254
+ meteredEquivalentUsd: Number(totalCostUsd.toFixed(4)),
153
255
  processedInputTokens: cacheStats.totalProcessedInputTokens,
154
256
  cacheReadInputTokens: cacheStats.totalCacheReadInputTokens,
155
257
  cacheCreationInputTokens: cacheStats.totalCacheCreationInputTokens,
@@ -159,11 +261,58 @@ export function summarizeCostData(entries, { days, agent } = {}) {
159
261
  cacheReadRate: cacheStats.cacheReadRate,
160
262
  cacheHitRate: cacheStats.cacheReadRate,
161
263
  byAgent: aggregateCostByAgent(normalized),
264
+ byProvider: providerRollup,
162
265
  days: days ?? null,
163
266
  agentFilter: agent ?? null,
164
267
  };
165
268
  }
166
269
 
270
+ /**
271
+ * Estimate cache efficiency: compare expected vs actual cache usage.
272
+ *
273
+ * @param {object} data - normalized cost entry
274
+ * @param {number} expectedCacheableTokens - tokens we expected to cache
275
+ * @returns {{ efficiency: number, waste: number, hitRate: number }}
276
+ */
277
+ export function computeCacheEfficiency(data, expectedCacheableTokens = 0) {
278
+ if (!data) return { efficiency: 0, waste: 0, hitRate: 0 };
279
+
280
+ const actualCacheRead = data.cacheReadInputTokens || 0;
281
+ const actualCacheWrite = data.cacheCreationInputTokens || 0;
282
+ const processedInput = data.processedInputTokens || data.inputTokens || 0;
283
+
284
+ const hitRate = processedInput > 0 ? actualCacheRead / processedInput : 0;
285
+ const writeRatio = expectedCacheableTokens > 0 ? actualCacheWrite / expectedCacheableTokens : 0;
286
+
287
+ // Efficiency: how close are we to expected cache usage?
288
+ const efficiency = expectedCacheableTokens > 0
289
+ ? Math.min(1, actualCacheRead / expectedCacheableTokens)
290
+ : 0;
291
+
292
+ // Waste: expected writes minus actual reads (over-cacheing)
293
+ const waste = Math.max(0, actualCacheWrite - actualCacheRead);
294
+
295
+ return {
296
+ efficiency: Math.round(efficiency * 100) / 100,
297
+ waste,
298
+ hitRate: Math.round(hitRate * 100) / 100,
299
+ expectedCacheableTokens,
300
+ actualCacheRead,
301
+ actualCacheWrite,
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Batch compute cache efficiency for multiple entries.
307
+ *
308
+ * @param {Array} entries - normalized cost entries
309
+ * @param {number} expectedCacheableTokens
310
+ * @returns {Array}
311
+ */
312
+ export function computeBatchCacheEfficiency(entries, expectedCacheableTokens = 0) {
313
+ return (entries || []).map(e => computeCacheEfficiency(e, expectedCacheableTokens));
314
+ }
315
+
167
316
  export function formatCostReport(data, colors = {}) {
168
317
  const C = { bold: '', dim: '', reset: '', green: '', yellow: '', cyan: '', red: '', ...colors };
169
318
  const lines = [];
@@ -175,7 +324,8 @@ export function formatCostReport(data, colors = {}) {
175
324
  if (data.agentFilter) lines.push(`${C.dim}Agent filter: ${data.agentFilter}${C.reset}`);
176
325
 
177
326
  lines.push(`Interactions: ${data.interactions}`);
178
- lines.push(`Total tokens: ${data.totalTokens.toLocaleString()} (${data.totalInputTokens.toLocaleString()} uncached in / ${data.totalOutputTokens.toLocaleString()} out)`);
327
+ lines.push(`Provider total: ${Number(data.providerTotalTokens || 0).toLocaleString()} (${data.totalInputTokens.toLocaleString()} uncached in / ${data.totalOutputTokens.toLocaleString()} out / ${Number(data.totalReasoningTokens || 0).toLocaleString()} reasoning)`);
328
+ lines.push(`Billed total: ${Number(data.billedTotalTokens || 0).toLocaleString()}`);
179
329
  lines.push(`Processed input: ${Number(data.processedInputTokens || data.totalInputTokens || 0).toLocaleString()}`);
180
330
 
181
331
  const readPct = ((data.cacheReadRate ?? data.cacheHitRate ?? 0) * 100).toFixed(1);
@@ -187,15 +337,16 @@ export function formatCostReport(data, colors = {}) {
187
337
  if (data.byAgent.length > 0) {
188
338
  lines.push('');
189
339
  lines.push('By Agent:');
190
- lines.push(` ${'Agent'.padEnd(28)} ${'Turns'.padStart(5)} ${'Input tok'.padStart(10)} ${'Cost'.padStart(8)} ${'Share'.padStart(5)} Cache`);
191
- lines.push(` ${'-'.repeat(28)} ${'-'.repeat(5)} ${'-'.repeat(10)} ${'-'.repeat(8)} ${'-'.repeat(5)} -----`);
340
+ lines.push(` ${'Agent'.padEnd(28)} ${'Turns'.padStart(5)} ${'Provider'.padStart(10)} ${'Billed'.padStart(10)} ${'Cost'.padStart(8)} ${'Share'.padStart(5)} Cache`);
341
+ lines.push(` ${'-'.repeat(28)} ${'-'.repeat(5)} ${'-'.repeat(10)} ${'-'.repeat(10)} ${'-'.repeat(8)} ${'-'.repeat(5)} -----`);
192
342
  for (const a of data.byAgent) {
193
343
  const sharePct = data.totalCostUsd > 0 ? Math.round((a.costUsd / data.totalCostUsd) * 100) : 0;
194
344
  const hitRate = Math.round((a.cacheReadRate || 0) * 100);
195
345
  const shareColor = sharePct >= 50 ? C.red : sharePct >= 25 ? C.yellow : '';
196
346
  lines.push(
197
347
  ` ${a.agent.padEnd(28)} ${String(a.interactions).padStart(5)}` +
198
- ` ${a.inputTokens.toLocaleString().padStart(10)}` +
348
+ ` ${a.providerTotalTokens.toLocaleString().padStart(10)}` +
349
+ ` ${a.billedTotalTokens.toLocaleString().padStart(10)}` +
199
350
  ` ${'$' + a.costUsd.toFixed(4).padStart(7)}` +
200
351
  ` ${shareColor}${String(sharePct + '%').padStart(5)}${C.reset}` +
201
352
  ` ${hitRate}%`,
@@ -208,6 +359,16 @@ export function formatCostReport(data, colors = {}) {
208
359
  lines.push(`${C.dim}No cost data recorded yet. Token usage is logged at session end.${C.reset}`);
209
360
  }
210
361
 
362
+ // Cache efficiency metrics
363
+ if (data.expectedCacheableTokens && data.expectedCacheableTokens > 0) {
364
+ const efficiency = computeCacheEfficiency(data, data.expectedCacheableTokens);
365
+ lines.push('');
366
+ lines.push('Cache Efficiency:');
367
+ lines.push(` Expected cacheable: ${Number(data.expectedCacheableTokens || 0).toLocaleString()} tokens`);
368
+ lines.push(` Actual cache reads: ${Number(data.cacheReadInputTokens || 0).toLocaleString()}`);
369
+ lines.push(` Efficiency: ${efficiency.efficiency * 100}% (${efficiency.waste > 0 ? `${efficiency.waste} over-cached` : 'optimal'})`);
370
+ }
371
+
211
372
  lines.push('');
212
373
  return lines.join('\n');
213
374
  }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * lib/dashboard-static.mjs — build and sync the dashboard static bundle.
3
+ *
4
+ * Keeps `dashboard/dist/` and `lib/server/static/` aligned so the HTTP server
5
+ * serves the current dashboard build from repo-owned assets.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { spawnSync } from 'node:child_process';
11
+
12
+ function walkFiles(rootDir, currentDir = rootDir) {
13
+ if (!fs.existsSync(currentDir)) return [];
14
+ const entries = [];
15
+ for (const name of fs.readdirSync(currentDir)) {
16
+ const fullPath = path.join(currentDir, name);
17
+ const stat = fs.statSync(fullPath);
18
+ if (stat.isDirectory()) {
19
+ entries.push(...walkFiles(rootDir, fullPath));
20
+ } else {
21
+ entries.push(path.relative(rootDir, fullPath));
22
+ }
23
+ }
24
+ return entries.sort();
25
+ }
26
+
27
+ function readBufferIfExists(filePath) {
28
+ if (!fs.existsSync(filePath)) return null;
29
+ return fs.readFileSync(filePath);
30
+ }
31
+
32
+ function compareTrees(sourceDir, targetDir) {
33
+ const sourceFiles = walkFiles(sourceDir);
34
+ const targetFiles = walkFiles(targetDir);
35
+ const sourceSet = new Set(sourceFiles);
36
+ const targetSet = new Set(targetFiles);
37
+ const copied = [];
38
+ const removed = [];
39
+
40
+ for (const relPath of sourceFiles) {
41
+ const source = readBufferIfExists(path.join(sourceDir, relPath));
42
+ const target = readBufferIfExists(path.join(targetDir, relPath));
43
+ if (!target || !source.equals(target)) copied.push(relPath);
44
+ }
45
+
46
+ for (const relPath of targetFiles) {
47
+ if (!sourceSet.has(relPath)) removed.push(relPath);
48
+ }
49
+
50
+ return {
51
+ copied,
52
+ removed,
53
+ sourceFiles,
54
+ targetFiles,
55
+ changed: copied.length > 0 || removed.length > 0,
56
+ inSync: copied.length === 0 && removed.length === 0,
57
+ sourceExists: fs.existsSync(sourceDir),
58
+ };
59
+ }
60
+
61
+ function ensureParentDir(filePath) {
62
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
63
+ }
64
+
65
+ function removeEmptyDirs(dirPath, stopAt) {
66
+ let current = dirPath;
67
+ while (current.startsWith(stopAt) && current !== stopAt) {
68
+ if (!fs.existsSync(current)) {
69
+ current = path.dirname(current);
70
+ continue;
71
+ }
72
+ if (fs.readdirSync(current).length > 0) break;
73
+ fs.rmdirSync(current);
74
+ current = path.dirname(current);
75
+ }
76
+ }
77
+
78
+ export function getDashboardPaths({ rootDir = process.cwd() } = {}) {
79
+ return {
80
+ rootDir,
81
+ dashboardDir: path.join(rootDir, 'dashboard'),
82
+ sourceDir: path.join(rootDir, 'dashboard', 'dist'),
83
+ targetDir: path.join(rootDir, 'lib', 'server', 'static'),
84
+ };
85
+ }
86
+
87
+ export function getDashboardStaticStatus({ rootDir = process.cwd() } = {}) {
88
+ const { sourceDir, targetDir } = getDashboardPaths({ rootDir });
89
+ return compareTrees(sourceDir, targetDir);
90
+ }
91
+
92
+ export function syncDashboardStatic({ rootDir = process.cwd(), check = false } = {}) {
93
+ const { sourceDir, targetDir } = getDashboardPaths({ rootDir });
94
+ if (!fs.existsSync(sourceDir)) {
95
+ throw new Error(`Dashboard build output missing: ${sourceDir}`);
96
+ }
97
+
98
+ const status = compareTrees(sourceDir, targetDir);
99
+ if (check || !status.changed) return status;
100
+
101
+ for (const relPath of status.copied) {
102
+ const sourcePath = path.join(sourceDir, relPath);
103
+ const targetPath = path.join(targetDir, relPath);
104
+ ensureParentDir(targetPath);
105
+ fs.copyFileSync(sourcePath, targetPath);
106
+ }
107
+
108
+ for (const relPath of status.removed) {
109
+ const targetPath = path.join(targetDir, relPath);
110
+ fs.rmSync(targetPath, { force: true });
111
+ removeEmptyDirs(path.dirname(targetPath), targetDir);
112
+ }
113
+
114
+ return {
115
+ ...status,
116
+ inSync: true,
117
+ };
118
+ }
119
+
120
+ export function buildDashboardStatic({
121
+ rootDir = process.cwd(),
122
+ spawnSyncFn = spawnSync,
123
+ } = {}) {
124
+ const { dashboardDir } = getDashboardPaths({ rootDir });
125
+ const result = spawnSyncFn('npm', ['run', 'build'], {
126
+ cwd: dashboardDir,
127
+ encoding: 'utf8',
128
+ stdio: ['ignore', 'pipe', 'pipe'],
129
+ });
130
+ if (result.status !== 0) {
131
+ throw new Error((result.stderr || result.stdout || 'dashboard build failed').trim());
132
+ }
133
+ return result;
134
+ }
135
+
136
+ export async function runDashboardStaticCli(args = [], { rootDir = process.cwd() } = {}) {
137
+ const build = args.includes('--build');
138
+ const check = args.includes('--check');
139
+
140
+ if (build) buildDashboardStatic({ rootDir });
141
+ const status = syncDashboardStatic({ rootDir, check });
142
+
143
+ if (check) {
144
+ if (status.changed) {
145
+ console.error('Dashboard static assets are out of date. Run `construct dashboard:sync --build`.');
146
+ return 1;
147
+ }
148
+ console.log('Dashboard static assets are up to date.');
149
+ return 0;
150
+ }
151
+
152
+ if (status.changed) {
153
+ console.log(`Dashboard static synced: ${status.copied.length} updated, ${status.removed.length} removed.`);
154
+ } else {
155
+ console.log('Dashboard static already up to date.');
156
+ }
157
+ return 0;
158
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * lib/deployment-mode.mjs — Construct deployment mode (solo | team | enterprise).
3
+ *
4
+ * Encodes the deployment posture the rest of the system reads to choose
5
+ * backends for the intake queue, memory, telemetry, and workers. The
6
+ * mode resolves with env > construct.config.json > default precedence
7
+ * (`getDeploymentMode(env, {cwd})`); the resource topology for each mode
8
+ * is derived here, not stored.
9
+ */
10
+ import { loadProjectConfig } from './config/project-config.mjs';
11
+
12
+ export const DEPLOYMENT_MODES = ['solo', 'team', 'enterprise'];
13
+ export const DEFAULT_DEPLOYMENT_MODE = 'solo';
14
+
15
+ export const DEPLOYMENT_MODE_ENV_KEY = 'CONSTRUCT_DEPLOYMENT_MODE';
16
+
17
+ const RESOURCE_TOPOLOGY = {
18
+ solo: {
19
+ queue: 'filesystem',
20
+ memory: 'local',
21
+ database: 'optional',
22
+ telemetry: 'optional',
23
+ workers: 'local',
24
+ policy: 'lightweight',
25
+ mcp: 'direct',
26
+ },
27
+ team: {
28
+ queue: 'postgres',
29
+ memory: 'shared',
30
+ database: 'postgres',
31
+ telemetry: 'central',
32
+ workers: 'docker',
33
+ policy: 'server-side',
34
+ mcp: 'brokered',
35
+ },
36
+ enterprise: {
37
+ queue: 'postgres',
38
+ memory: 'shared',
39
+ database: 'postgres',
40
+ telemetry: 'central',
41
+ workers: 'isolated',
42
+ policy: 'enforceable',
43
+ mcp: 'brokered-signed',
44
+ },
45
+ };
46
+
47
+ const MODE_DESCRIPTIONS = {
48
+ solo: 'Individual usage. Filesystem queue, local repo state, optional Postgres/Docker/telemetry.',
49
+ team: 'Shared usage. Postgres queue, shared memory, Docker worker pool, central telemetry, brokered MCP.',
50
+ enterprise: 'Hardened usage. Adds tenant isolation, RBAC/ABAC scaffolding, isolated worker containers, signed MCP allowlists, mandatory audit on top of team.',
51
+ };
52
+
53
+ export function isValidDeploymentMode(value) {
54
+ return typeof value === 'string' && DEPLOYMENT_MODES.includes(value);
55
+ }
56
+
57
+ export function describeDeploymentMode(mode) {
58
+ return MODE_DESCRIPTIONS[mode] || '';
59
+ }
60
+
61
+ export function getDeploymentMode(env = process.env, { cwd } = {}) {
62
+ const raw = env?.[DEPLOYMENT_MODE_ENV_KEY];
63
+ if (raw) {
64
+ const trimmed = String(raw).trim().toLowerCase();
65
+ return isValidDeploymentMode(trimmed) ? trimmed : DEFAULT_DEPLOYMENT_MODE;
66
+ }
67
+ try {
68
+ const { config } = loadProjectConfig(cwd, env);
69
+ const fromConfig = config?.deployment?.mode;
70
+ if (typeof fromConfig === 'string' && isValidDeploymentMode(fromConfig)) return fromConfig;
71
+ } catch { /* loader is best-effort — fall through to default */ }
72
+ return DEFAULT_DEPLOYMENT_MODE;
73
+ }
74
+
75
+ export function resolveResourceMode(mode = DEFAULT_DEPLOYMENT_MODE) {
76
+ const topology = RESOURCE_TOPOLOGY[mode];
77
+ if (!topology) {
78
+ throw new Error(`Unknown deployment mode: ${mode}. Valid modes: ${DEPLOYMENT_MODES.join(', ')}`);
79
+ }
80
+ return { ...topology };
81
+ }
82
+
83
+ export function describeResourceLine(mode = DEFAULT_DEPLOYMENT_MODE) {
84
+ const r = resolveResourceMode(mode);
85
+ return `queue:${r.queue} · memory:${r.memory} · workers:${r.workers} · telemetry:${r.telemetry}`;
86
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * lib/deprecate.mjs — single-warning-per-process deprecation infrastructure.
3
+ *
4
+ * Usage:
5
+ *
6
+ * import { deprecate } from './deprecate.mjs';
7
+ *
8
+ * export function oldThing() {
9
+ * deprecate('oldThing()', { since: '1.2.0', removeIn: '2.0.0', use: 'newThing()' });
10
+ * return newThing();
11
+ * }
12
+ *
13
+ * Each unique key (defaults to the symbol name) emits at most one stderr
14
+ * warning per process. Operators that want a strict signal can run with
15
+ * `CONSTRUCT_DEPRECATIONS=error` to make every deprecation fail-fast,
16
+ * which is useful in CI to flush out unintended deprecation usage before
17
+ * the next major release.
18
+ *
19
+ * The format is stable so it can be grepped from logs:
20
+ *
21
+ * [construct] deprecated: <symbol> will be removed in v<X.0.0> — use <replacement>
22
+ */
23
+
24
+ const SEEN = new Set();
25
+
26
+ function shouldThrow(env) {
27
+ return env.CONSTRUCT_DEPRECATIONS === 'error';
28
+ }
29
+
30
+ export function deprecate(symbol, opts = {}) {
31
+ const env = opts.env || process.env;
32
+ const key = opts.key || symbol;
33
+ if (SEEN.has(key)) return;
34
+ SEEN.add(key);
35
+
36
+ const since = opts.since ? ` since v${opts.since}` : '';
37
+ const removeIn = opts.removeIn ? ` will be removed in v${opts.removeIn}` : '';
38
+ const use = opts.use ? ` — use ${opts.use}` : '';
39
+ const message = `[construct] deprecated: ${symbol}${since}${removeIn}${use}`;
40
+
41
+ if (shouldThrow(env)) {
42
+ throw new Error(message);
43
+ }
44
+ process.stderr.write(message + '\n');
45
+ }
46
+
47
+ export function _resetDeprecationsForTests() {
48
+ SEEN.clear();
49
+ }