@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
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lib/ollama-manager.mjs — Ollama local LLM management.
4
+ *
5
+ * Provides commands for checking Ollama status, listing models,
6
+ * pulling new models, and configuring Ollama as a provider.
7
+ */
8
+
9
+ import { execSync } from 'node:child_process';
10
+ import { join } from 'node:path';
11
+ import { homedir } from 'node:os';
12
+
13
+ const OLLAMA_BASE_URL = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
14
+
15
+ /**
16
+ * Check if Ollama is installed and running
17
+ */
18
+ export function checkOllamaStatus() {
19
+ try {
20
+ execSync('which ollama', { stdio: 'pipe' });
21
+ const installed = true;
22
+
23
+ try {
24
+ const response = execSync(`curl -s ${OLLAMA_BASE_URL}/api/tags`, {
25
+ encoding: 'utf8',
26
+ timeout: 3000
27
+ });
28
+ const data = JSON.parse(response);
29
+ return {
30
+ installed,
31
+ running: true,
32
+ models: data.models || [],
33
+ baseUrl: OLLAMA_BASE_URL
34
+ };
35
+ } catch (e) {
36
+ return {
37
+ installed,
38
+ running: false,
39
+ error: 'Ollama server not responding',
40
+ baseUrl: OLLAMA_BASE_URL
41
+ };
42
+ }
43
+ } catch (e) {
44
+ return {
45
+ installed: false,
46
+ running: false,
47
+ error: 'Ollama not installed'
48
+ };
49
+ }
50
+ }
51
+
52
+ /**
53
+ * List available Ollama models
54
+ */
55
+ export function listModels() {
56
+ try {
57
+ const response = execSync(`curl -s ${OLLAMA_BASE_URL}/api/tags`, {
58
+ encoding: 'utf8',
59
+ timeout: 5000
60
+ });
61
+ const data = JSON.parse(response);
62
+ return data.models || [];
63
+ } catch (e) {
64
+ throw new Error(`Failed to fetch models: ${e.message}`);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Pull a model from Ollama registry
70
+ */
71
+ export function pullModel(modelName, { verbose = false } = {}) {
72
+ console.log(`Pulling ${modelName}...`);
73
+ try {
74
+ const output = execSync(`ollama pull ${modelName}`, {
75
+ encoding: 'utf8',
76
+ stdio: verbose ? 'inherit' : 'pipe'
77
+ });
78
+ return { success: true, output };
79
+ } catch (e) {
80
+ return { success: false, error: e.message };
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Remove a model
86
+ */
87
+ export function removeModel(modelName) {
88
+ try {
89
+ execSync(`ollama rm ${modelName}`, { encoding: 'utf8' });
90
+ return { success: true };
91
+ } catch (e) {
92
+ return { success: false, error: e.message };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Show model details
98
+ */
99
+ export function showModel(modelName) {
100
+ try {
101
+ const response = execSync(`curl -s ${OLLAMA_BASE_URL}/api/show -d '{"name":"${modelName}"}'`, {
102
+ encoding: 'utf8',
103
+ timeout: 5000
104
+ });
105
+ return JSON.parse(response);
106
+ } catch (e) {
107
+ throw new Error(`Failed to fetch model details: ${e.message}`);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Run a quick inference test
113
+ */
114
+ export function testModel(modelName, prompt = 'Say hello in one sentence.') {
115
+ try {
116
+ const response = execSync(`curl -s ${OLLAMA_BASE_URL}/api/generate -d '${JSON.stringify({
117
+ model: modelName,
118
+ prompt: prompt,
119
+ stream: false
120
+ })}'`, {
121
+ encoding: 'utf8',
122
+ timeout: 30000
123
+ });
124
+ const data = JSON.parse(response);
125
+ return {
126
+ success: true,
127
+ response: data.response,
128
+ duration: data.total_duration ? `${(data.total_duration / 1e9).toFixed(2)}s` : 'unknown'
129
+ };
130
+ } catch (e) {
131
+ return { success: false, error: e.message };
132
+ }
133
+ }
134
+
135
+ /**
136
+ * CLI entry point
137
+ */
138
+ export async function cmdOllama(argv = process.argv.slice(2)) {
139
+ const command = argv[0];
140
+ const args = argv.slice(1);
141
+
142
+ if (!command || command === '--help' || command === '-h') {
143
+ printHelp();
144
+ return;
145
+ }
146
+
147
+ switch (command) {
148
+ case 'status':
149
+ await cmdOllamaStatus();
150
+ break;
151
+ case 'list':
152
+ case 'ls':
153
+ await cmdOllamaList(args);
154
+ break;
155
+ case 'pull':
156
+ await cmdOllamaPull(args);
157
+ break;
158
+ case 'rm':
159
+ case 'remove':
160
+ await cmdOllamaRemove(args);
161
+ break;
162
+ case 'show':
163
+ await cmdOllamaShow(args);
164
+ break;
165
+ case 'test':
166
+ await cmdOllamaTest(args);
167
+ break;
168
+ case 'setup':
169
+ await cmdOllamaSetup(args);
170
+ break;
171
+ default:
172
+ console.error(`Unknown command: ${command}`);
173
+ printHelp();
174
+ process.exit(1);
175
+ }
176
+ }
177
+
178
+ function printHelp() {
179
+ console.log(`
180
+ Usage: construct ollama <command> [options]
181
+
182
+ Commands:
183
+ status Check if Ollama is installed and running
184
+ list, ls List available local models
185
+ pull <model> Pull a model from Ollama registry
186
+ rm <model> Remove a local model
187
+ show <model> Show model details (architecture, parameters)
188
+ test <model> Run a quick inference test
189
+ setup Interactive setup: install, start service, pull default model
190
+
191
+ Options:
192
+ --verbose Show detailed output (for pull)
193
+ --help, -h Show this help message
194
+
195
+ Examples:
196
+ construct ollama status
197
+ construct ollama list
198
+ construct ollama pull llama3.2
199
+ construct ollama test llama3.2
200
+ construct ollama setup --yes
201
+ `);
202
+ }
203
+
204
+ async function cmdOllamaStatus() {
205
+ const status = checkOllamaStatus();
206
+
207
+ if (!status.installed) {
208
+ console.log('❌ Ollama is not installed');
209
+ console.log(' Install with: brew install ollama');
210
+ process.exit(1);
211
+ }
212
+
213
+ if (!status.running) {
214
+ console.log('⚠️ Ollama is installed but not running');
215
+ console.log(' Start with: brew services start ollama');
216
+ console.log(` Base URL: ${status.baseUrl}`);
217
+ process.exit(1);
218
+ }
219
+
220
+ console.log('✅ Ollama is running');
221
+ console.log(` Base URL: ${status.baseUrl}`);
222
+ console.log(` Models: ${status.models.length}`);
223
+
224
+ if (status.models.length > 0) {
225
+ console.log('\n Available models:');
226
+ status.models.forEach(m => {
227
+ console.log(` - ${m.name} (${formatSize(m.size)})`);
228
+ });
229
+ }
230
+ }
231
+
232
+ async function cmdOllamaList(args) {
233
+ const status = checkOllamaStatus();
234
+ if (!status.running) {
235
+ console.error('Error: Ollama is not running. Start with: brew services start ollama');
236
+ process.exit(1);
237
+ }
238
+
239
+ const models = listModels();
240
+ if (models.length === 0) {
241
+ console.log('No models installed. Pull one with: construct ollama pull <model>');
242
+ console.log('\nPopular models:');
243
+ console.log(' - llama3.2 (3B, fast)');
244
+ console.log(' - llama3.1:8b (8B, balanced)');
245
+ console.log(' - llama3.1:70b (70B, reasoning)');
246
+ console.log(' - qwen2.5:7b (7B, balanced)');
247
+ console.log(' - mistral:7b (7B, fast)');
248
+ return;
249
+ }
250
+
251
+ console.log('NAME'.padEnd(25), 'SIZE'.padEnd(12), 'MODIFIED');
252
+ console.log('─'.repeat(50));
253
+ models.forEach(m => {
254
+ const name = m.name.padEnd(25);
255
+ const size = formatSize(m.size).padEnd(12);
256
+ const modified = new Date(m.modified_at).toLocaleDateString();
257
+ console.log(name, size, modified);
258
+ });
259
+ }
260
+
261
+ async function cmdOllamaPull(args) {
262
+ const modelName = args[0];
263
+ const verbose = args.includes('--verbose') || args.includes('-v');
264
+
265
+ if (!modelName) {
266
+ console.error('Error: Model name required');
267
+ console.log('Usage: construct ollama pull <model>');
268
+ console.log('\nPopular models:');
269
+ console.log(' - llama3.2 (3B, fast)');
270
+ console.log(' - llama3.1:8b (8B, balanced)');
271
+ console.log(' - llama3.1:70b (70B, reasoning)');
272
+ process.exit(1);
273
+ }
274
+
275
+ const result = pullModel(modelName, { verbose });
276
+ if (result.success) {
277
+ console.log(`✅ Successfully pulled ${modelName}`);
278
+ } else {
279
+ console.error(`❌ Failed to pull ${modelName}: ${result.error}`);
280
+ process.exit(1);
281
+ }
282
+ }
283
+
284
+ async function cmdOllamaRemove(args) {
285
+ const modelName = args[0];
286
+
287
+ if (!modelName) {
288
+ console.error('Error: Model name required');
289
+ console.log('Usage: construct ollama rm <model>');
290
+ process.exit(1);
291
+ }
292
+
293
+ const result = removeModel(modelName);
294
+ if (result.success) {
295
+ console.log(`✅ Removed ${modelName}`);
296
+ } else {
297
+ console.error(`❌ Failed to remove ${modelName}: ${result.error}`);
298
+ process.exit(1);
299
+ }
300
+ }
301
+
302
+ async function cmdOllamaShow(args) {
303
+ const modelName = args[0];
304
+
305
+ if (!modelName) {
306
+ console.error('Error: Model name required');
307
+ console.log('Usage: construct ollama show <model>');
308
+ process.exit(1);
309
+ }
310
+
311
+ try {
312
+ const details = showModel(modelName);
313
+ console.log(`Model: ${modelName}`);
314
+ console.log('─'.repeat(50));
315
+
316
+ if (details.details) {
317
+ console.log(` Format: ${details.details.format || 'unknown'}`);
318
+ console.log(` Family: ${details.details.family || 'unknown'}`);
319
+ console.log(` Parameters: ${details.details.parameter_size || 'unknown'}`);
320
+ console.log(` Quantization: ${details.details.quantization_level || 'unknown'}`);
321
+ }
322
+
323
+ if (details.model_info) {
324
+ const keys = Object.keys(details.model_info);
325
+ if (keys.length > 0) {
326
+ console.log('\n Info:');
327
+ keys.slice(0, 5).forEach(key => {
328
+ console.log(` ${key}: ${details.model_info[key]}`);
329
+ });
330
+ }
331
+ }
332
+ } catch (e) {
333
+ console.error(e.message);
334
+ process.exit(1);
335
+ }
336
+ }
337
+
338
+ async function cmdOllamaTest(args) {
339
+ const modelName = args[0];
340
+
341
+ if (!modelName) {
342
+ console.error('Error: Model name required');
343
+ console.log('Usage: construct ollama test <model>');
344
+ process.exit(1);
345
+ }
346
+
347
+ console.log(`Testing ${modelName}...`);
348
+ const result = testModel(modelName);
349
+
350
+ if (result.success) {
351
+ console.log(`✅ Response (${result.duration}):`);
352
+ console.log('─'.repeat(50));
353
+ console.log(result.response);
354
+ } else {
355
+ console.error(`❌ Test failed: ${result.error}`);
356
+ process.exit(1);
357
+ }
358
+ }
359
+
360
+ async function cmdOllamaSetup(args) {
361
+ const autoYes = args.includes('--yes') || args.includes('-y');
362
+
363
+ console.log('Ollama Setup');
364
+ console.log('─'.repeat(50));
365
+
366
+ // Check installation
367
+ const status = checkOllamaStatus();
368
+
369
+ if (!status.installed) {
370
+ console.log('❌ Ollama is not installed');
371
+ if (autoYes || process.platform === 'darwin') {
372
+ console.log('Installing via Homebrew...');
373
+ try {
374
+ execSync('brew install ollama', { stdio: 'inherit' });
375
+ console.log('✅ Ollama installed');
376
+ } catch (e) {
377
+ console.error('❌ Installation failed. Please install manually: https://ollama.com');
378
+ process.exit(1);
379
+ }
380
+ } else {
381
+ console.log('Install with: brew install ollama');
382
+ process.exit(1);
383
+ }
384
+ } else {
385
+ console.log('✅ Ollama is installed');
386
+ }
387
+
388
+ // Check if running
389
+ if (!status.running) {
390
+ console.log('⚠️ Ollama is not running');
391
+ console.log('Starting Ollama service...');
392
+ try {
393
+ execSync('brew services start ollama', { stdio: 'pipe' });
394
+ console.log('✅ Ollama service started');
395
+ // Wait for it to be ready
396
+ console.log('Waiting for Ollama to be ready...');
397
+ execSync('sleep 3');
398
+ } catch (e) {
399
+ console.log('You can start it manually with: ollama serve');
400
+ }
401
+ } else {
402
+ console.log('✅ Ollama is running');
403
+ }
404
+
405
+ // Pull default model
406
+ console.log('\nPulling default model (llama3.2)...');
407
+ const pullResult = pullModel('llama3.2', { verbose: false });
408
+ if (pullResult.success) {
409
+ console.log('✅ llama3.2 ready');
410
+ } else {
411
+ console.log('⚠️ Could not pull llama3.2, you can pull it later with: construct ollama pull llama3.2');
412
+ }
413
+
414
+ console.log('\n─'.repeat(50));
415
+ console.log('Setup complete!');
416
+ console.log('\nUsage:');
417
+ console.log(' construct ollama list # List available models');
418
+ console.log(' construct ollama test llama3.2 # Test a model');
419
+ console.log(' construct models --set=ollama/llama3.2 # Set as default');
420
+ }
421
+
422
+ function formatSize(bytes) {
423
+ if (!bytes) return 'unknown';
424
+ const gb = bytes / (1024 * 1024 * 1024);
425
+ if (gb >= 1) return `${gb.toFixed(1)} GB`;
426
+ const mb = bytes / (1024 * 1024);
427
+ return `${mb.toFixed(0)} MB`;
428
+ }
@@ -1,7 +1,10 @@
1
1
  /**
2
- * lib/opencode-config.mjs — <one-line purpose>
2
+ * lib/opencode-config.mjs — Read and write the OpenCode settings.json config file.
3
3
  *
4
- * <2–6 line summary: what it does, who calls it, key side effects.>
4
+ * Locates the active OpenCode config across standard install paths, parses it,
5
+ * and provides typed helpers to read provider settings, MCP registrations, and
6
+ * model assignments. Changes are written atomically. Backs model-router,
7
+ * mcp-manager, and the runtime plugin.
5
8
  */
6
9
  import fs from "node:fs";
7
10
  import path from "node:path";
@@ -36,9 +39,16 @@ export function readOpenCodeConfig() {
36
39
  };
37
40
  }
38
41
 
42
+ export function sanitizeOpenCodeConfig(config) {
43
+ if (!config || Array.isArray(config) || typeof config !== "object") return config;
44
+ const sanitized = { ...config };
45
+ delete sanitized.construct;
46
+ return sanitized;
47
+ }
48
+
39
49
  export function writeOpenCodeConfig(config, file = findOpenCodeConfigPath()) {
40
50
  const canonical = getCanonicalOpenCodeConfigPath();
41
51
  fs.mkdirSync(path.dirname(canonical), { recursive: true });
42
- fs.writeFileSync(canonical, `${JSON.stringify(config, null, 2)}\n`, "utf8");
52
+ fs.writeFileSync(canonical, `${JSON.stringify(sanitizeOpenCodeConfig(config), null, 2)}\n`, "utf8");
43
53
  return canonical;
44
54
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * lib/opencode-runtime-plugin.mjs — OpenCode plugin: Langfuse telemetry + model fallback.
2
+ * lib/opencode-runtime-plugin.mjs — OpenCode plugin: Telemetry + model fallback.
3
3
  *
4
4
  * Intercepts OpenCode session events (session.created, session.idle, session.error),
5
- * records each as a Langfuse trace, and applies model fallback on rate-limit errors.
5
+ * records each as a telemetry trace, and applies model fallback on rate-limit errors.
6
6
  */
7
7
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
8
8
  import { homedir } from "node:os";
@@ -11,7 +11,7 @@ import { spawnSync } from "node:child_process";
11
11
  import { randomUUID } from "node:crypto";
12
12
  import { findOpenCodeConfigPath } from "./opencode-config.mjs";
13
13
  import { classifyProviderFailure, readCurrentModels, resolveExecutionContractModelMetadata, resolveFallbackAction, selectModelTierForWorkCategory } from "./model-router.mjs";
14
- import { resolveRuntimePromptMetadata } from "./prompt-composer.mjs";
14
+ import { resolveRuntimePromptMetadata } from "./prompt-composer.js";
15
15
  import { enrichMetadataWithPrompt } from "./prompt-metadata.mjs";
16
16
  import { loadToolkitEnv } from "./toolkit-env.mjs";
17
17
  import { routeRequest } from "./orchestration-policy.mjs";
@@ -25,8 +25,9 @@ import {
25
25
  onCommandBefore,
26
26
  getIngestClient,
27
27
  } from "./opencode-telemetry.mjs";
28
- import { syncModelPricing } from "./telemetry/langfuse-model-sync.mjs";
29
- import { estimateUsageCost } from "./telemetry/langfuse-model-sync.mjs";
28
+ import { syncModelPricing, refreshPricingCatalog } from "./telemetry/model-pricing-catalog.mjs";
29
+ import { estimateUsageCost } from "./telemetry/model-pricing-catalog.mjs";
30
+ import { buildSessionPrelude } from "./intake/session-prelude.mjs";
30
31
 
31
32
  const EFFICIENCY_SESSION_IDLE_RESET_MS = 2 * 60 * 60 * 1000;
32
33
  const EFFICIENCY_REPEATED_READ_THRESHOLD = 5;
@@ -63,6 +64,32 @@ const RUNTIME_TRACE_EVENTS = new Set([
63
64
  "message.updated",
64
65
  ]);
65
66
 
67
+ // One-shot prelude emission per session id — kept in module scope so the
68
+ // prelude surfaces on session.created and not on every subsequent event
69
+ // that carries the same session id.
70
+ const preludeEmittedSessions = new Set();
71
+
72
+ export function _resetPreludeForTests() {
73
+ preludeEmittedSessions.clear();
74
+ }
75
+
76
+ export async function emitSessionPrelude(event, { client, env = process.env, cwd } = {}) {
77
+ if (event?.type !== "session.created") return false;
78
+ const sessionId = getSessionId(event) || "anonymous";
79
+ if (preludeEmittedSessions.has(sessionId)) return false;
80
+ preludeEmittedSessions.add(sessionId);
81
+ const prelude = buildSessionPrelude({ cwd: cwd || env.CX_TOOLKIT_DIR || process.cwd(), env });
82
+ if (!prelude) return false;
83
+ await client?.app?.log?.({
84
+ body: {
85
+ service: "construct",
86
+ level: "info",
87
+ message: prelude,
88
+ },
89
+ }).catch(() => {});
90
+ return true;
91
+ }
92
+
66
93
  const COOLDOWN_MS = 10 * 60 * 1000;
67
94
 
68
95
  function getStatePath(env = process.env) {
@@ -179,12 +206,25 @@ function sanitizeUsage(event) {
179
206
  legacyUsage?.cacheReadInputTokens,
180
207
  legacyUsage?.prompt_tokens_details?.cached_tokens,
181
208
  );
209
+ const cacheCreation5mInputTokens = positiveNumber(
210
+ infoTokens?.cache?.write_5m,
211
+ infoTokens?.cache?.create_5m,
212
+ legacyUsage?.cache_creation?.ephemeral_5m_input_tokens,
213
+ legacyUsage?.cache_creation_5m_input_tokens,
214
+ legacyUsage?.cacheCreation5mInputTokens,
215
+ );
216
+ const cacheCreation1hInputTokens = positiveNumber(
217
+ infoTokens?.cache?.write_1h,
218
+ infoTokens?.cache?.create_1h,
219
+ legacyUsage?.cache_creation?.ephemeral_1h_input_tokens,
220
+ legacyUsage?.cache_creation_1h_input_tokens,
221
+ legacyUsage?.cacheCreation1hInputTokens,
222
+ );
182
223
  const cacheCreationInputTokens = positiveNumber(
183
224
  infoTokens?.cache?.write,
184
225
  legacyUsage?.cache_creation_input_tokens,
185
226
  legacyUsage?.cacheCreationInputTokens,
186
- (legacyUsage?.cache_creation?.ephemeral_5m_input_tokens ?? 0) +
187
- (legacyUsage?.cache_creation?.ephemeral_1h_input_tokens ?? 0),
227
+ cacheCreation5mInputTokens + cacheCreation1hInputTokens,
188
228
  );
189
229
  const totalTokens = positiveNumber(
190
230
  legacyUsage?.total_tokens,
@@ -201,6 +241,8 @@ function sanitizeUsage(event) {
201
241
  totalTokens,
202
242
  ...(cacheReadInputTokens ? { cacheReadInputTokens } : {}),
203
243
  ...(cacheCreationInputTokens ? { cacheCreationInputTokens } : {}),
244
+ ...(cacheCreation5mInputTokens ? { cacheCreation5mInputTokens } : {}),
245
+ ...(cacheCreation1hInputTokens ? { cacheCreation1hInputTokens } : {}),
204
246
  ...(reasoningTokens ? { reasoningTokens } : {}),
205
247
  ...(cost ? { costUsd: cost, costSource: "event" } : {}),
206
248
  };
@@ -367,7 +409,7 @@ function buildHostConstraints() {
367
409
  return {
368
410
  runtime: "opencode",
369
411
  providerAgnostic: true,
370
- telemetryBackend: "langfuse",
412
+ telemetryBackend: "telemetry",
371
413
  };
372
414
  }
373
415
 
@@ -426,12 +468,12 @@ export function buildRuntimeTracePayload(event, { env = process.env } = {}) {
426
468
  eventType: event.type,
427
469
  agent: agent || undefined,
428
470
  status: stripPlaceholder(event?.status || event?.session?.status || event?.data?.status),
471
+ ...runtimePromptMetadata,
429
472
  ...usage,
430
473
  ...(pricing.costUsd ? { costUsd: pricing.costUsd, costSource: pricing.costSource, costModel: pricing.modelName } : { costUsd: 0, costSource: pricing.costSource, costModel: pricing.modelName }),
431
474
  ...(modelInfo.modelName ? { modelName: modelInfo.modelName } : {}),
432
475
  ...(modelInfo.provider ? { provider: modelInfo.provider } : {}),
433
476
  ...errorMeta,
434
- ...runtimePromptMetadata,
435
477
  }, { rootDir });
436
478
 
437
479
  // Extract assistant response text for message.updated events so the trace output is populated.
@@ -632,23 +674,6 @@ export function trackReadEfficiencyFromMessage(event, { env = process.env, cwd =
632
674
  return { warnings };
633
675
  }
634
676
 
635
- function langfuseAvailable(env = process.env) {
636
- return Boolean(env.LANGFUSE_PUBLIC_KEY && env.LANGFUSE_SECRET_KEY);
637
- }
638
-
639
- function langfuseHeaders(env = process.env) {
640
- const key = env.LANGFUSE_PUBLIC_KEY;
641
- const secret = env.LANGFUSE_SECRET_KEY;
642
- return {
643
- Authorization: `Basic ${Buffer.from(`${key}:${secret}`).toString('base64')}`,
644
- 'Content-Type': 'application/json',
645
- };
646
- }
647
-
648
- function langfuseBaseUrl(env = process.env) {
649
- return (env.LANGFUSE_BASEURL ?? 'https://cloud.langfuse.com').replace(/\/$/, '');
650
- }
651
-
652
677
  async function emitRuntimeTrace(event, { client, env = process.env }) {
653
678
  const payload = buildRuntimeTracePayload(event, { env });
654
679
  if (!payload) return;
@@ -683,7 +708,7 @@ async function emitRuntimeTrace(event, { client, env = process.env }) {
683
708
  return text || undefined;
684
709
  })();
685
710
  const toolCalls = extractReadToolCalls(info);
686
- const langfuseUsage = usage.inputTokens ? {
711
+ const telemetryUsage = usage.inputTokens ? {
687
712
  input: usage.inputTokens,
688
713
  output: usage.outputTokens,
689
714
  total: usage.totalTokens,
@@ -697,7 +722,7 @@ async function emitRuntimeTrace(event, { client, env = process.env }) {
697
722
  startTime: info?.time?.created ? new Date(info.time.created).toISOString() : payload.timestamp,
698
723
  endTime: info?.time?.completed ? new Date(info.time.completed).toISOString() : new Date().toISOString(),
699
724
  model,
700
- usage: langfuseUsage,
725
+ usage: telemetryUsage,
701
726
  output: {
702
727
  kind: "assistant_generation",
703
728
  agent: payload.metadata?.agent || undefined,
@@ -709,12 +734,12 @@ async function emitRuntimeTrace(event, { client, env = process.env }) {
709
734
  costSource: pricing.costSource,
710
735
  costModel: pricing.modelName,
711
736
  toolCalls: toolCalls.length ? toolCalls.map((call) => ({ filePath: call.filePath, limit: call.limit, callId: call.callId })) : undefined,
712
- usage: langfuseUsage ? {
713
- input: langfuseUsage.input,
714
- output: langfuseUsage.output,
715
- total: langfuseUsage.total,
716
- unit: langfuseUsage.unit,
717
- ...(langfuseUsage.totalCost ? { totalCost: langfuseUsage.totalCost } : {}),
737
+ usage: telemetryUsage ? {
738
+ input: telemetryUsage.input,
739
+ output: telemetryUsage.output,
740
+ total: telemetryUsage.total,
741
+ unit: telemetryUsage.unit,
742
+ ...(telemetryUsage.totalCost ? { totalCost: telemetryUsage.totalCost } : {}),
718
743
  } : undefined,
719
744
  },
720
745
  metadata: {
@@ -725,6 +750,9 @@ async function emitRuntimeTrace(event, { client, env = process.env }) {
725
750
  costModel: pricing.modelName,
726
751
  ...(usage.reasoningTokens ? { reasoningTokens: usage.reasoningTokens } : {}),
727
752
  ...(usage.cacheReadInputTokens ? { cacheReadInputTokens: usage.cacheReadInputTokens } : {}),
753
+ ...(usage.cacheCreationInputTokens ? { cacheCreationInputTokens: usage.cacheCreationInputTokens } : {}),
754
+ ...(usage.cacheCreation5mInputTokens ? { cacheCreation5mInputTokens: usage.cacheCreation5mInputTokens } : {}),
755
+ ...(usage.cacheCreation1hInputTokens ? { cacheCreation1hInputTokens: usage.cacheCreation1hInputTokens } : {}),
728
756
  },
729
757
  });
730
758
  await ingest.flush();
@@ -856,11 +884,14 @@ export function createConstructOpenCodePlugin({
856
884
  configPath = findOpenCodeConfigPath(),
857
885
  } = {}) {
858
886
  loadToolkitEnv(toolkitDir, env);
859
- // Fire-and-forget: sync model pricing into Langfuse on plugin load.
887
+ // Refresh local pricing catalog from LiteLLM (24h cached, no auth). Runs before
888
+ // Sync pricing so estimateUsageCost uses live rates from the first assistant turn.
889
+ void refreshPricingCatalog().catch(() => {});
890
+ // Fire-and-forget: sync model pricing into telemetry backend on plugin load.
860
891
  void syncModelPricing({
861
- baseUrl: env.LANGFUSE_BASEURL,
862
- publicKey: env.LANGFUSE_PUBLIC_KEY,
863
- secretKey: env.LANGFUSE_SECRET_KEY,
892
+ baseUrl: env.CONSTRUCT_TELEMETRY_URL,
893
+ publicKey: env.CONSTRUCT_TELEMETRY_PUBLIC_KEY,
894
+ secretKey: env.CONSTRUCT_TELEMETRY_SECRET_KEY,
864
895
  }).catch(() => {});
865
896
  const safe = async (fn, label, client) => {
866
897
  try {
@@ -879,6 +910,7 @@ export function createConstructOpenCodePlugin({
879
910
  event: async ({ event }) => {
880
911
  await safe(() => onBusEvent(event, { env }), "event", client);
881
912
  await safe(() => emitRuntimeTrace(event, { client, env }), "legacy-trace", client);
913
+ await safe(() => emitSessionPrelude(event, { client, env, cwd: toolkitDir }), "session-prelude", client);
882
914
  await maybeApplyModelFallback(event, { client, env, toolkitDir, configPath });
883
915
  try {
884
916
  const { warnings } = trackReadEfficiencyFromMessage(event, { env, cwd: toolkitDir || process.cwd() });