@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,120 @@
1
+ /**
2
+ * lib/bootstrap/resources.mjs — registry of optional resources Construct can spin up.
3
+ *
4
+ * A resource is something Construct can use but does not require: Postgres
5
+ * with pgvector, the local ONNX embedding model, Docker, git, etc. Each
6
+ * resource declares:
7
+ *
8
+ * id stable identifier
9
+ * displayName human-readable label
10
+ * required true → missing means CLI commands fail; false → fall back
11
+ * detect() async probe → { present, version?, location?, healthy?, detail? }
12
+ * install() async installer → { method, success, log } (optional)
13
+ * fallback() string explaining what runs in degraded mode
14
+ * consentKey env var name in config.env for cached operator consent
15
+ * downloadSize approximate install size in bytes (informational)
16
+ *
17
+ * The registry is consulted by:
18
+ * - `construct init` walks every resource, asks consent, installs
19
+ * - `construct doctor --bootstrap` re-probes every resource verbosely
20
+ * - lazy-install paths in hooks (consult the cached consent silently)
21
+ *
22
+ * Resources are "additive" — anyone can register a new resource via
23
+ * `registerResource()` so plugins (or future built-ins) can extend the set
24
+ * without forking this file.
25
+ */
26
+
27
+ const REGISTRY = new Map();
28
+
29
+ /**
30
+ * Register a resource. Throws if the id is already registered.
31
+ */
32
+ export function registerResource(resource) {
33
+ if (!resource || typeof resource !== 'object') {
34
+ throw new Error('registerResource: resource must be an object');
35
+ }
36
+ for (const field of ['id', 'displayName', 'detect', 'consentKey']) {
37
+ if (!resource[field]) throw new Error(`registerResource: ${field} is required`);
38
+ }
39
+ if (REGISTRY.has(resource.id)) {
40
+ throw new Error(`registerResource: ${resource.id} already registered`);
41
+ }
42
+ REGISTRY.set(resource.id, resource);
43
+ }
44
+
45
+ export function listResources() {
46
+ return [...REGISTRY.values()];
47
+ }
48
+
49
+ export function getResource(id) {
50
+ return REGISTRY.get(id);
51
+ }
52
+
53
+ export function clearResourceRegistry() {
54
+ REGISTRY.clear();
55
+ }
56
+
57
+ /**
58
+ * Probe one resource. Catches errors so a broken probe never cascades.
59
+ */
60
+ export async function probeResource(resource) {
61
+ if (!resource || typeof resource.detect !== 'function') {
62
+ return { id: resource?.id, present: false, error: 'resource has no detect()' };
63
+ }
64
+ try {
65
+ const result = await resource.detect();
66
+ return {
67
+ id: resource.id,
68
+ displayName: resource.displayName,
69
+ required: resource.required ?? false,
70
+ present: !!result?.present,
71
+ version: result?.version || null,
72
+ location: result?.location || null,
73
+ healthy: result?.healthy ?? !!result?.present,
74
+ detail: result?.detail || null,
75
+ consentKey: resource.consentKey,
76
+ fallback: resource.fallback?.() || null,
77
+ downloadSize: resource.downloadSize || null,
78
+ installable: typeof resource.install === 'function',
79
+ };
80
+ } catch (err) {
81
+ return {
82
+ id: resource.id,
83
+ displayName: resource.displayName,
84
+ required: resource.required ?? false,
85
+ present: false,
86
+ error: err?.message || String(err),
87
+ };
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Probe every registered resource. Returns a sorted array (required first,
93
+ * then by id) so callers can render a deterministic report.
94
+ */
95
+ export async function probeAll() {
96
+ const probes = await Promise.all([...REGISTRY.values()].map(probeResource));
97
+ return probes.sort((a, b) => {
98
+ if (a.required !== b.required) return a.required ? -1 : 1;
99
+ return String(a.id).localeCompare(String(b.id));
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Format a probe result line for human display.
105
+ */
106
+ export function formatProbe(probe) {
107
+ const status = probe.present
108
+ ? (probe.healthy ? '✓' : '⚠')
109
+ : (probe.required ? '✗' : '○');
110
+ const v = probe.version ? ` (${probe.version})` : '';
111
+ const loc = probe.location ? ` ${probe.location}` : '';
112
+ const detail = probe.detail ? ` — ${probe.detail}` : '';
113
+ const fallback = !probe.present && !probe.required && probe.fallback
114
+ ? `\n fallback: ${probe.fallback}`
115
+ : '';
116
+ const install = !probe.present && probe.installable
117
+ ? `\n installable via construct init`
118
+ : '';
119
+ return ` ${status} ${probe.displayName}${v}${loc}${detail}${fallback}${install}`;
120
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * lib/bootstrap.mjs — imports the seed observation corpus into the local memory store.
3
+ *
4
+ * Reads markdown files from examples/seed-observations/, parses each ## heading group
5
+ * into individual observations, and writes them via addObservation. Safe to re-run:
6
+ * observations with matching content hashes are skipped. Reports counts on completion.
7
+ */
8
+
9
+ import crypto from 'node:crypto';
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { addObservation, listObservations, getObservation } from './observation-store.mjs';
13
+
14
+ const SEED_DIR = path.resolve(import.meta.dirname, '..', 'examples', 'seed-observations');
15
+
16
+ const FILE_CATEGORY_MAP = {
17
+ 'patterns.md': 'pattern',
18
+ 'anti-patterns.md': 'anti-pattern',
19
+ 'decisions.md': 'decision',
20
+ };
21
+
22
+ function contentHash(str) {
23
+ return crypto.createHash('sha256').update(str).digest('hex').slice(0, 16);
24
+ }
25
+
26
+ function parseObservations(content, category) {
27
+ const observations = [];
28
+ const lines = content.split('\n');
29
+ let currentH2 = null;
30
+ let buffer = [];
31
+
32
+ function flush() {
33
+ if (!buffer.length) return;
34
+ const raw = buffer.join('\n').trim();
35
+ if (!raw) { buffer = []; return; }
36
+ const firstLine = raw.split('\n')[0];
37
+ const boldMatch = firstLine.match(/^\*\*(.+?)\*\*/);
38
+ const summary = boldMatch ? boldMatch[1] : firstLine.replace(/^#+\s*/, '').slice(0, 120);
39
+ observations.push({
40
+ summary,
41
+ content: raw,
42
+ category,
43
+ tags: currentH2 ? [currentH2.toLowerCase().replace(/\s+/g, '-')] : [],
44
+ role: 'construct',
45
+ confidence: 0.85,
46
+ source: 'seed-corpus',
47
+ });
48
+ buffer = [];
49
+ }
50
+
51
+ for (const line of lines) {
52
+ if (line.startsWith('## ')) {
53
+ flush();
54
+ currentH2 = line.replace(/^## /, '').trim();
55
+ continue;
56
+ }
57
+ if (line.startsWith('**') && buffer.length > 0) {
58
+ flush();
59
+ }
60
+ buffer.push(line);
61
+ }
62
+ flush();
63
+
64
+ return observations;
65
+ }
66
+
67
+ export function runBootstrap(rootDir = process.cwd(), { verbose = false } = {}) {
68
+ if (!fs.existsSync(SEED_DIR)) {
69
+ return { imported: 0, skipped: 0, error: `seed dir not found: ${SEED_DIR}` };
70
+ }
71
+
72
+ // Load existing seed-corpus entries and hash their content for idempotency check.
73
+ // Index lacks content field; load full records for hash comparison.
74
+ const existing = listObservations(rootDir, { limit: 10000 });
75
+ const existingHashes = new Set();
76
+ for (const entry of existing.filter((e) => e.source === 'seed-corpus' || e.role === 'construct')) {
77
+ const full = getObservation(rootDir, entry.id);
78
+ if (full?.content) existingHashes.add(contentHash(full.content));
79
+ }
80
+
81
+ let imported = 0;
82
+ let skipped = 0;
83
+
84
+ for (const [filename, category] of Object.entries(FILE_CATEGORY_MAP)) {
85
+ const filePath = path.join(SEED_DIR, filename);
86
+ if (!fs.existsSync(filePath)) continue;
87
+ const content = fs.readFileSync(filePath, 'utf8');
88
+ const observations = parseObservations(content, category);
89
+
90
+ for (const obs of observations) {
91
+ const hash = contentHash(obs.content);
92
+ if (existingHashes.has(hash)) {
93
+ skipped++;
94
+ if (verbose) process.stdout.write(` skip ${obs.summary.slice(0, 60)}\n`);
95
+ continue;
96
+ }
97
+ addObservation(rootDir, obs);
98
+ existingHashes.add(hash);
99
+ imported++;
100
+ if (verbose) process.stdout.write(` import ${obs.summary.slice(0, 60)}\n`);
101
+ }
102
+ }
103
+
104
+ return { imported, skipped };
105
+ }
@@ -0,0 +1,213 @@
1
+ /**
2
+ * lib/cache-governor.js — Adaptive cache strategy feedback loop.
3
+ *
4
+ * Reads cost log, computes cache efficiency per provider.
5
+ * Adjusts strategy based on observed vs expected cache usage.
6
+ * Persists state in ~/.cx/cache-strategy.json.
7
+ *
8
+ * Runs as a PostToolUse hook (lightweight, sub-5ms).
9
+ * Adjustments are persisted per-provider.
10
+ */
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { homedir } from 'node:os';
14
+ import { normalizeCostEntry } from './cost.mjs';
15
+
16
+ const STRATEGY_PATH = join(homedir(), '.cx', 'cache-strategy.json');
17
+ const REVIEW_INTERVAL_MS = 10 * 60 * 1000; // 10 minutes between adjustments
18
+
19
+ function readStrategy() {
20
+ try {
21
+ if (!existsSync(STRATEGY_PATH)) return {};
22
+ return JSON.parse(readFileSync(STRATEGY_PATH, 'utf8'));
23
+ } catch {
24
+ return {};
25
+ }
26
+ }
27
+
28
+ function writeStrategy(data) {
29
+ try {
30
+ mkdirSync(join(homedir(), '.cx'), { recursive: true });
31
+ writeFileSync(STRATEGY_PATH, JSON.stringify(data, null, 2));
32
+ } catch { /* best effort */ }
33
+ }
34
+
35
+ /**
36
+ * Compute cache efficiency from a cost entry.
37
+ * Compares actual cache reads vs expected cacheable tokens.
38
+ *
39
+ * @param {object} entry - normalized cost entry
40
+ * @param {number} expectedCacheableTokens
41
+ * @returns {{ efficiency: number, waste: number, hitRate: number }}
42
+ */
43
+ export function computeCacheEfficiency(entry, expectedCacheableTokens = 0) {
44
+ if (!entry) return { efficiency: 0, waste: 0, hitRate: 0 };
45
+
46
+ const actualCacheRead = entry.cacheReadInputTokens || 0;
47
+ const actualCacheWrite = entry.cacheCreationInputTokens || 0;
48
+ const processedInput = entry.processedInputTokens || entry.inputTokens || 0;
49
+
50
+ const hitRate = processedInput > 0 ? actualCacheRead / processedInput : 0;
51
+ const writeRatio = expectedCacheableTokens > 0
52
+ ? actualCacheWrite / expectedCacheableTokens
53
+ : 0;
54
+
55
+ // Efficiency: how close are we to expected cache usage?
56
+ const efficiency = expectedCacheableTokens > 0
57
+ ? Math.min(1, actualCacheRead / expectedCacheableTokens)
58
+ : 0;
59
+
60
+ // Waste: expected writes minus actual reads (over-caching)
61
+ const waste = Math.max(0, actualCacheWrite - actualCacheRead);
62
+
63
+ return {
64
+ efficiency: Math.round(efficiency * 100) / 100,
65
+ waste,
66
+ hitRate: Math.round(hitRate * 100) / 100,
67
+ actualCacheRead,
68
+ actualCacheWrite,
69
+ expectedCacheableTokens,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Analyze recent cost entries and suggest cache strategy adjustments.
75
+ *
76
+ * @param {Array} entries - normalized cost entries
77
+ * @param {string} provider
78
+ * @returns {{ adjustBreakpoints: boolean, suggestion: string, reason: string }}
79
+ */
80
+ export function analyzeCachePerformance(entries, provider) {
81
+ if (!Array.isArray(entries) || entries.length === 0) {
82
+ return { adjustBreakpoints: false, suggestion: 'no data', reason: 'No cost entries' };
83
+ }
84
+
85
+ // Get latest entry with cache data
86
+ const withCache = entries
87
+ .filter(e => (e.cacheReadInputTokens || 0) > 0 || (e.cacheCreationInputTokens || 0) > 0)
88
+ .sort((a, b) => new Date(b.ts || 0) - new Date(a.ts || 0));
89
+
90
+ if (withCache.length === 0) {
91
+ return { adjustBreakpoints: false, suggestion: 'no cache activity', reason: 'No cache reads/writes detected' };
92
+ }
93
+
94
+ const latest = withCache[0];
95
+ const efficiency = computeCacheEfficiency(latest, latest.expectedCacheableTokens || 0);
96
+
97
+ // Strategy rules
98
+ if (efficiency.efficiency < 0.2 && (latest.expectedCacheableTokens || 0) > 0) {
99
+ return {
100
+ adjustBreakpoints: true,
101
+ suggestion: 'shift later',
102
+ reason: `Very low cache efficiency (${(efficiency.efficiency * 100).toFixed(1)}% — cache placement may be wrong`,
103
+ };
104
+ }
105
+
106
+ if (efficiency.hitRate < 0.2 && (latest.expectedCacheableTokens || 0) > 0) {
107
+ return {
108
+ adjustBreakpoints: true,
109
+ suggestion: 'add earlier breakpoints',
110
+ reason: `Low cache hit rate (${(efficiency.hitRate * 100).toFixed(1)}% — try caching more static content`,
111
+ };
112
+ }
113
+
114
+ if (efficiency.efficiency > 0.8) {
115
+ return {
116
+ adjustBreakpoints: false,
117
+ suggestion: 'optimal',
118
+ reason: `Good cache efficiency (${(efficiency.efficiency * 100).toFixed(1)}% — no adjustment needed`,
119
+ };
120
+ }
121
+
122
+ if (efficiency.waste > 5000) {
123
+ return {
124
+ adjustBreakpoints: true,
125
+ suggestion: 'reduce breakpoints',
126
+ reason: `High cache waste (${efficiency.waste} tokens over-cached)`,
127
+ };
128
+ }
129
+
130
+ return {
131
+ adjustBreakpoints: false,
132
+ suggestion: 'stable',
133
+ reason: `Cache efficiency ${(efficiency.efficiency * 100).toFixed(1)}% — within normal range`,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * PostToolUse hook entry point.
139
+ * Reads cost log and adjusts strategy if needed.
140
+ *
141
+ * @param {object} opts - { costLogPath, provider, dryRun }
142
+ * @returns {{ adjusted: boolean, strategy: object }}
143
+ */
144
+ export function onPostToolUse({ costLogPath, provider, dryRun = false } = {}) {
145
+ const strategy = readStrategy();
146
+
147
+ // Check cooldown
148
+ const lastReview = strategy[`${provider}:lastReview`] || 0;
149
+ if (Date.now() - lastReview < REVIEW_INTERVAL_MS) {
150
+ return { adjusted: false, reason: 'Cooldown active', strategy: strategy[provider] };
151
+ }
152
+
153
+ // Read cost log
154
+ const entries = [];
155
+ try {
156
+ const logPath = costLogPath || join(homedir(), '.cx', 'session-cost.jsonl');
157
+ if (!existsSync(logPath)) return { adjusted: false, reason: 'No cost log' };
158
+
159
+ const lines = readFileSync(logPath, 'utf8')
160
+ .split('\n')
161
+ .filter(Boolean);
162
+
163
+ for (const line of lines) {
164
+ try {
165
+ entries.push(normalizeCostEntry(JSON.parse(line)));
166
+ } catch { /* skip */ }
167
+ }
168
+ } catch { /* skip */ }
169
+
170
+ const analysis = analyzeCachePerformance(entries, provider);
171
+
172
+ if (analysis.adjustBreakpoints && !dryRun) {
173
+ // Persist adjustment
174
+ strategy[provider] = {
175
+ ...strategy[provider],
176
+ lastReview: Date.now(),
177
+ suggestion: analysis.suggestion,
178
+ reason: analysis.reason,
179
+ updatedAt: new Date().toISOString(),
180
+ };
181
+ writeStrategy(strategy);
182
+
183
+ return { adjusted: true, suggestion: analysis.suggestion, reason: analysis.reason, strategy: strategy[provider] };
184
+ }
185
+
186
+ return { adjusted: false, reason: analysis.reason, strategy: strategy[provider] };
187
+ }
188
+
189
+ /**
190
+ * Get current cache strategy for a provider.
191
+ *
192
+ * @param {string} provider
193
+ * @returns {object|null}
194
+ */
195
+ export function getCacheStrategy(provider) {
196
+ const strategy = readStrategy();
197
+ return strategy[provider] || null;
198
+ }
199
+
200
+ /**
201
+ * Reset cache strategy for a provider (or all).
202
+ *
203
+ * @param {string} [provider] - if omitted, resets all
204
+ */
205
+ export function resetCacheStrategy(provider) {
206
+ if (provider) {
207
+ const strategy = readStrategy();
208
+ delete strategy[provider];
209
+ writeStrategy(strategy);
210
+ } else {
211
+ writeStrategy({});
212
+ }
213
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * lib/cache-strategy-anthropic.js — Anthropic cache annotation strategy.
3
+ *
4
+ * Anthropic supports cache_control annotations on system messages.
5
+ * Header required: anthropic-version: 2024-10-22
6
+ * Cache TTL: 5m and 1h (ephemeral variants)
7
+ */
8
+ import { estimateTokens } from './token-engine.js';
9
+
10
+ /**
11
+ * Annotate prompt structure for Anthropic models.
12
+ *
13
+ * @param {object} promptStructure - { system, messages, staticEndIndex }
14
+ * @param {object} caps - Provider capabilities
15
+ * @returns {Promise<object>} - { messages, annotations, expectedCacheWriteTokens }
16
+ */
17
+ export async function annotate(promptStructure, caps) {
18
+ const { system, messages, staticEndIndex } = promptStructure || {};
19
+
20
+ // Annotate system message with cache_control
21
+ const annotatedMessages = (messages || []).map((msg, idx) => {
22
+ if (idx === 0 && msg.role === 'system') {
23
+ return {
24
+ ...msg,
25
+ cache_control: { type: 'ephemeral' }, // 5m TTL by default
26
+ };
27
+ }
28
+ return msg;
29
+ });
30
+
31
+ // Calculate expected cacheable tokens
32
+ const expectedCacheWriteTokens = staticEndIndex >= 0
33
+ ? await estimateTokens(system || '', { modelId: 'anthropic/claude-opus-4-6' })
34
+ : 0;
35
+
36
+ return {
37
+ messages: annotatedMessages,
38
+ annotations: [
39
+ {
40
+ type: 'cache_control',
41
+ placement: 'system-message',
42
+ ttl: caps?.cacheTTL?.['5m'] || 300_000,
43
+ note: 'Anthropic cache_control: ephemeral (5m TTL)',
44
+ },
45
+ {
46
+ type: 'header',
47
+ name: 'anthropic-version',
48
+ value: caps?.annotationHeaders?.['anthropic-version'] || '2024-10-22',
49
+ },
50
+ ],
51
+ expectedCacheWriteTokens,
52
+ expectedCacheReadTokens: 0, // unknown until response
53
+ };
54
+ }
55
+
56
+ export async function capabilities() {
57
+ return {
58
+ supportsAnnotations: true,
59
+ annotationType: 'cache_control',
60
+ requiresHeader: 'anthropic-version',
61
+ };
62
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * lib/cache-strategy-google.js — Google Gemini cache strategy.
3
+ *
4
+ * Google uses cachedContent API (resource-based caching, not annotations).
5
+ * Cache TTL: 1h via cachedContent resource.
6
+ * Requires separate API call to create/update cached content.
7
+ */
8
+ import { estimateTokens } from './token-engine.js';
9
+
10
+ const GEMINI_CACHE_ENDPOINT = 'https://generativelanguage.googleapis.com/v1';
11
+
12
+ /**
13
+ * Annotate prompt structure for Google Gemini models.
14
+ * Note: Gemini uses cachedContent API, not message annotations.
15
+ *
16
+ * @param {object} promptStructure - { system, messages }
17
+ * @param {object} caps - Provider capabilities
18
+ * @param {object} opts - { apiKey, modelId }
19
+ * @returns {Promise<object>}
20
+ */
21
+ export async function annotate(promptStructure, caps, { apiKey, modelId } = {}) {
22
+ const { system, messages } = promptStructure || {};
23
+
24
+ let cachedContentName = null;
25
+
26
+ if (apiKey && system) {
27
+ try {
28
+ cachedContentName = await createCachedContent(system, caps, apiKey, modelId);
29
+ } catch (err) {
30
+ }
31
+ }
32
+
33
+ return {
34
+ messages: messages || [],
35
+ annotations: cachedContentName
36
+ ? [
37
+ {
38
+ type: 'cached_content',
39
+ name: cachedContentName,
40
+ ttl: caps?.cacheTTL?.['1h'] || 1_200_000,
41
+ note: 'Gemini cachedContent resource',
42
+ },
43
+ ]
44
+ : [],
45
+ expectedCacheWriteTokens: cachedContentName
46
+ ? await estimateTokens(system || '', { modelId: 'google/gemini-1.5-pro' })
47
+ : 0,
48
+ expectedCacheReadTokens: 0,
49
+ };
50
+ }
51
+
52
+ async function createCachedContent(systemText, caps, apiKey, modelId) {
53
+ // POST to https://generativelanguage.googleapis.com/v1/cachedContents
54
+ // Body: { model: 'gemini-1.5-pro', contents: [{ parts: [{ text: systemText }] }] }
55
+ // Returns: { name: 'cachedContents/abc123' }
56
+
57
+ const model = extractModelName(modelId);
58
+ const url = `${GEMINI_CACHE_ENDPOINT}/cachedContents?key=${apiKey}`;
59
+
60
+ // This is async but we don't want to block dispatch
61
+ // In production, this should be pre-cached
62
+ // For now, return null (defer to Phase C follow-up with live probing)
63
+ return null;
64
+ }
65
+
66
+ function extractModelName(modelId) {
67
+ const id = String(modelId || '').toLowerCase();
68
+ const match = id.match(/google\/(gemini-[^\/]+)/);
69
+ return match ? match[1] : 'gemini-1.5-pro';
70
+ }
71
+
72
+ export async function capabilities() {
73
+ return {
74
+ supportsAnnotations: false,
75
+ annotationType: 'cached_content',
76
+ requiresApiCall: true,
77
+ cacheEndpoint: GEMINI_CACHE_ENDPOINT,
78
+ };
79
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * lib/cache-strategy-none.js — Fallback for providers with no cache support.
3
+ *
4
+ * Used by DeepSeek and other providers without prompt caching.
5
+ * Returns messages as-is with no annotations.
6
+ */
7
+ /**
8
+ * Annotate prompt structure for providers with no cache support.
9
+ *
10
+ * @param {object} promptStructure - { messages }
11
+ * @returns {Promise<object>}
12
+ */
13
+ export async function annotate(promptStructure) {
14
+ const { messages } = promptStructure || {};
15
+
16
+ return {
17
+ messages: messages || [],
18
+ annotations: [],
19
+ expectedCacheWriteTokens: 0,
20
+ expectedCacheReadTokens: 0,
21
+ };
22
+ }
23
+
24
+ export async function capabilities() {
25
+ return {
26
+ supportsAnnotations: false,
27
+ annotationType: 'none',
28
+ notes: 'Provider does not support prompt caching',
29
+ };
30
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * lib/cache-strategy-openai.js — OpenAI cache strategy.
3
+ *
4
+ * OpenAI uses automatic prefix caching (no annotations needed).
5
+ * Cache is triggered by stable prefixes in the prompt.
6
+ * The structured composition (static system + dynamic suffix) naturally triggers it.
7
+ */
8
+ import { estimateTokens } from './token-engine.js';
9
+
10
+ /**
11
+ * Annotate prompt structure for OpenAI models.
12
+ * Note: OpenAI uses automatic prefix caching — no annotations needed.
13
+ *
14
+ * @param {object} promptStructure - { system, messages }
15
+ * @param {object} caps - Provider capabilities
16
+ * @returns {Promise<object>}
17
+ */
18
+ export async function annotate(promptStructure, caps) {
19
+ const { system, messages } = promptStructure || {};
20
+
21
+ // OpenAI: automatic prefix caching — no annotations
22
+ // Just ensure stable system prefix is at the start
23
+ const stablePrefix = system || '';
24
+ const expectedCacheableTokens = stablePrefix
25
+ ? await estimateTokens(stablePrefix, { modelId: 'openai/gpt-4o' })
26
+ : 0;
27
+
28
+ return {
29
+ messages: messages || [],
30
+ annotations: [
31
+ {
32
+ type: 'automatic_prefix',
33
+ note: 'OpenAI caches repeated prefixes automatically',
34
+ expectedPrefixTokens: expectedCacheableTokens,
35
+ },
36
+ ],
37
+ expectedCacheWriteTokens: expectedCacheableTokens,
38
+ expectedCacheReadTokens: 0, // OpenAI doesn't expose cache hit/miss
39
+ };
40
+ }
41
+
42
+ export async function capabilities() {
43
+ return {
44
+ supportsAnnotations: false,
45
+ annotationType: 'automatic_prefix',
46
+ visibleCacheMetrics: false,
47
+ };
48
+ }