avorelo 0.1.0

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 (258) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/bin/avorelo +9 -0
  4. package/package.json +135 -0
  5. package/scripts/README.md +40 -0
  6. package/scripts/cco-dashboard.js +252 -0
  7. package/scripts/cco-status.js +430 -0
  8. package/scripts/lib/activation/account-state.js +37 -0
  9. package/scripts/lib/activation/activation-runner.js +546 -0
  10. package/scripts/lib/activation/activation-self-healing.js +480 -0
  11. package/scripts/lib/activation/activation-state.js +83 -0
  12. package/scripts/lib/activation/activation-summary.js +191 -0
  13. package/scripts/lib/activation/adapters/claude-code.js +77 -0
  14. package/scripts/lib/activation/adapters/codex-cli.js +52 -0
  15. package/scripts/lib/activation/adapters/cursor.js +37 -0
  16. package/scripts/lib/activation/adapters/github-agent.js +39 -0
  17. package/scripts/lib/activation/adapters/terminal.js +42 -0
  18. package/scripts/lib/activation/adapters/vscode.js +39 -0
  19. package/scripts/lib/activation/adapters/windsurf.js +37 -0
  20. package/scripts/lib/activation/ai-surface-detector.js +151 -0
  21. package/scripts/lib/activation/connect-account.js +145 -0
  22. package/scripts/lib/activation/detect-environment.js +75 -0
  23. package/scripts/lib/activation/detect-hosts.js +62 -0
  24. package/scripts/lib/activation/format-activation-output.js +109 -0
  25. package/scripts/lib/activation/next-action.js +43 -0
  26. package/scripts/lib/activation/repair-engine.js +219 -0
  27. package/scripts/lib/activation-distribution-readiness.js +507 -0
  28. package/scripts/lib/adapter-conformance.js +176 -0
  29. package/scripts/lib/adapter-readiness.js +417 -0
  30. package/scripts/lib/adapter-safety-boundaries.js +335 -0
  31. package/scripts/lib/adapter-technical-readiness-gate.js +205 -0
  32. package/scripts/lib/agent-access-governance.js +455 -0
  33. package/scripts/lib/agent-enforcement.js +765 -0
  34. package/scripts/lib/agent-policy-profile.js +210 -0
  35. package/scripts/lib/agent-security/action-evaluator.js +507 -0
  36. package/scripts/lib/agent-security/adapter-registry.js +98 -0
  37. package/scripts/lib/agent-security/auto-policy.js +139 -0
  38. package/scripts/lib/agent-security/bounded-scan.js +93 -0
  39. package/scripts/lib/agent-security/enforcement-adapter.js +174 -0
  40. package/scripts/lib/agent-security/enforcement-engine.js +1129 -0
  41. package/scripts/lib/agent-security/file-write-adapter.js +183 -0
  42. package/scripts/lib/agent-security/file-write-rules.js +178 -0
  43. package/scripts/lib/agent-security/index.js +3342 -0
  44. package/scripts/lib/agent-security/instruction-risk.js +181 -0
  45. package/scripts/lib/agent-security/mcp-action-adapter.js +185 -0
  46. package/scripts/lib/agent-security/mcp-action-rules.js +184 -0
  47. package/scripts/lib/agent-security/package-action-adapter.js +175 -0
  48. package/scripts/lib/agent-security/package-action-rules.js +233 -0
  49. package/scripts/lib/agent-security/performance.js +148 -0
  50. package/scripts/lib/agent-security/permission-minimizer.js +403 -0
  51. package/scripts/lib/agent-security/scan-cache.js +74 -0
  52. package/scripts/lib/agent-security/source-trust.js +146 -0
  53. package/scripts/lib/ai-install-prompt.js +288 -0
  54. package/scripts/lib/ai-workspace-hygiene.js +1499 -0
  55. package/scripts/lib/alpha-activation.js +520 -0
  56. package/scripts/lib/alpha-feedback.js +263 -0
  57. package/scripts/lib/alpha-readiness-gate.js +332 -0
  58. package/scripts/lib/anti-gaming.js +169 -0
  59. package/scripts/lib/artifact-health.js +431 -0
  60. package/scripts/lib/attribution.js +180 -0
  61. package/scripts/lib/audit.js +289 -0
  62. package/scripts/lib/avorelo-skill-registry.js +810 -0
  63. package/scripts/lib/batch-jobs.js +71 -0
  64. package/scripts/lib/brain-pack.js +578 -0
  65. package/scripts/lib/brand-boundary.js +424 -0
  66. package/scripts/lib/brand.js +74 -0
  67. package/scripts/lib/browser-capability.js +1048 -0
  68. package/scripts/lib/browser-proof-preflight.js +321 -0
  69. package/scripts/lib/cache-readiness.js +187 -0
  70. package/scripts/lib/canonical-reentry.js +162 -0
  71. package/scripts/lib/capability-packs.js +314 -0
  72. package/scripts/lib/capability-recommender.js +512 -0
  73. package/scripts/lib/capability-registry.js +1059 -0
  74. package/scripts/lib/carry-forward-surfacing.js +194 -0
  75. package/scripts/lib/ccusage-adapter.js +188 -0
  76. package/scripts/lib/company-loop.js +1149 -0
  77. package/scripts/lib/config.js +637 -0
  78. package/scripts/lib/context-acquisition-plan.js +287 -0
  79. package/scripts/lib/context-budget-guard.js +170 -0
  80. package/scripts/lib/context-budget-scanner.js +257 -0
  81. package/scripts/lib/context-optimizer.js +715 -0
  82. package/scripts/lib/context-reduction-plan.js +178 -0
  83. package/scripts/lib/context-safety.js +88 -0
  84. package/scripts/lib/context-savings-engine.js +158 -0
  85. package/scripts/lib/cost-evidence.js +254 -0
  86. package/scripts/lib/cross-host-install-plan.js +308 -0
  87. package/scripts/lib/cross-host-install-readiness.js +237 -0
  88. package/scripts/lib/cross-host-value-flow.js +268 -0
  89. package/scripts/lib/dashboard.js +900 -0
  90. package/scripts/lib/design-partner-feedback.js +346 -0
  91. package/scripts/lib/entitlements.js +100 -0
  92. package/scripts/lib/execution-packet.js +559 -0
  93. package/scripts/lib/experimentation-events.js +547 -0
  94. package/scripts/lib/external-capability-compliance.js +107 -0
  95. package/scripts/lib/external-user-simulation.js +166 -0
  96. package/scripts/lib/failure-recovery-readiness.js +81 -0
  97. package/scripts/lib/failure-recovery.js +419 -0
  98. package/scripts/lib/feedback-intelligence.js +537 -0
  99. package/scripts/lib/feedback-signals.js +205 -0
  100. package/scripts/lib/file-integrity.js +68 -0
  101. package/scripts/lib/fsx.js +127 -0
  102. package/scripts/lib/full-readiness-gate.js +451 -0
  103. package/scripts/lib/guidance-builder.js +174 -0
  104. package/scripts/lib/hook-apply.js +1019 -0
  105. package/scripts/lib/hook-baseline.js +310 -0
  106. package/scripts/lib/hook-config-preview.js +275 -0
  107. package/scripts/lib/hook-contracts.js +290 -0
  108. package/scripts/lib/hook-safety-boundary-readiness.js +80 -0
  109. package/scripts/lib/host-capability-matrix.js +351 -0
  110. package/scripts/lib/host-support-context.js +254 -0
  111. package/scripts/lib/http-hook-action.js +538 -0
  112. package/scripts/lib/install-ai-readiness.js +84 -0
  113. package/scripts/lib/install-intake-risk.js +1037 -0
  114. package/scripts/lib/install-journey-intelligence.js +329 -0
  115. package/scripts/lib/intervention-guidance.js +57 -0
  116. package/scripts/lib/known-limitations.js +115 -0
  117. package/scripts/lib/l8-path-truth.js +146 -0
  118. package/scripts/lib/launch-hardening-gate.js +436 -0
  119. package/scripts/lib/launch-readiness.js +628 -0
  120. package/scripts/lib/learning-memory.js +686 -0
  121. package/scripts/lib/lifecycle-hooks.js +802 -0
  122. package/scripts/lib/local-package-smoke.js +423 -0
  123. package/scripts/lib/local-pricing.js +299 -0
  124. package/scripts/lib/mcp-enforcement.js +311 -0
  125. package/scripts/lib/mcp-least-privilege-policy.js +303 -0
  126. package/scripts/lib/mcp-tool-inventory.js +388 -0
  127. package/scripts/lib/mcp-tool-risk.js +0 -0
  128. package/scripts/lib/memory.js +335 -0
  129. package/scripts/lib/metrics.js +699 -0
  130. package/scripts/lib/micro-proof.js +133 -0
  131. package/scripts/lib/next-run-context.js +436 -0
  132. package/scripts/lib/operating-value.js +1648 -0
  133. package/scripts/lib/optimization-v3.js +122 -0
  134. package/scripts/lib/orchestration/adapters/_shared.js +49 -0
  135. package/scripts/lib/orchestration/adapters/aider.js +18 -0
  136. package/scripts/lib/orchestration/adapters/claude-code.js +35 -0
  137. package/scripts/lib/orchestration/adapters/codex.js +35 -0
  138. package/scripts/lib/orchestration/adapters/gemini-cli.js +18 -0
  139. package/scripts/lib/orchestration/adapters/git.js +25 -0
  140. package/scripts/lib/orchestration/adapters/index.js +31 -0
  141. package/scripts/lib/orchestration/adapters/lm-studio.js +18 -0
  142. package/scripts/lib/orchestration/adapters/ollama.js +18 -0
  143. package/scripts/lib/orchestration/adapters/opencode.js +18 -0
  144. package/scripts/lib/orchestration/adapters/openrouter.js +18 -0
  145. package/scripts/lib/orchestration/adapters/test-runner.js +25 -0
  146. package/scripts/lib/orchestration/cli.js +438 -0
  147. package/scripts/lib/orchestration/execution-manager.js +279 -0
  148. package/scripts/lib/orchestration/handoff.js +314 -0
  149. package/scripts/lib/orchestration/index.js +456 -0
  150. package/scripts/lib/orchestration/inventory.js +47 -0
  151. package/scripts/lib/orchestration/model-discovery.js +498 -0
  152. package/scripts/lib/orchestration/model-profiler.js +170 -0
  153. package/scripts/lib/orchestration/model-profiles.js +252 -0
  154. package/scripts/lib/orchestration/model-refresh-policy.js +72 -0
  155. package/scripts/lib/orchestration/proof-writer.js +349 -0
  156. package/scripts/lib/orchestration/provider-discovery/aider.js +49 -0
  157. package/scripts/lib/orchestration/provider-discovery/claude-code.js +56 -0
  158. package/scripts/lib/orchestration/provider-discovery/codex.js +49 -0
  159. package/scripts/lib/orchestration/provider-discovery/common.js +186 -0
  160. package/scripts/lib/orchestration/provider-discovery/gemini.js +106 -0
  161. package/scripts/lib/orchestration/provider-discovery/lm-studio.js +118 -0
  162. package/scripts/lib/orchestration/provider-discovery/models-dev.js +12 -0
  163. package/scripts/lib/orchestration/provider-discovery/ollama.js +100 -0
  164. package/scripts/lib/orchestration/provider-discovery/opencode.js +47 -0
  165. package/scripts/lib/orchestration/provider-discovery/openrouter.js +44 -0
  166. package/scripts/lib/orchestration/risk-classifier.js +130 -0
  167. package/scripts/lib/orchestration/routing-policy.js +486 -0
  168. package/scripts/lib/orchestration/settings.js +112 -0
  169. package/scripts/lib/orchestration/state.js +165 -0
  170. package/scripts/lib/orchestration/verification-manager.js +138 -0
  171. package/scripts/lib/output-profiles.js +146 -0
  172. package/scripts/lib/package-content-audit.js +368 -0
  173. package/scripts/lib/package-runtime.js +278 -0
  174. package/scripts/lib/plan-surface.js +53 -0
  175. package/scripts/lib/plans.js +2318 -0
  176. package/scripts/lib/policy-provider.js +27 -0
  177. package/scripts/lib/prelaunch-activation-readiness.js +409 -0
  178. package/scripts/lib/prelaunch-evidence-store.js +816 -0
  179. package/scripts/lib/prelaunch-intelligence.js +869 -0
  180. package/scripts/lib/pricing-experiment.js +118 -0
  181. package/scripts/lib/pro-moment-events.js +77 -0
  182. package/scripts/lib/pro-moment-state.js +227 -0
  183. package/scripts/lib/pro-moments.js +1216 -0
  184. package/scripts/lib/product-learning-events.js +629 -0
  185. package/scripts/lib/project-profile.js +555 -0
  186. package/scripts/lib/prompt-compiler.js +280 -0
  187. package/scripts/lib/prompt-lint.js +32 -0
  188. package/scripts/lib/prompt-suggestions.js +52 -0
  189. package/scripts/lib/proof-canonical.js +398 -0
  190. package/scripts/lib/proof-drilldown.js +383 -0
  191. package/scripts/lib/proof-events.js +342 -0
  192. package/scripts/lib/proof-history.js +243 -0
  193. package/scripts/lib/proof-metrics.js +296 -0
  194. package/scripts/lib/proof-outcome-evidence.js +134 -0
  195. package/scripts/lib/proof-receipt.js +335 -0
  196. package/scripts/lib/proof-record.js +461 -0
  197. package/scripts/lib/public-activation-distribution-gate.js +258 -0
  198. package/scripts/lib/public-cli.js +3891 -0
  199. package/scripts/lib/public-distribution-truth.js +211 -0
  200. package/scripts/lib/public-install-claim-checker.js +294 -0
  201. package/scripts/lib/publish-provenance-readiness.js +283 -0
  202. package/scripts/lib/readiness-delta.js +218 -0
  203. package/scripts/lib/readiness-evidence-closure.js +196 -0
  204. package/scripts/lib/reentry-memory-capture.js +241 -0
  205. package/scripts/lib/reentry-memory-retrieval.js +302 -0
  206. package/scripts/lib/reentry-memory-status.js +146 -0
  207. package/scripts/lib/reentry-memory-store.js +178 -0
  208. package/scripts/lib/reentry-state.js +66 -0
  209. package/scripts/lib/release-candidate-bundle.js +166 -0
  210. package/scripts/lib/remediation.js +81 -0
  211. package/scripts/lib/repo-map.js +391 -0
  212. package/scripts/lib/run-improvements-lifecycle.js +330 -0
  213. package/scripts/lib/run-improvements.js +789 -0
  214. package/scripts/lib/runtime-decision-policy.js +387 -0
  215. package/scripts/lib/safe-path-engine.js +705 -0
  216. package/scripts/lib/safe-run-controller.js +887 -0
  217. package/scripts/lib/score.js +262 -0
  218. package/scripts/lib/seamless-enforcement.js +329 -0
  219. package/scripts/lib/seamless-outcome.js +689 -0
  220. package/scripts/lib/seamless-reality-gate.js +5043 -0
  221. package/scripts/lib/security-risk-classifier.js +511 -0
  222. package/scripts/lib/security-scan.js +384 -0
  223. package/scripts/lib/session-context-optimizer.js +1211 -0
  224. package/scripts/lib/session-timing.js +315 -0
  225. package/scripts/lib/skill-hygiene.js +805 -0
  226. package/scripts/lib/skill-packs.js +161 -0
  227. package/scripts/lib/skills-operating-layer.js +580 -0
  228. package/scripts/lib/smart-work-routing.js +768 -0
  229. package/scripts/lib/source-catalog.js +700 -0
  230. package/scripts/lib/status-value-summary.js +32 -0
  231. package/scripts/lib/support-bundle.js +578 -0
  232. package/scripts/lib/task-continuation.js +440 -0
  233. package/scripts/lib/test-helpers.js +15 -0
  234. package/scripts/lib/tier.js +38 -0
  235. package/scripts/lib/token-context-quality-gate.js +370 -0
  236. package/scripts/lib/token-cost-capture.js +187 -0
  237. package/scripts/lib/token-cost-intelligence.js +358 -0
  238. package/scripts/lib/token-efficiency-evidence.js +213 -0
  239. package/scripts/lib/token-evidence.js +699 -0
  240. package/scripts/lib/tokenish.js +17 -0
  241. package/scripts/lib/tool-output-sandbox.js +304 -0
  242. package/scripts/lib/trust-audit.js +136 -0
  243. package/scripts/lib/unified-events.js +396 -0
  244. package/scripts/lib/upgrade-interruption-recovery.js +407 -0
  245. package/scripts/lib/usage-ledger.js +201 -0
  246. package/scripts/lib/value-ledger.js +130 -0
  247. package/scripts/lib/value-proof-calibration.js +531 -0
  248. package/scripts/lib/visual-qa.js +231 -0
  249. package/scripts/lib/voice-alpha.js +29 -0
  250. package/scripts/lib/work-aware-orchestration.js +976 -0
  251. package/scripts/lib/work-control-receipts.js +577 -0
  252. package/scripts/lib/work-ledger.js +1123 -0
  253. package/scripts/lib/work-panel-preview.js +352 -0
  254. package/scripts/lib/workflow-discipline.js +280 -0
  255. package/scripts/lib/workflow-signals.js +419 -0
  256. package/scripts/lib/workspace-map.js +281 -0
  257. package/scripts/lib/workspace-registry.js +1367 -0
  258. package/scripts/lib/workspace-resolver.js +480 -0
