@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
package/lib/status.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  /**
2
- * lib/status.mjs — <one-line purpose>
2
+ * lib/status.mjs — Project health summary for the Construct dashboard and CLI.
3
3
  *
4
- * <2–6 line summary: what it does, who calls it, key side effects.>
4
+ * Reads workflow state, plan.md, tracker config, and MCP surface to produce a
5
+ * structured health object. Called by `construct status`, the MCP status tool,
6
+ * and the session-start bootstrap to surface blocked tasks and open questions.
5
7
  */
6
8
  import { readFileSync, statSync, existsSync, readdirSync } from 'node:fs';
7
9
  import { join, dirname } from 'node:path';
@@ -9,17 +11,19 @@ import { homedir } from 'node:os';
9
11
  import { fileURLToPath } from 'node:url';
10
12
 
11
13
  import { inspectContextState } from './context-state.mjs';
12
- import { inspectWorkflowHealth, loadWorkflow } from './workflow-state.mjs';
13
14
  import { CLI_COMMANDS } from './cli-commands.mjs';
14
15
  import { checkAllFeatures } from './features.mjs';
15
16
  import { loadConstructEnv } from './env-config.mjs';
17
+ import { describeDeploymentMode, getDeploymentMode, resolveResourceMode } from './deployment-mode.mjs';
16
18
  import { getActiveOverlays, getPromotionRequests } from './headhunt.mjs';