@@ -0,0 +1,387 @@
1
+ "use strict";
2
+
3
+ const { readAllMetrics, readSessionMetrics } = require("./metrics");
4
+ const { readOutcomeEvents } = require("./unified-events");
5
+
6
+ const POLICY_VERSION = 1;
7
+ const SESSION_ROUTE_POLICY_VERSION = 1;
8
+ const ESTIMATED_REROUTE_POLICY_VERSION = 1;
9
+
10
+ const RECENT_FAILURE_WINDOW_MS = 15 * 60 * 1000;
11
+ const SESSION_ROUTE_WINDOW_MS = 24 * 60 * 60 * 1000;
12
+ const COUNTERFACTUAL_REASON_CODES = new Set(["FAIL_RETRY_GUARD", "LOOP_REPEATED_TOOL", "CTX_LARGE_OUTPUT"]);
13
+
14
+ function numberOrNull(value) {
15
+ if (value === null || value === undefined || value === "") return null;
16
+ const number = Number(value);
17
+ return Number.isFinite(number) ? number : null;
18
+ }
19
+
20
+ function thresholdsForProfile(profile) {
21
+ if (profile === "cost-saver") {
22
+ return { cost: 0.03, tokens: 15000, latencyMs: 1500 };
23
+ }
24
+ if (profile === "quality") {
25
+ return { cost: 0.2, tokens: 60000, latencyMs: 4000 };
26
+ }
27
+ return { cost: 0.08, tokens: 30000, latencyMs: 2500 };
28
+ }
29
+
30
+ function heuristicThresholdsForProfile(profile) {
31
+ if (profile === "cost-saver") {
32
+ return { failureCount: 2, estimatedCounterfactual: 4 };
33
+ }
34
+ if (profile === "quality") {
35
+ return { failureCount: 2, estimatedCounterfactual: 8 };
36
+ }
37
+ return { failureCount: 2, estimatedCounterfactual: 6 };
38
+ }
39
+
40
+ function metricTs(metric) {
41
+ return Date.parse(metric?.ts || 0);
42
+ }
43
+
44
+ function eventTs(event) {
45
+ return Date.parse(event?.timestamp || 0);
46
+ }
47
+
48
+ function isRecent(ts, windowMs) {
49
+ return Number.isFinite(ts) && ts >= Date.now() - windowMs;
50
+ }
51
+
52
+ function triggerMatches(evidence, thresholds) {
53
+ const triggers = [];
54
+ if (numberOrNull(evidence.cost) !== null && Number(evidence.cost) >= thresholds.cost) triggers.push("cost");
55
+ if (numberOrNull(evidence.tokenUsage) !== null && Number(evidence.tokenUsage) >= thresholds.tokens) triggers.push("tokens");
56
+ if (numberOrNull(evidence.latencyMs) !== null && Number(evidence.latencyMs) >= thresholds.latencyMs) triggers.push("latency");
57
+ return triggers;
58
+ }
59
+
60
+ function evidenceSummary(evidence = {}) {
61
+ const parts = [];
62
+ if (evidence.provider || evidence.model) {
63
+ parts.push([evidence.provider, evidence.model].filter(Boolean).join(" "));
64
+ }
65
+ if (numberOrNull(evidence.tokenUsage) !== null) parts.push(`${Number(evidence.tokenUsage)} tokens`);
66
+ if (numberOrNull(evidence.cost) !== null) parts.push(`$${Number(evidence.cost).toFixed(2)}`);
67
+ if (numberOrNull(evidence.latencyMs) !== null) parts.push(`${Number(evidence.latencyMs)}ms`);
68
+ return parts.join(", ");
69
+ }
70
+
71
+ function extractFailureEvidence(metric = {}) {
72
+ const attribution = metric.attribution || {};
73
+ return {
74
+ provider: attribution.provider || null,
75
+ model: attribution.model || null,
76
+ tokenUsage: numberOrNull(attribution.tokenUsage),
77
+ cost: numberOrNull(attribution.cost),
78
+ latencyMs: numberOrNull(attribution.latencyMs),
79
+ executionPath: attribution.executionPath || null,
80
+ };
81
+ }
82
+
83
+ function toolLooksModelDriven(toolName, evidence = {}) {
84
+ return evidence.executionPath === "llm" || evidence.provider || evidence.model || toolName === "Agent";
85
+ }
86
+
87
+ function latestByTs(entries) {
88
+ return (entries || []).filter(Boolean).sort((a, b) => (b.ts || 0) - (a.ts || 0))[0] || null;
89
+ }
90
+
91
+ function findLatestExpensiveFailure(metrics, toolName, thresholds, options = {}) {
92
+ const windowMs = Number(options.windowMs || RECENT_FAILURE_WINDOW_MS);
93
+ const candidates = (Array.isArray(metrics) ? metrics : [])
94
+ .filter((metric) => metric?.event === "PostToolUseFailure" && String(metric?.tool || "") === String(toolName || ""))
95
+ .map((metric) => {
96
+ const ts = metricTs(metric);
97
+ const evidence = extractFailureEvidence(metric);
98
+ return {
99
+ metric,
100
+ ts,
101
+ evidence,
102
+ triggers: triggerMatches(evidence, thresholds),
103
+ };
104
+ })
105
+ .filter((entry) => isRecent(entry.ts, windowMs))
106
+ .filter((entry) => toolLooksModelDriven(toolName, entry.evidence))
107
+ .filter((entry) => entry.triggers.length > 0);
108
+
109
+ return latestByTs(candidates);
110
+ }
111
+
112
+ function buildCounterfactualSnapshot(cwd, sessionIds, windowMs) {
113
+ const events = readOutcomeEvents(cwd)
114
+ .map((event) => ({ event, ts: eventTs(event) }))
115
+ .filter((entry) => isRecent(entry.ts, windowMs))
116
+ .filter((entry) => sessionIds.has(String(entry.event.session_id || "")))
117
+ .filter((entry) => COUNTERFACTUAL_REASON_CODES.has(String(entry.event.reason_code || "")));
118
+
119
+ return {
120
+ estimatedCounterfactualTotal: events.reduce((sum, entry) => sum + Number(entry.event.estimated_counterfactual_value || 0), 0),
121
+ loopCount: events.filter((entry) => String(entry.event.reason_code || "") === "LOOP_REPEATED_TOOL").length,
122
+ largeOutputCount: events.filter((entry) => String(entry.event.reason_code || "") === "CTX_LARGE_OUTPUT").length,
123
+ };
124
+ }
125
+
126
+ function findEstimatedLowValuePattern(cwd, metrics, toolName, profile, windowMs, scope) {
127
+ const thresholds = heuristicThresholdsForProfile(profile);
128
+ const filtered = (Array.isArray(metrics) ? metrics : [])
129
+ .map((metric) => ({ metric, ts: metricTs(metric) }))
130
+ .filter((entry) => isRecent(entry.ts, windowMs));
131
+ const metricLoopCount = filtered.filter((entry) => (entry.metric?.reasonCodes || []).includes("LOOP_REPEATED_TOOL")).length;
132
+ const metricLargeOutputCount = filtered.filter((entry) => (entry.metric?.reasonCodes || []).includes("CTX_LARGE_OUTPUT")).length;
133
+
134
+ const modelDrivenFailures = filtered
135
+ .filter((entry) => entry.metric?.event === "PostToolUseFailure")
136
+ .filter((entry) => !toolName || String(entry.metric?.tool || "") === String(toolName))
137
+ .map((entry) => ({
138
+ metric: entry.metric,
139
+ ts: entry.ts,
140
+ evidence: extractFailureEvidence(entry.metric),
141
+ }))
142
+ .filter((entry) => toolLooksModelDriven(toolName || entry.metric?.tool, entry.evidence))
143
+ .filter((entry) => entry.evidence.cost === null);
144
+
145
+ if (modelDrivenFailures.length < thresholds.failureCount) {
146
+ return null;
147
+ }
148
+
149
+ const sessionIds = new Set(modelDrivenFailures.map((entry) => String(entry.metric?.sessionId || "")));
150
+ const counterfactual = buildCounterfactualSnapshot(cwd, sessionIds, windowMs);
151
+ const triggers = [];
152
+ if (counterfactual.estimatedCounterfactualTotal >= thresholds.estimatedCounterfactual) triggers.push("estimated_counterfactual");
153
+ if (counterfactual.loopCount > 0 || metricLoopCount > 0) triggers.push("loops");
154
+ if (counterfactual.largeOutputCount > 0 || metricLargeOutputCount > 0) triggers.push("large_output");
155
+
156
+ if (!triggers.length) {
157
+ return null;
158
+ }
159
+
160
+ const latestFailure = latestByTs(modelDrivenFailures);
161
+ return {
162
+ scope,
163
+ failureCount: modelDrivenFailures.length,
164
+ estimatedCounterfactualTotal: counterfactual.estimatedCounterfactualTotal,
165
+ loopCount: Math.max(counterfactual.loopCount, metricLoopCount),
166
+ largeOutputCount: Math.max(counterfactual.largeOutputCount, metricLargeOutputCount),
167
+ triggerSignals: triggers,
168
+ thresholds,
169
+ latestFailure,
170
+ };
171
+ }
172
+
173
+ function buildMeasuredRouteDecision(toolName, profile, requestedProfile, failure) {
174
+ const summary = evidenceSummary(failure.evidence);
175
+ const detail = summary ? ` after recent ${toolName} failures using ${summary}` : "";
176
+ return {
177
+ outputProfile: "terse",
178
+ reasonCodes: ["SMART_ROUTE_CHEAPER_SAFER"],
179
+ meta: {
180
+ decisionPolicy: `runtime_route_optimizer_v${SESSION_ROUTE_POLICY_VERSION}`,
181
+ evidenceBasis: "measured",
182
+ profile,
183
+ triggerSignals: failure.triggers,
184
+ thresholds: thresholdsForProfile(profile),
185
+ routeBefore: requestedProfile,
186
+ routeAfter: "terse",
187
+ automatic: true,
188
+ sourceTool: toolName,
189
+ priorFailureAt: failure.metric.ts || null,
190
+ priorRuntimeEvidence: {
191
+ provider: failure.evidence.provider,
192
+ model: failure.evidence.model,
193
+ tokenUsage: failure.evidence.tokenUsage,
194
+ cost: failure.evidence.cost,
195
+ latencyMs: failure.evidence.latencyMs,
196
+ costSource: failure.evidence.cost !== null ? "measured" : "unavailable",
197
+ },
198
+ },
199
+ intervention: {
200
+ eventName: "SessionStart",
201
+ kind: "runtime_route_downgrade",
202
+ whatHappened: `Wuz automatically started this session on a cheaper-safer route${detail}`,
203
+ nextAction: "Stay on the terse route first and re-expand only if the next step clearly needs deeper model work",
204
+ preservedState: "Launch stayed local-first and no tool ran before the route change",
205
+ },
206
+ };
207
+ }
208
+
209
+ function buildEstimatedRouteDecision(profile, requestedProfile, pattern) {
210
+ const detail = `${pattern.failureCount} recent failed model-driven attempt${pattern.failureCount === 1 ? "" : "s"}, estimated counterfactual ${Math.round(pattern.estimatedCounterfactualTotal * 100) / 100}, loops ${pattern.loopCount}`;
211
+ return {
212
+ outputProfile: "terse",
213
+ reasonCodes: ["SMART_ROUTE_CHEAPER_SAFER"],
214
+ meta: {
215
+ decisionPolicy: `runtime_route_optimizer_v${SESSION_ROUTE_POLICY_VERSION}`,
216
+ evidenceBasis: "estimated",
217
+ profile,
218
+ triggerSignals: pattern.triggerSignals,
219
+ thresholds: pattern.thresholds,
220
+ routeBefore: requestedProfile,
221
+ routeAfter: "terse",
222
+ automatic: true,
223
+ sourceTool: pattern.latestFailure?.metric?.tool || null,
224
+ priorFailureAt: pattern.latestFailure?.metric?.ts || null,
225
+ heuristicEvidence: {
226
+ failureCount: pattern.failureCount,
227
+ estimatedCounterfactualTotal: Math.round(pattern.estimatedCounterfactualTotal * 100) / 100,
228
+ loopCount: pattern.loopCount,
229
+ largeOutputCount: pattern.largeOutputCount,
230
+ costSource: "estimated",
231
+ },
232
+ },
233
+ intervention: {
234
+ eventName: "SessionStart",
235
+ kind: "runtime_route_downgrade",
236
+ whatHappened: `Wuz automatically started this session on a cheaper-safer route because recent model-driven retries already showed low expected value without measured cost (${detail})`,
237
+ nextAction: "Stay on the terse route first and prefer deterministic steps until the bounded path proves insufficient",
238
+ preservedState: "Launch stayed local-first and no tool ran before the route change",
239
+ },
240
+ };
241
+ }
242
+
243
+ function evaluateSessionStartRouteDecision(params = {}) {
244
+ const requestedProfile = String(params.requestedProfile || "standard");
245
+ if (!params.config?.smartRouting?.enabled) return null;
246
+ if (requestedProfile !== "standard") return null;
247
+
248
+ const profile = String(params.config?.profile || "balanced");
249
+ const allMetrics = readAllMetrics(params.cwd);
250
+ const thresholds = thresholdsForProfile(profile);
251
+
252
+ const candidates = [
253
+ findLatestExpensiveFailure(allMetrics, "Agent", thresholds, { windowMs: SESSION_ROUTE_WINDOW_MS }),
254
+ findLatestExpensiveFailure(allMetrics, "Skill", thresholds, { windowMs: SESSION_ROUTE_WINDOW_MS }),
255
+ ].filter(Boolean);
256
+
257
+ const measuredFailure = latestByTs(candidates);
258
+ if (measuredFailure) {
259
+ return buildMeasuredRouteDecision(String(measuredFailure.metric?.tool || "Agent"), profile, requestedProfile, measuredFailure);
260
+ }
261
+
262
+ const estimatedPattern = findEstimatedLowValuePattern(
263
+ params.cwd,
264
+ allMetrics,
265
+ null,
266
+ profile,
267
+ SESSION_ROUTE_WINDOW_MS,
268
+ "repo"
269
+ );
270
+ if (!estimatedPattern) return null;
271
+
272
+ return buildEstimatedRouteDecision(profile, requestedProfile, estimatedPattern);
273
+ }
274
+
275
+ function buildEstimatedRerouteDecision(toolName, profile, pattern) {
276
+ const failure = pattern.latestFailure;
277
+ const modelLine = evidenceSummary(failure?.evidence || {});
278
+ const detailParts = [];
279
+ if (modelLine) detailParts.push(modelLine);
280
+ detailParts.push(`${pattern.failureCount} failed attempt${pattern.failureCount === 1 ? "" : "s"}`);
281
+ detailParts.push(`estimated counterfactual ${Math.round(pattern.estimatedCounterfactualTotal * 100) / 100}`);
282
+ if (pattern.loopCount > 0) detailParts.push(`${pattern.loopCount} loop signal${pattern.loopCount === 1 ? "" : "s"}`);
283
+ const detail = detailParts.join(", ");
284
+
285
+ return {
286
+ action: "deny",
287
+ reasonCodes: ["EVIDENCE_ESTIMATED_LOW_VALUE_REROUTE"],
288
+ meta: {
289
+ decisionPolicy: `runtime_estimated_low_value_guard_v${ESTIMATED_REROUTE_POLICY_VERSION}`,
290
+ evidenceBasis: "estimated",
291
+ profile,
292
+ triggerSignals: pattern.triggerSignals,
293
+ thresholds: pattern.thresholds,
294
+ routeBefore: "model-driven",
295
+ routeAfter: "deterministic-first",
296
+ automatic: true,
297
+ sourceTool: toolName,
298
+ priorFailureAt: failure?.metric?.ts || null,
299
+ heuristicEvidence: {
300
+ provider: failure?.evidence?.provider || null,
301
+ model: failure?.evidence?.model || null,
302
+ failureCount: pattern.failureCount,
303
+ estimatedCounterfactualTotal: Math.round(pattern.estimatedCounterfactualTotal * 100) / 100,
304
+ loopCount: pattern.loopCount,
305
+ largeOutputCount: pattern.largeOutputCount,
306
+ costSource: "estimated",
307
+ },
308
+ },
309
+ intervention: {
310
+ eventName: "PreToolUse",
311
+ kind: "runtime_estimated_low_value_reroute",
312
+ whatHappened: `Wuz automatically kept this ${toolName} rerun off the model-driven path because recent retries already showed low expected value without measured cost (${detail})`,
313
+ nextAction: "Use a deterministic or smaller bounded step first, then retry the model-driven path only if that step proves it is still needed",
314
+ preservedState: `The ${toolName} rerun did not start`,
315
+ },
316
+ };
317
+ }
318
+
319
+ function evaluatePreToolRuntimeDecision(params = {}) {
320
+ const toolName = String(params.toolName || "");
321
+ if (!["Agent", "Skill"].includes(toolName)) return null;
322
+
323
+ const runtimeEvidence = params.runtimeEvidence || {};
324
+ const currentLooksModelDriven = toolLooksModelDriven(toolName, runtimeEvidence);
325
+ if (!currentLooksModelDriven) return null;
326
+
327
+ const profile = String(params.config?.profile || "balanced");
328
+ const thresholds = thresholdsForProfile(profile);
329
+ const metrics = readSessionMetrics(params.cwd, params.sessionId);
330
+ const recentFailure = findLatestExpensiveFailure(metrics, toolName, thresholds);
331
+ if (recentFailure) {
332
+ const summary = evidenceSummary(recentFailure.evidence);
333
+ const detail = summary ? ` after using ${summary}` : "";
334
+ const evidenceRecord = {
335
+ provider: recentFailure.evidence.provider,
336
+ model: recentFailure.evidence.model,
337
+ tokenUsage: recentFailure.evidence.tokenUsage,
338
+ cost: recentFailure.evidence.cost,
339
+ latencyMs: recentFailure.evidence.latencyMs,
340
+ costSource: recentFailure.evidence.cost !== null ? "measured" : "unavailable",
341
+ };
342
+
343
+ return {
344
+ action: "ask",
345
+ reasonCodes: ["EVIDENCE_RUNTIME_RETRY_REVIEW"],
346
+ meta: {
347
+ decisionPolicy: `runtime_evidence_retry_guard_v${POLICY_VERSION}`,
348
+ profile,
349
+ thresholds,
350
+ triggerSignals: recentFailure.triggers,
351
+ priorFailureAt: recentFailure.metric.ts || null,
352
+ priorTool: toolName,
353
+ priorRuntimeEvidence: evidenceRecord,
354
+ },
355
+ intervention: {
356
+ eventName: "PreToolUse",
357
+ kind: "runtime_evidence_retry_review",
358
+ whatHappened: `Wuz paused this ${toolName} rerun because the last attempt failed${detail}`,
359
+ nextAction: "Narrow scope, prefer a deterministic path if one exists, or confirm the stronger reason for paying this cost before rerunning",
360
+ preservedState: `The ${toolName} rerun did not start`,
361
+ },
362
+ };
363
+ }
364
+
365
+ if (!params.config?.smartRouting?.enabled) return null;
366
+ const estimatedPattern = findEstimatedLowValuePattern(
367
+ params.cwd,
368
+ metrics,
369
+ toolName,
370
+ profile,
371
+ RECENT_FAILURE_WINDOW_MS,
372
+ "session"
373
+ );
374
+ if (!estimatedPattern) return null;
375
+
376
+ return buildEstimatedRerouteDecision(toolName, profile, estimatedPattern);
377
+ }
378
+
379
+ module.exports = {
380
+ POLICY_VERSION,
381
+ SESSION_ROUTE_POLICY_VERSION,
382
+ ESTIMATED_REROUTE_POLICY_VERSION,
383
+ thresholdsForProfile,
384
+ heuristicThresholdsForProfile,
385
+ evaluatePreToolRuntimeDecision,
386
+ evaluateSessionStartRouteDecision,
387
+ };