17
19
  import { resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from './model-router.mjs';
20
+ import { loadPluginRegistry } from './plugin-registry.mjs';
18
21
  import { readCostLog, summarizeCostData, normalizeCostEntry } from './cost.mjs';
19
22
  import { readEfficiencyLog, summarizeEfficiencyData } from './efficiency.mjs';
20
23
  import { describeSqlStore, sqlStoreHealth } from './storage/sql-store.mjs';
21
24
  import { describeVectorStore } from './storage/vector-store.mjs';
22
25
  import { describeSqlStoreHealth } from './storage/sql-store.mjs';
26
+ import { triggerAutoBackfillIfSparse } from './telemetry/backfill.mjs';
23
27
  const TOTAL_BYTES_WARNING_THRESHOLD = 750_000;
24
28
 
25
29
  function readJSON(path) {
@@ -122,6 +126,12 @@ export function readSessionUsage(homeDir) {
122
126
  interactions: 0,
123
127
  inputTokens: 0,
124
128
  outputTokens: 0,
129
+ reasoningTokens: 0,
130
+ providerTotalTokens: 0,
131
+ billedTotalTokens: 0,
132
+ processedInputTokens: 0,
133
+ cacheReadInputTokens: 0,
134
+ cacheCreationInputTokens: 0,
125
135
  totalTokens: 0,
126
136
  totalCostUsd: 0,
127
137
  summary: 'No token usage recorded yet',
@@ -137,29 +147,40 @@ export function readSessionUsage(homeDir) {
137
147
  timestamp: lastEntry.ts || null,
138
148
  inputTokens: normalizedLast.inputTokens,
139
149
  outputTokens: normalizedLast.outputTokens,
150
+ reasoningTokens: normalizedLast.reasoningTokens,
140
151
  cacheReadInputTokens: normalizedLast.cacheReadInputTokens,
141
152
  cacheCreationInputTokens: normalizedLast.cacheCreationInputTokens,
142
153
  processedInputTokens: normalizedLast.processedInputTokens,
143
- totalTokens: normalizedLast.totalTokens,
154
+ providerTotalTokens: normalizedLast.providerTotalTokens,
155
+ billedTotalTokens: normalizedLast.billedTotalTokens,
156
+ totalTokens: normalizedLast.providerTotalTokens,
144
157
  costUsd: normalizedLast.costUsd,
145
158
  };
146
159
 
147
160
  const data = summarizeCostData(entries);
148
161
 
162
+ const billed = data.billedTotalTokens || 0;
163
+ const cacheReadPct = billed > 0 ? ((data.cacheReadInputTokens / billed) * 100).toFixed(1) : '?';
164
+ const cacheWritePct = billed > 0 ? ((data.cacheCreationInputTokens / billed) * 100).toFixed(1) : '?';
165
+ const freshPct = billed > 0 ? ((((data.totalInputTokens || 0) + (data.totalOutputTokens || 0)) / billed) * 100).toFixed(1) : '?';
166
+
149
167
  return {
150
168
  status: 'available',
151
169
  interactions: data.interactions,
152
170
  inputTokens: data.totalInputTokens,
153
171
  outputTokens: data.totalOutputTokens,
172
+ reasoningTokens: data.totalReasoningTokens,
154
173
  cacheReadInputTokens: data.cacheReadInputTokens,
155
174
  cacheCreationInputTokens: data.cacheCreationInputTokens,
156
175
  processedInputTokens: data.processedInputTokens,
157
176
  cachedTokens: data.cachedTokens,
158
177
  cacheReadRate: data.cacheReadRate,
159
178
  cacheHitRate: data.cacheReadRate,
160
- totalTokens: data.totalTokens,
179
+ providerTotalTokens: data.providerTotalTokens,
180
+ billedTotalTokens: billed,
181
+ totalTokens: data.providerTotalTokens,
161
182
  totalCostUsd: data.totalCostUsd,
162
- summary: `${data.interactions} interaction${data.interactions === 1 ? '' : 's'} · ${data.totalTokens.toLocaleString()} tokens · $${data.totalCostUsd.toFixed(2)}`,
183
+ summary: `${data.interactions} interaction${data.interactions === 1 ? '' : 's'} · ${data.providerTotalTokens.toLocaleString()} provider · ${billed.toLocaleString()} billed (${cacheReadPct}% cache read · ${cacheWritePct}% cache write · ${freshPct}% fresh)`,
163
184
  lastInteraction,
164
185
  source: join(homeDir, '.cx', 'session-cost.jsonl'),
165
186
  lastUpdatedAt: lastInteraction.timestamp,
@@ -229,7 +250,12 @@ function parsePort(value, fallback) {
229
250
  return Number.isFinite(parsed) ? parsed : fallback;
230
251
  }
231
252
 
232
- async function probeHttp(url, { method = 'GET', headers, body, timeout = 2000 } = {}) {
253
+ async function probeHttp(url, { method = 'GET', headers, body, timeout = 5000 } = {}) {
254
+ // 5s is generous on purpose. The dashboard runs many concurrent requests when
255
+ // a page first loads (status + insights + registry + auth + …), which can stall
256
+ // its own event loop just long enough that a 2s timeout fires on a healthy
257
+ // localhost service. Reachability is a yes/no signal — a hung remote dies
258
+ // visibly via ECONNREFUSED long before this, so the extra headroom is free.
233
259
  const controller = new AbortController();
234
260
  const timer = setTimeout(() => controller.abort(), timeout);
235
261
  try {
@@ -242,6 +268,24 @@ async function probeHttp(url, { method = 'GET', headers, body, timeout = 2000 }
242
268
  }
243
269
  }
244
270
 
271
+ // Per-service probe-result cache so back-to-back /api/status calls (page reload,
272
+ // React StrictMode double-mount, multiple components polling) don't pound the
273
+ // same probe URLs. TTL is short so a true outage shows within a few seconds.
274
+ const PROBE_CACHE = new Map();
275
+ const PROBE_CACHE_TTL_MS = 3000;
276
+ function probeCacheKey(service) {
277
+ return `${service.id || service.name}\0${service.method || 'GET'}\0${service.probeUrl || service.url}`;
278
+ }
279
+ function readProbeCache(service, now = Date.now()) {
280
+ const entry = PROBE_CACHE.get(probeCacheKey(service));
281
+ if (!entry) return null;
282
+ if (now - entry.at > PROBE_CACHE_TTL_MS) return null;
283
+ return entry.result;
284
+ }
285
+ function writeProbeCache(service, result, now = Date.now()) {
286
+ PROBE_CACHE.set(probeCacheKey(service), { at: now, result });
287
+ }
288
+
245
289
  function normalizeProbeResult(result) {
246
290
  if (typeof result === 'string') {
247
291
  return { status: result };
@@ -250,28 +294,34 @@ function normalizeProbeResult(result) {
250
294
  }
251
295
 
252
296
  async function defaultProbeService(service) {
297
+ const cached = readProbeCache(service);
298
+ if (cached) return cached;
299
+
300
+ const headers = service.probeHeaders || (service.method === 'POST' ? { 'Content-Type': 'application/json' } : undefined);
253
301
  const result = service.method === 'POST'
254
- ? await probeHttp(service.url, {
255
- method: 'POST',
256
- headers: { 'Content-Type': 'application/json' },
257
- body: service.body,
258
- })
259
- : await probeHttp(service.url);
260
-
261
- if (result.ok) return { status: 'healthy', message: service.healthyMessage ?? 'Reachable' };
262
- if (result.statusCode) return { status: 'degraded', message: `HTTP ${result.statusCode}` };
263
- if (result.error?.name === 'AbortError') return { status: 'unavailable', message: 'Timed out' };
264
- if (result.error?.cause?.code === 'ECONNREFUSED' || result.error?.code === 'ECONNREFUSED') {
265
- return { status: 'unavailable', message: 'Connection refused' };
302
+ ? await probeHttp(service.url, { method: 'POST', headers, body: service.body })
303
+ : await probeHttp(service.url, headers ? { headers } : undefined);
304
+
305
+ let outcome;
306
+ if (result.ok) outcome = { status: 'healthy', message: service.healthyMessage ?? 'Reachable' };
307
+ else if (result.statusCode) outcome = { status: 'degraded', message: `HTTP ${result.statusCode}` };
308
+ else if (result.error?.name === 'AbortError') outcome = { status: 'unavailable', message: 'Timed out' };
309
+ else if (result.error?.cause?.code === 'ECONNREFUSED' || result.error?.code === 'ECONNREFUSED') {
310
+ outcome = { status: 'unavailable', message: 'Connection refused' };
311
+ } else {
312
+ outcome = { status: 'unavailable', message: result.error?.message ?? 'Connection failed' };
266
313
  }
267
- return { status: 'unavailable', message: result.error?.message ?? 'Connection failed' };
314
+
315
+ writeProbeCache(service, outcome);
316
+ return outcome;
268
317
  }
269
318
 
270
- async function fetchLangfuseTelemetryStatus(env, { timeout = 2500 } = {}) {
271
- const baseUrl = (env.LANGFUSE_BASEURL ?? 'https://cloud.langfuse.com').replace(/\/$/, '');
272
- const key = env.LANGFUSE_PUBLIC_KEY;
273
- const secret = env.LANGFUSE_SECRET_KEY;
274
- if (!key || !secret) return { status: 'unavailable', summary: 'Langfuse credentials not configured' };
319
+ async function fetchTelemetryStatus(env, { timeout = 2500 } = {}) {
320
+ const baseUrl = (env.CONSTRUCT_TELEMETRY_URL ?? '').replace(/\/$/, '');
321
+ const key = env.CONSTRUCT_TELEMETRY_PUBLIC_KEY;
322
+ const secret = env.CONSTRUCT_TELEMETRY_SECRET_KEY;
323
+ if (!key || !secret) return { status: 'unavailable', summary: 'Telemetry credentials not configured' };
324
+ if (!baseUrl) return { status: 'unavailable', summary: 'CONSTRUCT_TELEMETRY_URL not set' };
275
325
  const headers = {
276
326
  Authorization: `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}`,
277
327
  };
@@ -279,7 +329,10 @@ async function fetchLangfuseTelemetryStatus(env, { timeout = 2500 } = {}) {
279
329
  const timer = setTimeout(() => controller.abort(), timeout);
280
330
  try {
281
331
  const res = await fetch(`${baseUrl}/api/public/traces?limit=25`, { headers, signal: controller.signal });
282
- if (!res.ok) return { status: 'unavailable', summary: `Langfuse HTTP ${res.status}` };
332
+ if (res.status === 401 || res.status === 403) {
333
+ return { status: 'credentials-invalid', summary: `Telemetry credentials rejected (HTTP ${res.status}) — run: construct init` };
334
+ }
335
+ if (!res.ok) return { status: 'degraded', summary: `Telemetry HTTP ${res.status}` };
283
336
  const json = await res.json().catch(() => ({}));
284
337
  const traces = Array.isArray(json.data) ? json.data : [];
285
338
  const counts = { rich: 0, partial: 0, sparse: 0 };
@@ -291,8 +344,6 @@ async function fetchLangfuseTelemetryStatus(env, { timeout = 2500 } = {}) {
291
344
  const metaKeys = trace?.metadata && typeof trace.metadata === 'object' ? Object.keys(trace.metadata).length : 0;
292
345
  const hasRichMetadata = metaKeys >= 5;
293
346
  const hasPayload = hasInput || hasOutput || metaKeys > 0;
294
- // Rich: has meaningful input/output AND (at least one observation OR rich metadata with 5+ keys).
295
- // This correctly scores event-style traces (session, message) that carry metadata instead of spans.
296
347
  if ((hasInput || hasOutput) && (observationCount >= 1 || hasRichMetadata)) counts.rich += 1;
297
348
  else if (hasPayload || observationCount >= 1) counts.partial += 1;
298
349
  else counts.sparse += 1;
@@ -308,19 +359,18 @@ async function fetchLangfuseTelemetryStatus(env, { timeout = 2500 } = {}) {
308
359
 
309
360
  return {
310
361
  status,
311
- backend: 'langfuse',
312
362
  total,
313
363
  rich: counts.rich,
314
364
  partial: counts.partial,
315
365
  sparse: counts.sparse,
316
366
  coverage: Number(coverage.toFixed(2)),
317
367
  summary: total > 0
318
- ? `Langfuse reachable · ${total} traces · rich ${counts.rich} · partial ${counts.partial} · sparse ${counts.sparse} · coverage ${(coverage * 100).toFixed(0)}%`
319
- : 'Langfuse reachable · no traces yet',
368
+ ? `Telemetry reachable · ${total} traces · rich ${counts.rich} · partial ${counts.partial} · sparse ${counts.sparse} · coverage ${(coverage * 100).toFixed(0)}%`
369
+ : 'Telemetry reachable · no traces yet',
320
370
  };
321
371
  } catch (err) {
322
372
  const msg = err?.name === 'AbortError' ? 'timed out' : err.message;
323
- return { status: 'unavailable', summary: `Langfuse ${msg}` };
373
+ return { status: 'unavailable', summary: `Telemetry ${msg}` };
324
374
  } finally {
325
375
  clearTimeout(timer);
326
376
  }
@@ -335,7 +385,7 @@ function summarizeRuntime(services) {
335
385
  const optionalServices = services.filter((service) => service.impactsOverall === false);
336
386
 
337
387
  const healthy = coreServices.filter((service) => service.status === 'healthy').length;
338
- const degraded = coreServices.filter((service) => service.status === 'degraded').length;
388
+ const degraded = coreServices.filter((service) => service.status === 'degraded' || service.status === 'credentials-invalid').length;
339
389
  const unavailable = coreServices.filter((service) => service.status === 'unavailable').length;
340
390
  const optionalUnavailable = optionalServices.filter((service) => service.status !== 'healthy').length;
341
391
 
@@ -375,42 +425,51 @@ function summarizeIntegrations(features) {
375
425
  return { counts, summary: summary || 'No integrations detected' };
376
426
  }
377
427
 
378
- function workflowStatusSummary(workflow, cwd) {
379
- const health = inspectWorkflowHealth(workflow, { cwd });
428
+ function summarizePlugins(pluginRegistry) {
429
+ const plugins = pluginRegistry?.plugins ?? [];
430
+ const external = plugins.filter((plugin) => !plugin.builtIn);
431
+ const invalidCount = pluginRegistry?.errors?.length ?? 0;
432
+ const totalMcpCount = pluginRegistry?.mcps?.length ?? 0;
433
+
434
+ let status = 'healthy';
435
+ if (invalidCount > 0) status = 'degraded';
436
+ else if (external.length === 0) status = 'configured';
437
+
438
+ const summary = [
439
+ `${plugins.length} plugin${plugins.length === 1 ? '' : 's'}`,
440
+ `${external.length} external`,
441
+ `${totalMcpCount} MCP entries`,
442
+ invalidCount ? `${invalidCount} errors` : null,
443
+ ].filter(Boolean).join(' · ');
444
+
380
445
  return {
381
- cwd,
382
- exists: health.exists,
383
- status: health.alignment.status,
384
- summary: health.summary,
385
- state: workflow,
386
- findings: health.alignment.findings,
446
+ status,
447
+ total: plugins.length,
448
+ external: external.length,
449
+ builtIn: plugins.length - external.length,
450
+ totalMcpCount,
451
+ invalidCount,
452
+ summary,
387
453
  };
388
454
  }
389
455
 
390
456
  export function buildPublicHealthSurface({
391
457
  cwd = process.cwd(),
392
458
  contextInspection = inspectContextState(cwd),
393
- workflow = loadWorkflow(cwd),
394
459
  executionContractModel = null,
395
460
  } = {}) {
396
- const workflowHealth = inspectWorkflowHealth(workflow, { cwd });
397
-
398
461
  return {
399
- activeTask: workflowHealth.activeTask,
400
462
  context: {
401
463
  hasFile: Boolean(contextInspection?.hasFile),
402
464
  source: contextInspection?.source ?? 'missing',
403
465
  savedAt: contextInspection?.savedAt ?? null,
404
466
  summary: contextInspection?.summary ?? null,
405
467
  },
406
- workflow: {
407
- exists: workflowHealth.exists,
408
- phase: workflowHealth.phase,
409
- lifecycleStatus: workflowHealth.lifecycleStatus,
410
- currentTaskKey: workflowHealth.currentTaskKey,
411
- summary: workflowHealth.summary,
468
+ coordination: {
469
+ authority: 'external-tracker-plus-plan',
470
+ fileOwnershipRule: 'single-writer',
471
+ memoryRole: 'cross-session-recall',
412
472
  },
413
- alignment: workflowHealth.alignment,
414
473
  metadataPresence: {
415
474
  executionContractModel: Boolean(executionContractModel?.version),
416
475
  contextState: contextInspection?.source === 'json',
@@ -419,18 +478,91 @@ export function buildPublicHealthSurface({
419
478
  }
420
479
 
421
480
  function traceBackendDefinition(env) {
422
- const langfuseUrl = (env.LANGFUSE_BASEURL ?? 'https://cloud.langfuse.com').replace(/\/$/, '');
481
+ const url = (env.CONSTRUCT_TELEMETRY_URL ?? '').replace(/\/$/, '');
482
+ const pubKey = env.CONSTRUCT_TELEMETRY_PUBLIC_KEY || '';
483
+ const secKey = env.CONSTRUCT_TELEMETRY_SECRET_KEY || '';
484
+ const auth = pubKey && secKey ? `Basic ${Buffer.from(`${pubKey}:${secKey}`).toString('base64')}` : '';
423
485
  return {
424
- id: 'langfuse',
425
- name: 'Langfuse',
426
- url: langfuseUrl,
427
- probeUrl: `${langfuseUrl}/api/public/health`,
486
+ id: 'telemetry',
487
+ name: 'Telemetry',
488
+ url: url || '(not configured)',
489
+ probeUrl: url ? `${url}/api/public/traces?limit=1` : '',
490
+ probeHeaders: auth ? { Authorization: auth } : undefined,
428
491
  runtime: 'live',
429
492
  note: 'Trace backend',
430
493
  healthyMessage: 'Reachable',
431
494
  };
432
495
  }
433
496
 
497
+ /**
498
+ * In-process self-check for when Construct is serving its own dashboard.
499
+ * Rather than skipping the probe and asserting "healthy", we actually measure
500
+ * the process state and surface real problems.
501
+ *
502
+ * Checks:
503
+ * - Heap usage — warn at 80%, critical at 95%
504
+ * - Process uptime — surface as context
505
+ * - Embed daemon — read state file and report if stale or crashed
506
+ *
507
+ * Returns { status: 'healthy'|'degraded'|'unavailable', message: string }
508
+ */
509
+ function buildSelfCheck() {
510
+ const problems = [];
511
+ const notes = [];
512
+
513
+ // Memory
514
+ try {
515
+ const mem = process.memoryUsage();
516
+ const heapPct = mem.heapUsed / mem.heapTotal;
517
+ if (heapPct >= 0.95) {
518
+ problems.push(`heap ${Math.round(heapPct * 100)}% (critical)`);
519
+ } else if (heapPct >= 0.80) {
520
+ problems.push(`heap ${Math.round(heapPct * 100)}% (high)`);
521
+ } else {
522
+ notes.push(`heap ${Math.round(heapPct * 100)}%`);
523
+ }
524
+ } catch { /* non-fatal */ }
525
+
526
+ // Uptime
527
+ try {
528
+ const uptimeSec = process.uptime();
529
+ const h = Math.floor(uptimeSec / 3600);
530
+ const m = Math.floor((uptimeSec % 3600) / 60);
531
+ notes.push(`up ${h}h${m}m`);
532
+ } catch { /* non-fatal */ }
533
+
534
+ // Embed daemon state
535
+ try {
536
+ const stateFile = join(homedir(), '.cx', 'runtime', 'embed-daemon.json');
537
+ if (existsSync(stateFile)) {
538
+ const state = JSON.parse(readFileSync(stateFile, 'utf8'));
539
+ if (state.status === 'running' && state.pid) {
540
+ // Verify the PID is actually alive
541
+ try {
542
+ process.kill(state.pid, 0);
543
+ notes.push(`embed daemon pid ${state.pid}`);
544
+ } catch {
545
+ problems.push(`embed daemon pid ${state.pid} is not running (state file stale)`);
546
+ }
547
+ } else if (state.status === 'stopped') {
548
+ notes.push('embed daemon stopped');
549
+ }
550
+ }
551
+ } catch { /* non-fatal */ }
552
+
553
+ if (problems.length) {
554
+ return {
555
+ status: problems.some(p => p.includes('critical')) ? 'unavailable' : 'degraded',
556
+ message: problems.join('; ') + (notes.length ? ` — ${notes.join(', ')}` : ''),
557
+ };
558
+ }
559
+
560
+ return {
561
+ status: 'healthy',
562
+ message: ['Serving status API', ...notes].join(' — '),
563
+ };
564
+ }
565
+
434
566
  function serviceDefinitions(env, dashboardPort, selfDashboard) {
435
567
  const memoryPort = parsePort(env.MEMORY_PORT, 8765);
436
568
  const bridgePort = parsePort(env.BRIDGE_PORT, 5173);
@@ -443,7 +575,7 @@ function serviceDefinitions(env, dashboardPort, selfDashboard) {
443
575
  runtime: 'live',
444
576
  note: selfDashboard ? 'Current process' : 'Dashboard API',
445
577
  healthyMessage: selfDashboard ? 'Serving status API' : 'Reachable',
446
- selfHealthy: selfDashboard,
578
+ selfCheck: selfDashboard ? buildSelfCheck() : null,
447
579
  },
448
580
  traceBackendDefinition(env),
449
581
  {
@@ -486,12 +618,19 @@ export async function buildStatus({
486
618
  const pkg = readJSON(join(rootDir, 'package.json')) ?? {};
487
619
  const registry = readJSON(join(rootDir, 'agents', 'registry.json')) ?? {};
488
620
  const settings = readJSON(join(homeDir, '.claude', 'settings.json')) ?? {};
489
- const features = await checkAllFeatures({ homeDir, env: mergedEnv });
621
+ const features = await checkAllFeatures({ homeDir, cwd, env: mergedEnv });
622
+ const pluginRegistry = loadPluginRegistry({ cwd, homeDir, rootDir, env: mergedEnv });
490
623
  const services = [];
491
624
 
492
625
  for (const definition of serviceDefinitions(mergedEnv, resolvedDashboardPort, selfDashboard)) {
493
- if (definition.selfHealthy) {
494
- services.push({ ...definition, status: 'healthy', message: definition.healthyMessage ?? 'Reachable' });
626
+ if (definition.selfCheck) {
627
+ const check = definition.selfCheck;
628
+ services.push({
629
+ ...definition,
630
+ status: check.status,
631
+ message: check.message,
632
+ selfCheck: undefined,
633
+ });
495
634
  continue;
496
635
  }
497
636
 
@@ -500,8 +639,9 @@ export async function buildStatus({
500
639
  url: definition.probeUrl ?? definition.url,
501
640
  }));
502
641
 
642
+ const { probeHeaders, ...safeDef } = definition;
503
643
  services.push({
504
- ...definition,
644
+ ...safeDef,
505
645
  status: result.status ?? 'unavailable',
506
646
  message: result.message ?? '',
507
647
  });
@@ -509,14 +649,16 @@ export async function buildStatus({
509
649
 
510
650
  const runtime = summarizeRuntime(services);
511
651
  const integrations = summarizeIntegrations(features);
512
- const workflow = loadWorkflow(cwd);
513
- const workflowSummary = workflowStatusSummary(workflow, cwd);
652
+ const plugins = summarizePlugins(pluginRegistry);
514
653
  const contextInspection = inspectContextState(cwd);
515
654
  const sessionEfficiency = readSessionEfficiency(homeDir);
516
655
  const sessionUsage = readSessionUsage(homeDir);
517
656
  const telemetryRichness = readTelemetryRichness(homeDir)
518
657
  ?? summarizeTelemetryHealth()
519
- ?? await fetchLangfuseTelemetryStatus(mergedEnv);
658
+ ?? await fetchTelemetryStatus(mergedEnv);
659
+
660
+ // Fire-and-forget: post backfill observations to sparse traces when coverage is low.
661
+ triggerAutoBackfillIfSparse(telemetryRichness, { ...mergedEnv });
520
662
  const efficiencyDigest = summarizeEfficiencyData(readEfficiencyLog(homeDir));
521
663
  const activeOverlays = getActiveOverlays(cwd);
522
664
  const sqlStore = describeSqlStore(mergedEnv);
@@ -526,13 +668,12 @@ export async function buildStatus({
526
668
  const executionContractModel = resolveExecutionContractModelMetadata({
527
669
  envValues: mergedEnv,
528
670
  registryModels: registry.models ?? {},
529
- requestedTier: selectModelTierForWorkCategory(workflowSummary.state?.tasks?.find((task) => task.key === workflowSummary.state?.currentTaskKey)?.workCategory),
671
+ requestedTier: selectModelTierForWorkCategory(null),
530
672
  workCategory: null,
531
673
  });
532
674
  const publicHealth = buildPublicHealthSurface({
533
675
  cwd,
534
676
  contextInspection,
535
- workflow,
536
677
  executionContractModel,
537
678
  });
538
679
 
@@ -563,15 +704,45 @@ export async function buildStatus({
563
704
  }
564
705
  }
565
706
 
707
+ const deploymentMode = getDeploymentMode(mergedEnv);
708
+ const resourceMode = resolveResourceMode(deploymentMode);
709
+
566
710
  return {
567
711
  version: pkg.version ?? '1.0.0',
568
712
  lastSync: newestMtime(join(homeDir, '.claude', 'agents')),
713
+ deployment: {
714
+ mode: deploymentMode,
715
+ resourceMode,
716
+ description: describeDeploymentMode(deploymentMode),
717
+ },
569
718
  system: {
570
719
  overall: runtime,
571
720
  services,
572
721
  integrations,
722
+ plugins,
573
723
  },
574
724
  features,
725
+ plugins: {
726
+ status: plugins.status,
727
+ summary: plugins.summary,
728
+ directories: pluginRegistry.pluginDirs,
729
+ errors: pluginRegistry.errors,
730
+ entries: pluginRegistry.plugins.map((plugin) => ({
731
+ id: plugin.id,
732
+ name: plugin.name,
733
+ version: plugin.version,
734
+ description: plugin.description,
735
+ capabilities: plugin.capabilities ?? [],
736
+ builtIn: Boolean(plugin.builtIn),
737
+ manifestPath: plugin.manifestPath,
738
+ mcpCount: (plugin.mcps ?? []).length,
739
+ mcps: (plugin.mcps ?? []).map((mcp) => ({
740
+ id: mcp.id,
741
+ name: mcp.name,
742
+ category: mcp.category,
743
+ })),
744
+ })),
745
+ },
575
746
  personas,
576
747
  specialists,
577
748
  hooks,
@@ -579,7 +750,6 @@ export async function buildStatus({
579
750
  commands: listCommands(rootDir),
580
751
  cliCommands: CLI_COMMANDS,
581
752
  mcpServers: Object.keys(settings.mcpServers ?? {}),
582
- workflow: workflowSummary,
583
753
  publicHealth,
584
754
  storage: {
585
755
  sql: sqlStore,
@@ -628,7 +798,11 @@ export function formatStatusReport(status) {
628
798
  lines.push('════════════════');
629
799
  lines.push('');
630
800
  lines.push(`Overall: ${statusLabel(status.system.overall.status)} · ${status.system.overall.summary}`);
631
- lines.push(`Workflow: ${statusLabel(status.workflow.status)} · ${status.workflow.summary}`);
801
+ if (status.deployment?.mode) {
802
+ const r = status.deployment.resourceMode || {};
803
+ lines.push(`Deployment: ${status.deployment.mode} · queue:${r.queue} · workers:${r.workers} · telemetry:${r.telemetry}`);
804
+ }
805
+ lines.push('Coordination: external tracker + plan.md · single-writer per file · cass-memory for recall');
632
806
  if (status.executionContractModel?.selectedTier && status.executionContractModel?.selectedModel) {
633
807
  lines.push(`Execution contract: ${status.executionContractModel.selectedTier} · ${status.executionContractModel.selectedModel} (${status.executionContractModel.selectedModelSource})`);
634
808
  }
@@ -652,7 +826,13 @@ export function formatStatusReport(status) {
652
826
  : '';
653
827
  lines.push(`Usage: ${statusLabel(status.sessionUsage.status)} · ${status.sessionUsage.summary}${cacheNote}`);
654
828
  if (status.sessionUsage.lastInteraction) {
655
- lines.push(`Last interaction: ${status.sessionUsage.lastInteraction.totalTokens.toLocaleString()} tokens (${status.sessionUsage.lastInteraction.inputTokens.toLocaleString()} in / ${status.sessionUsage.lastInteraction.outputTokens.toLocaleString()} out)`);
829
+ lines.push(
830
+ `Last interaction: ${status.sessionUsage.lastInteraction.providerTotalTokens.toLocaleString()} provider total` +
831
+ ` · ${status.sessionUsage.lastInteraction.billedTotalTokens.toLocaleString()} billed total` +
832
+ ` (${status.sessionUsage.lastInteraction.inputTokens.toLocaleString()} uncached in / ` +
833
+ `${status.sessionUsage.lastInteraction.outputTokens.toLocaleString()} out / ` +
834
+ `${Number(status.sessionUsage.lastInteraction.reasoningTokens || 0).toLocaleString()} reasoning)`,
835
+ );
656
836
  }
657
837
  }
658
838
  if (status.telemetryRichness) {
@@ -683,6 +863,17 @@ export function formatStatusReport(status) {
683
863
  for (const feature of status.features) {
684
864
  lines.push(` ${serviceIcon(feature.status)} ${feature.name.padEnd(22)} ${statusLabel(feature.status)} — ${feature.message}`);
685
865
  }
866
+ if (status.plugins) {
867
+ lines.push('');
868
+ lines.push(`Plugins: ${statusLabel(status.plugins.status)} · ${status.plugins.summary}`);
869
+ for (const plugin of status.plugins.entries ?? []) {
870
+ const source = plugin.builtIn ? 'built-in' : plugin.manifestPath;
871
+ lines.push(` ${serviceIcon(plugin.builtIn ? 'configured' : 'healthy')} ${plugin.name.padEnd(22)} ${plugin.version} — ${source}`);
872
+ }
873
+ for (const error of status.plugins.errors ?? []) {
874
+ lines.push(` ${serviceIcon('degraded')} manifest error — ${error}`);
875
+ }
876
+ }
686
877
  return `${lines.join('\n')}\n`;
687
878
  }
688
879