pi-smart-compact 9.7.1 → 10.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 (206) hide show
  1. package/ARCHITECTURE.md +973 -372
  2. package/CHANGELOG.md +721 -0
  3. package/LICENSE +8 -0
  4. package/README.md +128 -640
  5. package/SECURITY.md +34 -12
  6. package/SUPPORT.md +26 -9
  7. package/assets/DejaVu-LICENSE.txt +187 -0
  8. package/assets/DejaVuSansMono.ttf +0 -0
  9. package/assets/README.md +26 -0
  10. package/assets/skills/context-management/SKILL.md +34 -0
  11. package/dist/app/anchor-cache.d.ts +36 -0
  12. package/dist/app/anchor-cache.d.ts.map +1 -0
  13. package/dist/app/artifact-storage.d.ts +47 -0
  14. package/dist/app/artifact-storage.d.ts.map +1 -0
  15. package/dist/app/background-preparation.d.ts +39 -0
  16. package/dist/app/background-preparation.d.ts.map +1 -0
  17. package/dist/app/compaction-commit-store.d.ts +5 -1
  18. package/dist/app/compaction-commit-store.d.ts.map +1 -1
  19. package/dist/app/context-evidence.d.ts +57 -0
  20. package/dist/app/context-evidence.d.ts.map +1 -0
  21. package/dist/app/context-guide.d.ts +3 -0
  22. package/dist/app/context-guide.d.ts.map +1 -0
  23. package/dist/app/context-operations.d.ts +106 -0
  24. package/dist/app/context-operations.d.ts.map +1 -0
  25. package/dist/app/effective-state.d.ts +23 -0
  26. package/dist/app/effective-state.d.ts.map +1 -0
  27. package/dist/app/global-settings-runtime.d.ts +3 -3
  28. package/dist/app/global-settings-runtime.d.ts.map +1 -1
  29. package/dist/app/hindsight-memory.d.ts +100 -0
  30. package/dist/app/hindsight-memory.d.ts.map +1 -0
  31. package/dist/app/host-cache-ledger.d.ts +68 -0
  32. package/dist/app/host-cache-ledger.d.ts.map +1 -0
  33. package/dist/app/lazy-tools.d.ts +36 -0
  34. package/dist/app/lazy-tools.d.ts.map +1 -0
  35. package/dist/app/memory-backend.d.ts +58 -0
  36. package/dist/app/memory-backend.d.ts.map +1 -0
  37. package/dist/app/mnemopi-memory.d.ts +13 -0
  38. package/dist/app/mnemopi-memory.d.ts.map +1 -0
  39. package/dist/app/mnemopi-protocol.d.ts +78 -0
  40. package/dist/app/mnemopi-protocol.d.ts.map +1 -0
  41. package/dist/app/mnemopi-worker.d.ts +2 -0
  42. package/dist/app/mnemopi-worker.d.ts.map +1 -0
  43. package/dist/app/model-feasibility.d.ts +20 -0
  44. package/dist/app/model-feasibility.d.ts.map +1 -0
  45. package/dist/app/native-compaction.d.ts +88 -0
  46. package/dist/app/native-compaction.d.ts.map +1 -0
  47. package/dist/app/native-continuity-bridge.d.ts.map +1 -1
  48. package/dist/app/navigation-data.d.ts +28 -0
  49. package/dist/app/navigation-data.d.ts.map +1 -0
  50. package/dist/app/navigation-types.d.ts +60 -0
  51. package/dist/app/navigation-types.d.ts.map +1 -0
  52. package/dist/app/pending-slot.d.ts +11 -1
  53. package/dist/app/pending-slot.d.ts.map +1 -1
  54. package/dist/app/preflight.d.ts.map +1 -1
  55. package/dist/app/register-context-tools.d.ts +16 -3
  56. package/dist/app/register-context-tools.d.ts.map +1 -1
  57. package/dist/app/register-navigation.d.ts +20 -0
  58. package/dist/app/register-navigation.d.ts.map +1 -0
  59. package/dist/app/register-smart-compact-command.d.ts +17 -2
  60. package/dist/app/register-smart-compact-command.d.ts.map +1 -1
  61. package/dist/app/register-smart-compact-tool.d.ts.map +1 -1
  62. package/dist/app/register-smart-context-tool.d.ts +55 -0
  63. package/dist/app/register-smart-context-tool.d.ts.map +1 -0
  64. package/dist/app/run-context.d.ts +1 -0
  65. package/dist/app/run-context.d.ts.map +1 -1
  66. package/dist/app/run-smart-compact.d.ts +3 -3
  67. package/dist/app/run-smart-compact.d.ts.map +1 -1
  68. package/dist/app/session-handoff.d.ts +64 -0
  69. package/dist/app/session-handoff.d.ts.map +1 -0
  70. package/dist/app/session-lineage.d.ts +17 -0
  71. package/dist/app/session-lineage.d.ts.map +1 -0
  72. package/dist/app/session-run-lock.d.ts +0 -2
  73. package/dist/app/session-run-lock.d.ts.map +1 -1
  74. package/dist/app/settled-auto-trigger.d.ts +2 -0
  75. package/dist/app/settled-auto-trigger.d.ts.map +1 -1
  76. package/dist/app/smart-compact-input.d.ts +1 -1
  77. package/dist/app/smart-compact-input.d.ts.map +1 -1
  78. package/dist/app/smart-compact-policy.d.ts +1 -1
  79. package/dist/app/smart-compact-policy.d.ts.map +1 -1
  80. package/dist/app/steps/extract.d.ts +45 -1
  81. package/dist/app/steps/extract.d.ts.map +1 -1
  82. package/dist/app/steps/metrics.d.ts +1 -0
  83. package/dist/app/steps/metrics.d.ts.map +1 -1
  84. package/dist/app/steps/persist.d.ts.map +1 -1
  85. package/dist/app/steps/prepare.d.ts.map +1 -1
  86. package/dist/app/steps/recover.d.ts +9 -0
  87. package/dist/app/steps/recover.d.ts.map +1 -1
  88. package/dist/app/steps/synthesize.d.ts.map +1 -1
  89. package/dist/app/steps/tier.d.ts.map +1 -1
  90. package/dist/app/steps/verify.d.ts.map +1 -1
  91. package/dist/app/steps/visual.d.ts +4 -0
  92. package/dist/app/steps/visual.d.ts.map +1 -0
  93. package/dist/app/steps/window.d.ts.map +1 -1
  94. package/dist/app/tool-artifacts.d.ts +27 -0
  95. package/dist/app/tool-artifacts.d.ts.map +1 -0
  96. package/dist/app/visual-archive.d.ts +29 -0
  97. package/dist/app/visual-archive.d.ts.map +1 -0
  98. package/dist/constants.d.ts +96 -1
  99. package/dist/constants.d.ts.map +1 -1
  100. package/dist/domain/compaction-usage.d.ts +16 -0
  101. package/dist/domain/compaction-usage.d.ts.map +1 -0
  102. package/dist/domain/model-capacity.d.ts +12 -0
  103. package/dist/domain/model-capacity.d.ts.map +1 -0
  104. package/dist/domain/provider-evaluation.d.ts +7 -0
  105. package/dist/domain/provider-evaluation.d.ts.map +1 -1
  106. package/dist/domain/telemetry.d.ts +43 -2
  107. package/dist/domain/telemetry.d.ts.map +1 -1
  108. package/dist/domain/tool-semantics.d.ts +23 -0
  109. package/dist/domain/tool-semantics.d.ts.map +1 -1
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +15757 -6942
  112. package/dist/infra/ai-messages.d.ts +1 -1
  113. package/dist/infra/ai-messages.d.ts.map +1 -1
  114. package/dist/infra/context-graph.d.ts +38 -7
  115. package/dist/infra/context-graph.d.ts.map +1 -1
  116. package/dist/infra/fs.d.ts.map +1 -1
  117. package/dist/infra/hindsight-client.d.ts +73 -0
  118. package/dist/infra/hindsight-client.d.ts.map +1 -0
  119. package/dist/infra/hindsight-receipts.d.ts +68 -0
  120. package/dist/infra/hindsight-receipts.d.ts.map +1 -0
  121. package/dist/infra/llm-client.d.ts +26 -23
  122. package/dist/infra/llm-client.d.ts.map +1 -1
  123. package/dist/infra/memory-ref.d.ts +27 -0
  124. package/dist/infra/memory-ref.d.ts.map +1 -0
  125. package/dist/infra/native-protocol.d.ts +54 -0
  126. package/dist/infra/native-protocol.d.ts.map +1 -0
  127. package/dist/infra/optional-components.d.ts +15 -0
  128. package/dist/infra/optional-components.d.ts.map +1 -0
  129. package/dist/infra/paths.d.ts +2 -0
  130. package/dist/infra/paths.d.ts.map +1 -1
  131. package/dist/infra/services.d.ts +15 -5
  132. package/dist/infra/services.d.ts.map +1 -1
  133. package/dist/infra/visual-renderer.d.ts +16 -0
  134. package/dist/infra/visual-renderer.d.ts.map +1 -0
  135. package/dist/mnemopi-worker.js +213 -0
  136. package/dist/phases/explore.d.ts +12 -9
  137. package/dist/phases/explore.d.ts.map +1 -1
  138. package/dist/phases/synthesize.d.ts +18 -3
  139. package/dist/phases/synthesize.d.ts.map +1 -1
  140. package/dist/phases/verify.d.ts +5 -1
  141. package/dist/phases/verify.d.ts.map +1 -1
  142. package/dist/rtk.d.ts +7 -0
  143. package/dist/rtk.d.ts.map +1 -0
  144. package/dist/rtk.js +767 -0
  145. package/dist/types.d.ts +128 -4
  146. package/dist/types.d.ts.map +1 -1
  147. package/dist/ui/dashboard-format.d.ts +2 -1
  148. package/dist/ui/dashboard-format.d.ts.map +1 -1
  149. package/dist/ui/dashboard-insights.d.ts +9 -1
  150. package/dist/ui/dashboard-insights.d.ts.map +1 -1
  151. package/dist/ui/error-format.d.ts +7 -2
  152. package/dist/ui/error-format.d.ts.map +1 -1
  153. package/dist/ui/handoff-overlay.d.ts +26 -0
  154. package/dist/ui/handoff-overlay.d.ts.map +1 -0
  155. package/dist/ui/home-overlay.d.ts +54 -0
  156. package/dist/ui/home-overlay.d.ts.map +1 -0
  157. package/dist/ui/metrics-dashboard-overlay.d.ts.map +1 -1
  158. package/dist/ui/metrics-report.d.ts.map +1 -1
  159. package/dist/ui/navigation-overlay.d.ts +92 -0
  160. package/dist/ui/navigation-overlay.d.ts.map +1 -0
  161. package/dist/ui/overlays.d.ts +12 -2
  162. package/dist/ui/overlays.d.ts.map +1 -1
  163. package/dist/ui/profiles.d.ts +51 -0
  164. package/dist/ui/profiles.d.ts.map +1 -0
  165. package/dist/ui/settings-complex.d.ts +49 -3
  166. package/dist/ui/settings-complex.d.ts.map +1 -1
  167. package/dist/ui/settings-list.d.ts +28 -0
  168. package/dist/ui/settings-list.d.ts.map +1 -0
  169. package/dist/ui/settings-overlay.d.ts +13 -6
  170. package/dist/ui/settings-overlay.d.ts.map +1 -1
  171. package/dist/ui/storage-report.d.ts +4 -0
  172. package/dist/ui/storage-report.d.ts.map +1 -0
  173. package/dist/utils/backups.d.ts.map +1 -1
  174. package/dist/utils/cache.d.ts +6 -2
  175. package/dist/utils/cache.d.ts.map +1 -1
  176. package/dist/utils/config.d.ts +12 -0
  177. package/dist/utils/config.d.ts.map +1 -1
  178. package/dist/utils/helpers.d.ts.map +1 -1
  179. package/dist/utils/id-fingerprint.d.ts +3 -1
  180. package/dist/utils/id-fingerprint.d.ts.map +1 -1
  181. package/dist/utils/issues.d.ts +61 -0
  182. package/dist/utils/issues.d.ts.map +1 -0
  183. package/dist/utils/pruning.d.ts.map +1 -1
  184. package/dist/utils/session-log.d.ts +0 -2
  185. package/dist/utils/session-log.d.ts.map +1 -1
  186. package/dist/utils/state.d.ts +3 -1
  187. package/dist/utils/state.d.ts.map +1 -1
  188. package/dist/utils/tokens.d.ts +10 -2
  189. package/dist/utils/tokens.d.ts.map +1 -1
  190. package/docs/MIGRATING_TO_V8.md +7 -1
  191. package/docs/README.md +69 -0
  192. package/docs/RELEASE.md +173 -56
  193. package/docs/assets/banner.png +0 -0
  194. package/docs/assets/banner.svg +1158 -70
  195. package/docs/assets/pi-smart-compact.png +0 -0
  196. package/docs/assets/pi-smart-compact.svg +24 -0
  197. package/docs/configuration.md +637 -0
  198. package/docs/evaluation.md +408 -0
  199. package/docs/guide.md +860 -0
  200. package/docs/hindsight-memory.md +314 -0
  201. package/docs/identity.md +124 -0
  202. package/package.json +44 -11
  203. package/dist/provider-eval.js +0 -2122
  204. package/dist/provider-scenario-eval.js +0 -2900
  205. package/dist/telemetry-report.js +0 -1973
  206. package/docs/provider-evaluation-2026-08-06.md +0 -63
@@ -1,2122 +0,0 @@
1
- // @bun
2
- // src/domain/provider-evaluation.ts
3
- function providerScenario(entry) {
4
- const context = (entry.contextPercent ?? 0) >= 90 ? "critical" : (entry.contextPercent ?? 0) >= 70 ? "pressure" : "compact";
5
- const workload = (entry.toolPercent ?? 0) >= 70 ? "tool-heavy" : (entry.toolPercent ?? 0) >= 30 ? "mixed" : "conversational";
6
- return context + "/" + workload;
7
- }
8
- function validPersistedRoutes(value) {
9
- if (!Array.isArray(value))
10
- return [];
11
- return value.filter((route) => {
12
- if (!route || typeof route !== "object")
13
- return false;
14
- const item = route;
15
- const qualityValid = item.qualityScore === undefined && item.qualityBasis === undefined || typeof item.qualityScore === "number" && Number.isFinite(item.qualityScore) && item.qualityScore >= 0 && item.qualityScore <= 100 && item.qualityBasis === "pre-repair-verification";
16
- return qualityValid && (item.stage === "explore" || item.stage === "synthesize" || item.stage === "verify") && typeof item.provider === "string" && typeof item.model === "string" && typeof item.calls === "number" && Number.isFinite(item.calls) && item.calls > 0 && typeof item.successes === "number" && Number.isFinite(item.successes) && typeof item.avgLatencyMs === "number" && Number.isFinite(item.avgLatencyMs) && typeof item.inputTokens === "number" && Number.isFinite(item.inputTokens) && typeof item.outputTokens === "number" && Number.isFinite(item.outputTokens);
17
- });
18
- }
19
- function legacyRoute(entry) {
20
- if (!entry.provider || !entry.model || !entry.totalCalls)
21
- return [];
22
- const model = entry.model.startsWith(entry.provider + "/") ? entry.model.slice(entry.provider.length + 1) : entry.model;
23
- const successful = entry.status === "success" || entry.status === "dry-run";
24
- return [{
25
- stage: "synthesize",
26
- provider: entry.provider,
27
- model,
28
- calls: entry.totalCalls,
29
- successes: successful ? entry.totalCalls : 0,
30
- avgLatencyMs: entry.avgLatency,
31
- inputTokens: entry.totalInput,
32
- outputTokens: entry.totalOutput
33
- }];
34
- }
35
- function evaluateProviderMetrics(entries, options = {}) {
36
- const minSamples = Math.max(2, options.minSamples ?? 5);
37
- const groups = new Map;
38
- for (const entry of entries) {
39
- const scenario = providerScenario(entry);
40
- const persistedRoutes = validPersistedRoutes(entry.providerRoutes);
41
- const routes = persistedRoutes.length ? persistedRoutes : legacyRoute(entry);
42
- for (const route of routes) {
43
- const key = route.stage + "\x00" + scenario + "\x00" + route.provider + "\x00" + route.model;
44
- const group = groups.get(key) ?? {
45
- stage: route.stage,
46
- scenario,
47
- provider: route.provider,
48
- model: route.model,
49
- runs: 0,
50
- calls: 0,
51
- successes: 0,
52
- latencyCallMs: 0,
53
- tokens: 0,
54
- qualityTotal: 0,
55
- qualityRuns: 0
56
- };
57
- group.runs++;
58
- group.calls += route.calls;
59
- group.successes += Math.max(0, Math.min(route.calls, route.successes));
60
- group.latencyCallMs += Math.max(0, route.avgLatencyMs) * route.calls;
61
- group.tokens += Math.max(0, route.inputTokens) + Math.max(0, route.outputTokens);
62
- if (entry.metricsSchemaVersion === 2 && typeof route.qualityScore === "number") {
63
- group.qualityTotal += route.qualityScore;
64
- group.qualityRuns++;
65
- }
66
- groups.set(key, group);
67
- }
68
- }
69
- const cells = [...groups.values()].map((group) => {
70
- const successRate = group.calls ? group.successes / group.calls : 0;
71
- const avgLatencyMs = group.calls ? group.latencyCallMs / group.calls : 0;
72
- const avgTokensPerCall = group.calls ? group.tokens / group.calls : 0;
73
- const avgQuality = group.qualityRuns ? group.qualityTotal / group.qualityRuns : null;
74
- const qualityCoverage = group.runs ? group.qualityRuns / group.runs : 0;
75
- const quality = avgQuality == null ? 0.5 : avgQuality / 100;
76
- const latency = 1 / (1 + avgLatencyMs / 30000);
77
- const efficiency = 1 / (1 + avgTokensPerCall / 1e5);
78
- const rawScore = group.stage === "explore" ? successRate * 0.4 + latency * 0.3 + efficiency * 0.15 + quality * 0.15 : quality * 0.4 + successRate * 0.3 + latency * 0.2 + efficiency * 0.1;
79
- const confidence = Math.min(1, group.runs / minSamples) * (0.5 + qualityCoverage * 0.5);
80
- const score = 0.5 + (rawScore - 0.5) * confidence;
81
- return {
82
- stage: group.stage,
83
- scenario: group.scenario,
84
- provider: group.provider,
85
- model: group.model,
86
- runs: group.runs,
87
- calls: group.calls,
88
- successRate: Math.round(successRate * 1000) / 1000,
89
- avgLatencyMs: Math.round(avgLatencyMs),
90
- avgTokensPerCall: Math.round(avgTokensPerCall),
91
- avgQuality: avgQuality == null ? null : Math.round(avgQuality * 10) / 10,
92
- qualityCoverage: Math.round(qualityCoverage * 1000) / 1000,
93
- score: Math.round(score * 1000) / 1000,
94
- confidence: Math.round(confidence * 1000) / 1000,
95
- eligible: group.runs >= minSamples && successRate >= 0.8 && qualityCoverage >= 0.5 && avgQuality != null && avgQuality >= 85
96
- };
97
- }).sort((a, b) => a.stage.localeCompare(b.stage) || a.scenario.localeCompare(b.scenario) || b.score - a.score);
98
- const buckets = new Map;
99
- for (const cell of cells) {
100
- const key = cell.stage + "\x00" + cell.scenario;
101
- const values = buckets.get(key) ?? [];
102
- values.push(cell);
103
- buckets.set(key, values);
104
- }
105
- const recommendations = [...buckets.values()].map((values) => {
106
- const best = values.filter((value) => value.eligible).sort((a, b) => b.score - a.score || b.confidence - a.confidence)[0];
107
- const first = values[0];
108
- return best ? {
109
- stage: best.stage,
110
- scenario: best.scenario,
111
- model: best.provider + "/" + best.model,
112
- score: best.score,
113
- confidence: best.confidence,
114
- reason: best.qualityCoverage > 0 ? "best eligible quality/reliability/latency score" : "best eligible operational score; quality evidence pending"
115
- } : {
116
- stage: first.stage,
117
- scenario: first.scenario,
118
- model: null,
119
- score: 0,
120
- confidence: 0,
121
- reason: "insufficient samples, reliability, schema-v2 coverage, or absolute quality; keep the selected model"
122
- };
123
- }).sort((a, b) => a.stage.localeCompare(b.stage) || a.scenario.localeCompare(b.scenario));
124
- return {
125
- generatedAt: new Date().toISOString(),
126
- entries: entries.length,
127
- minSamples,
128
- advisoryOnly: true,
129
- cells,
130
- recommendations
131
- };
132
- }
133
- function formatProviderEvaluation(report) {
134
- const lines = [
135
- "# Provider Evaluation (advisory only)",
136
- "",
137
- "The selected Pi model remains the default. Apply a stage route only after representative quality evidence.",
138
- "",
139
- "| Stage | Scenario | Provider/model | Runs | Success | Quality | Latency | Score | Confidence |",
140
- "|---|---|---|---:|---:|---:|---:|---:|---:|"
141
- ];
142
- for (const cell of report.cells) {
143
- lines.push("| " + cell.stage + " | " + cell.scenario + " | " + cell.provider + "/" + cell.model + " | " + cell.runs + " | " + Math.round(cell.successRate * 100) + "% | " + (cell.avgQuality == null ? "n/a" : cell.avgQuality.toFixed(1)) + " | " + cell.avgLatencyMs + "ms | " + cell.score.toFixed(3) + " | " + Math.round(cell.confidence * 100) + "% |");
144
- }
145
- lines.push("", "## Recommendations", "");
146
- for (const item of report.recommendations) {
147
- lines.push("- **" + item.stage + " / " + item.scenario + "**: " + (item.model ?? "selected model") + " \u2014 " + item.reason + ".");
148
- }
149
- return lines.join(`
150
- `);
151
- }
152
-
153
- // src/utils/cache.ts
154
- import fs2 from "fs";
155
-
156
- // src/constants.ts
157
- var VERSION = "9.7.1";
158
- var SETTLED_TRIGGER_COOLDOWN_MS = 10 * 60000;
159
- var COMPACT_SYSTEM_PREFIX = "You are an expert conversation summarizer for a coding agent. " + "Produce structured markdown summaries. " + "Follow output format exactly. " + "Use EXACT names \u2014 never paraphrase code identifiers. " + "Trust deterministic extraction data over intuition.";
160
- var PROFILES = {
161
- light: {
162
- summaryBudgetTokens: 1e4,
163
- keepRecentTokens: 30000,
164
- minChunkTokens: 800,
165
- maxChunkTokens: 12000,
166
- singlePassMaxTokens: 40000,
167
- batchMaxTokens: 30000
168
- },
169
- balanced: {
170
- summaryBudgetTokens: 6000,
171
- keepRecentTokens: 20000,
172
- minChunkTokens: 500,
173
- maxChunkTokens: 8000,
174
- singlePassMaxTokens: 30000,
175
- batchMaxTokens: 24000
176
- },
177
- aggressive: {
178
- summaryBudgetTokens: 3000,
179
- keepRecentTokens: 1e4,
180
- minChunkTokens: 300,
181
- maxChunkTokens: 6000,
182
- singlePassMaxTokens: 20000,
183
- batchMaxTokens: 18000
184
- }
185
- };
186
- var DEFAULT_CONFIG = {
187
- mode: "auto",
188
- profile: "balanced",
189
- profiles: PROFILES,
190
- summaryModel: null,
191
- segmentationModel: null,
192
- verificationModel: null,
193
- summaryThinkingLevel: "minimal",
194
- segmentationThinkingLevel: "minimal",
195
- agentToolAccess: "inherit",
196
- autoTrigger: true,
197
- showStatus: true,
198
- autoTriggerStrategy: "native-hook",
199
- autoTriggerTimeoutMs: 120000,
200
- backupEnabled: true,
201
- backupDir: "",
202
- minContextPercent: 60,
203
- requireApproval: true,
204
- scrubSecrets: true,
205
- scrubPii: false,
206
- maxLlmCalls: 8,
207
- maxLlmInputTokens: 0,
208
- codexMaxCallMs: 0,
209
- maxLatencyMs: 0,
210
- pendingTtlMs: 300000,
211
- focusWeighting: true,
212
- zeroCallEnabled: true,
213
- contextGraphEnabled: true,
214
- telemetryChannel: "stable",
215
- adaptiveDamageFeedback: false,
216
- onlineDamageMonitor: true,
217
- pinPaths: []
218
- };
219
- var SINGLE_PASS_PREFIX = `Summarize this coding agent conversation. Produce ONE structured summary.
220
- ` + `
221
- Rules for Accuracy:
222
- ` + `1. Session Type: read-only tool calls = REVIEW, not implementation
223
- ` + `2. Status: Check for user complaints before marking "Done"
224
- ` + `3. Exact Names: Quote specific variable/function/parameter names, don't paraphrase
225
- ` + `4. Files: Use the VERIFIED file lists above (deterministically extracted, zero hallucination risk)
226
- ` + `
227
- Output EXACTLY this format:
228
-
229
- ` + `## Goal
230
- [What the user is trying to accomplish]
231
- ` + `## Constraints & Preferences
232
- - [CRITICAL: user requirements, preferences, constraints]
233
- ` + `## Progress
234
- ### Done
235
- - [x] [Completed tasks with file references]
236
- ### In Progress
237
- - [ ] [Current work state]
238
- ### Blocked
239
- - [Issues]
240
- ` + `## Key Decisions
241
- - **[Decision]**: [Rationale]
242
- ` + `## Files Modified
243
- - [Verified list from deterministic extraction]
244
- ` + `## Files Deleted
245
- - [Verified deleted paths from deterministic extraction]
246
- ` + `## Files Read
247
- - [Verified list from deterministic extraction]
248
- ` + `## Next Steps
249
- 1. [What should happen next]
250
- ` + `## Critical Context
251
- - [Specific data, patterns, info needed to continue]
252
- - [Error patterns or gotchas]
253
- ` + `## Topics Covered
254
- [Chronological bullet list with priority in brackets]
255
- `;
256
- var BATCH_PROMPT_PREFIX = `Summarize these conversation segments.
257
-
258
- Rules for Accuracy:
259
- ` + `1. Use EXACT file paths from extraction data
260
- ` + `2. Status: only mark "done" if there's clear evidence (successful test run, user confirmation)
261
- ` + `3. Quote specific values, don't paraphrase code
262
-
263
- ` + `For EACH segment produce EXACTLY:
264
- ` + `### CHUNK {NUMBER}: {TOPIC_NAME}
265
- ` + `**Priority**: [critical|high|normal|low]
266
- ` + `**Summary**: [2-4 sentences: what happened, errors, code changes with paths]
267
- ` + `**Decisions**: [comma-separated, or "None"]
268
- ` + `**Modified**: [comma-separated paths, or "None"]
269
- ` + `**Deleted**: [comma-separated paths, or "None"]
270
- ` + `**Read**: [comma-separated paths, or "None"]
271
- `;
272
- var ASSEMBLY_PROMPT_PREFIX = `Merge these topic summaries into ONE coherent summary.
273
-
274
- ` + `## IMMUTABLE CONTEXT (do not modify or contradict these facts)
275
- ` + `These are deterministically verified from the original conversation. They take priority over ANY summary content below.
276
-
277
- ` + `Rules:
278
- ` + `1. Preserve ALL critical/high info. Condense normal, minimize low.
279
- ` + `2. Chronological order.
280
- ` + `3. The pre-processed data below is GROUND TRUTH \u2014 trust it over individual summaries.
281
- ` + `4. Files Modified list is deterministically verified \u2014 if a summary says a file was modified but it's NOT in the list above, omit it.
282
- ` + `5. Key Decisions below are verified \u2014 preserve them exactly, do not paraphrase the decision text.
283
- ` + `6. Do NOT fabricate file paths, function names, or error messages not present in the verified data.
284
-
285
- ` + `Format:
286
- ` + `## Goal
287
- [Overall objective]
288
- ` + `## Constraints & Preferences
289
- - [CRITICAL requirements, preferences, constraints]
290
- ` + `## Progress
291
- ### Done
292
- - [x] [Completed tasks with file refs]
293
- ### In Progress
294
- - [ ] [Current work state]
295
- ### Blocked
296
- - [Issues]
297
- ` + `## Key Decisions
298
- - **[Decision]**: [Rationale]
299
- ` + `## Files Modified
300
- - [Verified deterministic list]
301
- ` + `## Files Deleted
302
- - [Verified deterministic deleted paths]
303
- ` + `## Files Read
304
- - [Verified deterministic list]
305
- ` + `## Next Steps
306
- 1. [What should happen next]
307
- ` + `## Critical Context
308
- - [Data, patterns, info needed]
309
- ` + `## Topics Covered
310
- [Chronological bullets with priority]
311
- `;
312
- var LOG_PREFIX = "[smart-compact]";
313
- var BACKUP_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
314
- var FIVE_MINUTES_MS = 5 * 60 * 1000;
315
- var ONE_HOUR_MS = 60 * 60 * 1000;
316
- var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000;
317
- var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
318
- var ID_PREFIX = {
319
- PROJECT: "proj-",
320
- COMPACT_SESSION: "sc-",
321
- MULTI_TOOL_USE_SYNTHETIC: "mtu_",
322
- OPEN_LOOP: "loop-",
323
- DECISION: "decision-",
324
- ERROR: "error-"
325
- };
326
- var TUNING = {
327
- EMA_PREV: 0.7,
328
- EMA_SAMPLE: 0.3,
329
- CALIBRATION_CLAMP_MIN: 0.3,
330
- CALIBRATION_CLAMP_MAX: 3,
331
- CONFIDENCE_HIGH: 0.85,
332
- CONFIDENCE_MEDIUM: 0.8,
333
- CONFIDENCE_LOW: 0.6
334
- };
335
- var TRUNC = {
336
- MESSAGE: 300,
337
- ERROR_DETAIL: 500,
338
- DECISION_SUMMARY: 200,
339
- USER_RESPONSE: 300,
340
- CONSTRAINT_TEXT: 300,
341
- OPEN_LOOP_SUMMARY: 120,
342
- TIMELINE_EVENT: 150,
343
- TIMELINE_ERROR: 100,
344
- SNIPPET: 80,
345
- PREVIEW: 150,
346
- PREVIEW_MID: 200,
347
- DETAIL: 300,
348
- PREVIEW_LONG: 400,
349
- PREVIEW_XL: 500,
350
- PREVIOUS_SUMMARY: 12000,
351
- CONTINUITY_CAPSULE: 4000,
352
- CHUNK_FALLBACK: 180,
353
- DECISION_DETAIL: 60,
354
- TOPIC_LABEL: 100,
355
- PROJ_ID_HASH: 12,
356
- CONV_HASH: 8,
357
- RESULT_GAPS: 5,
358
- SESSION_ID_DISPLAY: 20,
359
- ERROR_SNIPPET: 30,
360
- TIMELINE_DISPLAY: 10,
361
- EXPLORE_RESULTS: 15,
362
- BACKUP_PREVIEW_LINES: 5,
363
- FINGERPRINT_SEG: 2
364
- };
365
- var LIKELY_ERROR_RE = /(?:command not found|no such file|permission denied|syntax error|cannot find|module not found|compilation error|build failed|test failed|^FAIL\b|ERROR:)/i;
366
- var METRICS_BUFFER_MAX = 200;
367
- var RUNTIME_LOG_MAX_BYTES = 5 * 1024 * 1024;
368
- var EXPLORER_SYSTEM_PROMPT = `You are a conversation analyst. You have deterministic extraction data and can query the raw conversation using tools.
369
-
370
- ` + `Your job:
371
- ` + `1. Verify/enrich the extracted boundaries (merge, split, or add as needed)
372
- ` + `2. Identify cross-topic relationships
373
- ` + `3. Find implicit constraints (user tone, frustration, urgency)
374
- ` + `4. Assess completion status accurately
375
- ` + `5. Extract the narrative arc
376
-
377
- ` + `Use tools BEFORE forming conclusions. Finish within 3 tool rounds.
378
-
379
- ` + `After exploration, output ONLY a JSON object (no markdown):
380
- ` + '{"boundaries":[{"afterIndex":N,"topic":"...","priority":"critical|high|normal|low","confidence":0.0-1.0}],"mainGoal":"...","sessionType":"implementation|review|debugging|discussion","enrichedConstraints":[...],"crossReferences":[...],"statusAssessment":{"done":[...],"inProgress":[...],"blocked":[...]},"criticalContext":[...],"keyDecisions":[...]}';
381
-
382
- // src/utils/lru.ts
383
- function lruGet(m, key) {
384
- if (!m.has(key))
385
- return;
386
- const v = m.get(key);
387
- m.delete(key);
388
- m.set(key, v);
389
- return v;
390
- }
391
- function lruSet(m, key, value, max) {
392
- if (m.has(key))
393
- m.delete(key);
394
- m.set(key, value);
395
- while (m.size > max) {
396
- const oldest = m.keys().next().value;
397
- if (oldest === undefined)
398
- break;
399
- m.delete(oldest);
400
- }
401
- }
402
-
403
- // src/utils/tokens.ts
404
- var PROVIDER_MAP = {
405
- "zai-anthropic": {
406
- maxOutputTokens: 8192,
407
- supportsTools: "probe",
408
- jsonReliability: "high",
409
- instructionFollowing: "high",
410
- tokenRatioEstimate: 3.5,
411
- concurrencyLimit: 3,
412
- cacheStrategy: "anthropic",
413
- timeoutMultiplier: 1.2,
414
- singlePassTokenMultiplier: 1,
415
- multimodal: "metadata-only"
416
- },
417
- "kimi-coding": {
418
- maxOutputTokens: 8192,
419
- supportsTools: "probe",
420
- jsonReliability: "high",
421
- instructionFollowing: "high",
422
- tokenRatioEstimate: 3.5,
423
- concurrencyLimit: 2,
424
- cacheStrategy: "anthropic",
425
- timeoutMultiplier: 1.5,
426
- singlePassTokenMultiplier: 0.95,
427
- multimodal: "metadata-only"
428
- },
429
- anthropic: {
430
- maxOutputTokens: 8192,
431
- supportsTools: true,
432
- jsonReliability: "high",
433
- instructionFollowing: "high",
434
- tokenRatioEstimate: 3.5,
435
- concurrencyLimit: 3,
436
- cacheStrategy: "anthropic",
437
- timeoutMultiplier: 1.2,
438
- singlePassTokenMultiplier: 1,
439
- multimodal: "native"
440
- },
441
- openai: {
442
- maxOutputTokens: 16384,
443
- supportsTools: true,
444
- jsonReliability: "high",
445
- instructionFollowing: "high",
446
- tokenRatioEstimate: 4,
447
- concurrencyLimit: 5,
448
- cacheStrategy: "openai",
449
- timeoutMultiplier: 1,
450
- singlePassTokenMultiplier: 1.15,
451
- multimodal: "native"
452
- },
453
- google: {
454
- maxOutputTokens: 8192,
455
- supportsTools: true,
456
- jsonReliability: "high",
457
- instructionFollowing: "high",
458
- tokenRatioEstimate: 3.8,
459
- concurrencyLimit: 3,
460
- cacheStrategy: "openai",
461
- timeoutMultiplier: 1.15,
462
- singlePassTokenMultiplier: 1.1,
463
- multimodal: "native"
464
- },
465
- deepseek: {
466
- maxOutputTokens: 8192,
467
- supportsTools: true,
468
- jsonReliability: "medium",
469
- instructionFollowing: "medium",
470
- tokenRatioEstimate: 3.6,
471
- concurrencyLimit: 2,
472
- cacheStrategy: "none",
473
- timeoutMultiplier: 1.5,
474
- singlePassTokenMultiplier: 0.85,
475
- multimodal: "metadata-only"
476
- },
477
- minimax: {
478
- maxOutputTokens: 4096,
479
- supportsTools: "probe",
480
- jsonReliability: "medium",
481
- instructionFollowing: "medium",
482
- tokenRatioEstimate: 3.8,
483
- concurrencyLimit: 2,
484
- cacheStrategy: "anthropic",
485
- timeoutMultiplier: 1.6,
486
- singlePassTokenMultiplier: 0.8,
487
- multimodal: "metadata-only"
488
- },
489
- "xiaomi-token-plan": {
490
- maxOutputTokens: 8192,
491
- supportsTools: "probe",
492
- jsonReliability: "medium",
493
- instructionFollowing: "medium",
494
- tokenRatioEstimate: 3.3,
495
- concurrencyLimit: 2,
496
- cacheStrategy: "openai",
497
- timeoutMultiplier: 1.35,
498
- singlePassTokenMultiplier: 0.9,
499
- multimodal: "metadata-only"
500
- },
501
- "xiaomi-mimo": {
502
- maxOutputTokens: 8192,
503
- supportsTools: "probe",
504
- jsonReliability: "medium",
505
- instructionFollowing: "medium",
506
- tokenRatioEstimate: 3.3,
507
- concurrencyLimit: 2,
508
- cacheStrategy: "anthropic",
509
- timeoutMultiplier: 1.35,
510
- singlePassTokenMultiplier: 0.9,
511
- multimodal: "metadata-only"
512
- },
513
- crofai: {
514
- maxOutputTokens: 8192,
515
- supportsTools: "probe",
516
- jsonReliability: "medium",
517
- instructionFollowing: "medium",
518
- tokenRatioEstimate: 3.8,
519
- concurrencyLimit: 3,
520
- cacheStrategy: "none",
521
- timeoutMultiplier: 1.2,
522
- singlePassTokenMultiplier: 0.95,
523
- multimodal: "metadata-only"
524
- },
525
- mistral: {
526
- maxOutputTokens: 8192,
527
- supportsTools: true,
528
- jsonReliability: "high",
529
- instructionFollowing: "high",
530
- tokenRatioEstimate: 3.5,
531
- concurrencyLimit: 3,
532
- cacheStrategy: "openai",
533
- timeoutMultiplier: 1.2,
534
- singlePassTokenMultiplier: 1,
535
- multimodal: "metadata-only"
536
- },
537
- xai: {
538
- maxOutputTokens: 8192,
539
- supportsTools: true,
540
- jsonReliability: "medium",
541
- instructionFollowing: "high",
542
- tokenRatioEstimate: 3.8,
543
- concurrencyLimit: 3,
544
- cacheStrategy: "openai",
545
- timeoutMultiplier: 1.2,
546
- singlePassTokenMultiplier: 1,
547
- multimodal: "native"
548
- }
549
- };
550
- var PROVIDER_ALIASES = [
551
- { pattern: /anthropic/i, provider: "anthropic" },
552
- { pattern: /kimi/i, provider: "kimi-coding" },
553
- { pattern: /zai/i, provider: "zai-anthropic" },
554
- { pattern: /openai/i, provider: "openai" },
555
- { pattern: /gpt/i, provider: "openai" },
556
- { pattern: /google|gemini/i, provider: "google" },
557
- { pattern: /deepseek/i, provider: "deepseek" },
558
- { pattern: /minimax/i, provider: "minimax" },
559
- { pattern: /xiaomi-mimo/i, provider: "xiaomi-mimo" },
560
- { pattern: /xiaomi/i, provider: "xiaomi-token-plan" },
561
- { pattern: /crofai/i, provider: "crofai" },
562
- { pattern: /mistral/i, provider: "mistral" },
563
- { pattern: /xai|grok/i, provider: "xai" }
564
- ];
565
- var DEFAULT_CAPS = {
566
- maxOutputTokens: 8192,
567
- supportsTools: "probe",
568
- jsonReliability: "medium",
569
- instructionFollowing: "medium",
570
- tokenRatioEstimate: 3.8,
571
- concurrencyLimit: 2,
572
- cacheStrategy: "none",
573
- timeoutMultiplier: 1.35,
574
- singlePassTokenMultiplier: 0.9,
575
- multimodal: "metadata-only"
576
- };
577
- function getProviderCaps(provider) {
578
- if (PROVIDER_MAP[provider])
579
- return PROVIDER_MAP[provider];
580
- for (const { pattern, provider: key } of PROVIDER_ALIASES) {
581
- if (pattern.test(provider))
582
- return PROVIDER_MAP[key] ?? DEFAULT_CAPS;
583
- }
584
- return DEFAULT_CAPS;
585
- }
586
- class TokenCalibrationStore {
587
- maxEntries;
588
- factors = new Map;
589
- constructor(maxEntries = 128) {
590
- this.maxEntries = maxEntries;
591
- }
592
- clear() {
593
- this.factors.clear();
594
- }
595
- get(provider, model) {
596
- if (!provider)
597
- return 1;
598
- const exactKey = calibrationKey(provider, model);
599
- const exact = lruGet(this.factors, exactKey);
600
- if (exact !== undefined)
601
- return exact;
602
- return lruGet(this.factors, calibrationKey(provider)) ?? 1;
603
- }
604
- calibrate(estimated, actual, provider, model) {
605
- if (actual <= 0 || estimated <= 0 || !provider)
606
- return;
607
- const key = calibrationKey(provider, model);
608
- const prev = lruGet(this.factors, key) ?? 1;
609
- const target = prev * actual / estimated;
610
- const clamped = Math.max(TUNING.CALIBRATION_CLAMP_MIN, Math.min(TUNING.CALIBRATION_CLAMP_MAX, target));
611
- lruSet(this.factors, key, prev * TUNING.EMA_PREV + clamped * TUNING.EMA_SAMPLE, Math.max(1, this.maxEntries));
612
- }
613
- size() {
614
- return this.factors.size;
615
- }
616
- }
617
- var _fallbackCalibration = new TokenCalibrationStore;
618
- function calibrationKey(provider, model) {
619
- return model ? provider + "/" + model : provider + "/*";
620
- }
621
-
622
- // src/utils/type-guards.ts
623
- function isRecord(value) {
624
- return typeof value === "object" && value !== null;
625
- }
626
- function isTextBlock(c) {
627
- return isRecord(c) && c.type === "text" && typeof c.text === "string";
628
- }
629
- function isToolCallBlock(c) {
630
- return isRecord(c) && c.type === "toolCall" && typeof c.name === "string" && isRecord(c.arguments);
631
- }
632
- var KNOWN_METHODS = new Set(["eesv", "single-pass", "heuristic"]);
633
- var KNOWN_PROFILES = new Set(["light", "balanced", "aggressive"]);
634
- var KNOWN_MODES = new Set(["balanced", "aggressive", "fast", "thorough"]);
635
-
636
- // src/utils/file-needles.ts
637
- var GENERIC_BASENAMES = new Set([
638
- "index.ts",
639
- "index.js",
640
- "index.tsx",
641
- "index.jsx",
642
- "types.ts",
643
- "helpers.ts",
644
- "utils.ts",
645
- "main.ts",
646
- "main.js",
647
- "mod.rs",
648
- "lib.rs",
649
- "__init__.py"
650
- ]);
651
- var MIN_BARE_BASENAME_LEN = 5;
652
- function normalizePath(filePath) {
653
- return filePath.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
654
- }
655
- function buildPathNeedles(filePath) {
656
- const parts = normalizePath(filePath).split("/").filter(Boolean);
657
- if (parts.length === 0)
658
- return [];
659
- const needles = [];
660
- const basename = parts[parts.length - 1];
661
- if (!GENERIC_BASENAMES.has(basename) && basename.length >= MIN_BARE_BASENAME_LEN) {
662
- needles.push(basename);
663
- }
664
- for (let j = parts.length - 2;j >= 0; j--) {
665
- needles.push(parts.slice(j).join("/"));
666
- }
667
- return needles;
668
- }
669
- var MAX_INDEXED_SUFFIX_CHARS = 1024;
670
- function buildPathNeedleOwnershipIndex(allPaths) {
671
- const owners = new Map;
672
- const normalizedPaths = allPaths.map(normalizePath);
673
- let hasUnindexedSuffixes = false;
674
- for (const normalized of normalizedPaths) {
675
- const suffixes = new Set([normalized]);
676
- for (let index = 0;index < normalized.length; index++) {
677
- if (normalized[index] !== "/")
678
- continue;
679
- if (normalized.length - index - 1 <= MAX_INDEXED_SUFFIX_CHARS) {
680
- suffixes.add(normalized.slice(index + 1));
681
- } else {
682
- hasUnindexedSuffixes = true;
683
- }
684
- }
685
- for (const suffix of suffixes) {
686
- owners.set(suffix, (owners.get(suffix) ?? 0) + 1);
687
- }
688
- }
689
- return { counts: owners, normalizedPaths, hasUnindexedSuffixes };
690
- }
691
- function buildUniquePathNeedlesFromIndex(filePath, owners) {
692
- return buildPathNeedles(filePath).filter((needle) => {
693
- if (!owners.hasUnindexedSuffixes)
694
- return owners.counts.get(needle) === 1;
695
- let count = 0;
696
- for (const candidate of owners.normalizedPaths) {
697
- if (candidate === needle || candidate.endsWith("/" + needle))
698
- count++;
699
- if (count > 1)
700
- return false;
701
- }
702
- return count === 1;
703
- });
704
- }
705
- var PATH_CANDIDATE_CHAR_RE = /[\w./-]/;
706
- function buildKnownPathReferenceIndex(knownPaths) {
707
- const segmentSuffixes = new Set;
708
- const boundarySuffixes = new Set;
709
- const normalizedPaths = [];
710
- let hasUnindexedSuffixes = false;
711
- for (const path of knownPaths) {
712
- const normalizedPath = normalizePath(path).replace(/^\/+/, "");
713
- if (!normalizedPath)
714
- continue;
715
- normalizedPaths.push(normalizedPath);
716
- segmentSuffixes.add(normalizedPath);
717
- for (let index = 0;index < normalizedPath.length; index++) {
718
- if (normalizedPath[index] === "/") {
719
- if (normalizedPath.length - index - 1 <= MAX_INDEXED_SUFFIX_CHARS) {
720
- segmentSuffixes.add(normalizedPath.slice(index + 1));
721
- } else {
722
- hasUnindexedSuffixes = true;
723
- }
724
- }
725
- if (index > 0 && !PATH_CANDIDATE_CHAR_RE.test(normalizedPath[index - 1])) {
726
- if (normalizedPath.length - index <= MAX_INDEXED_SUFFIX_CHARS) {
727
- boundarySuffixes.add(normalizedPath.slice(index));
728
- } else {
729
- hasUnindexedSuffixes = true;
730
- }
731
- }
732
- }
733
- }
734
- return {
735
- segmentSuffixes,
736
- sortedSegmentSuffixes: [...segmentSuffixes].sort(),
737
- boundarySuffixes,
738
- normalizedPaths,
739
- hasUnindexedSuffixes
740
- };
741
- }
742
- function matchesKnownPathReference(normalizedRef, normalizedPaths) {
743
- const pathShaped = normalizedRef.includes("/");
744
- return normalizedPaths.some((normalizedPath) => {
745
- if (normalizedPath === normalizedRef || normalizedPath.endsWith("/" + normalizedRef))
746
- return true;
747
- if (normalizedPath.endsWith(normalizedRef)) {
748
- const boundary = normalizedPath[normalizedPath.length - normalizedRef.length - 1];
749
- if (boundary && !PATH_CANDIDATE_CHAR_RE.test(boundary))
750
- return true;
751
- }
752
- if (!pathShaped)
753
- return false;
754
- return normalizedPath.startsWith(normalizedRef + "/") || normalizedPath.includes("/" + normalizedRef + "/");
755
- });
756
- }
757
- function sortedHasPrefix(values, prefix) {
758
- let low = 0;
759
- let high = values.length;
760
- while (low < high) {
761
- const middle = low + high >>> 1;
762
- if (values[middle] < prefix)
763
- low = middle + 1;
764
- else
765
- high = middle;
766
- }
767
- return values[low]?.startsWith(prefix) ?? false;
768
- }
769
- function isKnownPathReferenceInIndex(ref, index) {
770
- const normalizedRef = normalizePath(ref).replace(/^\/+/, "");
771
- if (!normalizedRef)
772
- return false;
773
- if (index.segmentSuffixes.has(normalizedRef) || index.boundarySuffixes.has(normalizedRef)) {
774
- return true;
775
- }
776
- if (normalizedRef.includes("/") && sortedHasPrefix(index.sortedSegmentSuffixes, normalizedRef + "/"))
777
- return true;
778
- return index.hasUnindexedSuffixes ? matchesKnownPathReference(normalizedRef, index.normalizedPaths) : false;
779
- }
780
-
781
- // src/domain/tool-semantics.ts
782
- var PATH_KEYS = [
783
- "path",
784
- "file_path",
785
- "filePath",
786
- "filename",
787
- "file",
788
- "target_file",
789
- "file_uri",
790
- "absolute_path"
791
- ];
792
- var PAYLOAD_KEYS = [
793
- "content",
794
- "newText",
795
- "oldText",
796
- "new_str",
797
- "old_str",
798
- "new_string",
799
- "old_string",
800
- "edits",
801
- "patch",
802
- "replacement"
803
- ];
804
- var COMMAND_KEYS = ["command", "cmd", "script"];
805
- function hasPresent(args, keys) {
806
- return keys.some((k) => args[k] != null);
807
- }
808
- function extractToolPath(args) {
809
- if (!args || typeof args !== "object")
810
- return;
811
- const a = args;
812
- for (const k of PATH_KEYS) {
813
- const v = a[k];
814
- if (typeof v === "string" && v.length > 0)
815
- return v;
816
- }
817
- return;
818
- }
819
- function normalizeToolName(name) {
820
- if (typeof name !== "string")
821
- return "";
822
- return name.replace(/^functions[.:/_-]+/i, "").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
823
- }
824
- function nameHas(name, hints) {
825
- const words = name.split("_");
826
- return hints.some((hint) => words.includes(hint));
827
- }
828
- function classifyToolOperation(args, toolName) {
829
- const a = args && typeof args === "object" ? args : {};
830
- const name = normalizeToolName(toolName);
831
- const hasPath = extractToolPath(a) !== undefined;
832
- if (hasPath && hasPresent(a, PAYLOAD_KEYS))
833
- return "mutate";
834
- if (hasPresent(a, COMMAND_KEYS))
835
- return "execute";
836
- if (hasPath && hasPresent(a, ["text"]) && nameHas(name, ["write", "edit", "patch", "replace", "append", "create", "update", "insert"]))
837
- return "mutate";
838
- if (hasPath && nameHas(name, ["delete", "remove", "unlink"]))
839
- return "delete";
840
- if (hasPresent(a, ["pattern", "query", "glob"]) || nameHas(name, ["grep", "search", "find", "glob", "rg"]))
841
- return "search";
842
- if (nameHas(name, ["list", "ls", "tree"]))
843
- return "list";
844
- if (hasPath || nameHas(name, ["read"]))
845
- return "read";
846
- return "unknown";
847
- }
848
-
849
- // src/utils/logger.ts
850
- var DEBUG = process.env.DEBUG?.includes("smart-compact") ?? false;
851
- function ts() {
852
- return new Date().toISOString();
853
- }
854
- function warn(msg, err) {
855
- const detail = err instanceof Error ? err.message : err ?? "";
856
- console.error(ts() + " " + LOG_PREFIX + " " + msg + (detail ? ": " + detail : ""));
857
- }
858
-
859
- // src/infra/fs.ts
860
- import fs from "fs";
861
- function readJsonlTail(target, limit, maxBytes = 512 * 1024) {
862
- if (limit <= 0 || !fs.existsSync(target))
863
- return [];
864
- const stat = fs.statSync(target);
865
- const length = Math.min(stat.size, maxBytes);
866
- const buffer = Buffer.alloc(length);
867
- const fd = fs.openSync(target, "r");
868
- try {
869
- fs.readSync(fd, buffer, 0, length, stat.size - length);
870
- } finally {
871
- fs.closeSync(fd);
872
- }
873
- let text = buffer.toString("utf8");
874
- if (stat.size > length) {
875
- const newline = text.indexOf(`
876
- `);
877
- text = newline >= 0 ? text.slice(newline + 1) : "";
878
- }
879
- const values = [];
880
- for (const line of text.split(`
881
- `)) {
882
- if (!line)
883
- continue;
884
- try {
885
- values.push(JSON.parse(line));
886
- } catch {}
887
- }
888
- return values.slice(-limit);
889
- }
890
-
891
- // src/infra/paths.ts
892
- import path from "path";
893
- import os from "os";
894
- function home() {
895
- const configured = process.env.HOME?.trim() || process.env.USERPROFILE?.trim();
896
- return configured || os.homedir();
897
- }
898
- function piAgentDir() {
899
- return path.join(home(), ".pi", "agent");
900
- }
901
- function cacheDir() {
902
- return path.join(piAgentDir(), ".cache");
903
- }
904
- function smartCompactCacheDir() {
905
- return path.join(cacheDir(), "smart-compact");
906
- }
907
- function metricsLogFile() {
908
- return path.join(cacheDir(), "compact-metrics.jsonl");
909
- }
910
- function damageReportsFile() {
911
- return path.join(smartCompactCacheDir(), "damage-reports.jsonl");
912
- }
913
- // src/utils/helpers.ts
914
- function normalizeFactKey(text) {
915
- return text.toLowerCase().replace(/\s+/g, " ").trim();
916
- }
917
-
918
- // src/utils/file-ref-detect.ts
919
- var CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|rs|py|go|java|rb|cs|cpp|c|h|hpp|swift|kt|scala|php|css|scss|html|json|yaml|yml|toml|md|mdx|sh|sql|tf|ini|env|lock|gradle|xml)$/i;
920
- var VERSION_RE = /^v?\d+(?:\.\d+)+(?:[-+][\w.-]+)?$/i;
921
- function isAsciiWordCode(code) {
922
- return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code === 95 || code >= 97 && code <= 122;
923
- }
924
- function isCandidateCode(code) {
925
- return isAsciiWordCode(code) || code === 45 || code === 46 || code === 47;
926
- }
927
- function isLikelyFileRef(candidate) {
928
- if (candidate.startsWith("//") || VERSION_RE.test(candidate))
929
- return false;
930
- if (candidate.includes("/")) {
931
- const last = candidate.split("/").pop() ?? "";
932
- return last.length > 0 && !VERSION_RE.test(last);
933
- }
934
- return CODE_EXT_RE.test(candidate);
935
- }
936
- function extractFileRefs(summary) {
937
- const refs = [];
938
- let cursor = 0;
939
- while (cursor < summary.length) {
940
- while (cursor < summary.length && !isCandidateCode(summary.charCodeAt(cursor)))
941
- cursor++;
942
- const runStart = cursor;
943
- while (cursor < summary.length && isCandidateCode(summary.charCodeAt(cursor)))
944
- cursor++;
945
- const runEnd = cursor;
946
- let extensionDot = -1;
947
- for (let index = runStart + 1;index + 1 < runEnd; index++) {
948
- if (summary.charCodeAt(index) === 46 && isAsciiWordCode(summary.charCodeAt(index + 1))) {
949
- extensionDot = index;
950
- }
951
- }
952
- if (extensionDot < 0)
953
- continue;
954
- let matchEnd = extensionDot + 2;
955
- while (matchEnd < runEnd && isAsciiWordCode(summary.charCodeAt(matchEnd))) {
956
- matchEnd++;
957
- }
958
- const candidate = summary.slice(runStart, matchEnd);
959
- if (/[\\/]/.test(summary[matchEnd] ?? ""))
960
- continue;
961
- if (isLikelyFileRef(candidate))
962
- refs.push(candidate);
963
- }
964
- return refs;
965
- }
966
-
967
- // src/domain/summary-parse.ts
968
- import { createHash } from "crypto";
969
-
970
- // src/domain/summary-schema.ts
971
- function classifyHeading(raw) {
972
- const text = raw.replace(/^#+\s*/, "").replace(/[:\s]+$/, "").trim().toLowerCase();
973
- if (!text)
974
- return "unknown";
975
- if (text === "goal" || text === "goals" || text === "objective" || text === "objectives")
976
- return "goal";
977
- if (text.startsWith("constraint") || text.includes("preference"))
978
- return "constraints";
979
- if (text === "progress" || text === "status")
980
- return "progress";
981
- if (text.includes("key decision") || text === "decisions")
982
- return "decisions";
983
- if (text.includes("file") && text.includes("modif"))
984
- return "files-modified";
985
- if (text.includes("file") && (text.includes("read") || text.includes("viewed")))
986
- return "files-read";
987
- if (text.includes("file") && (text.includes("delet") || text.includes("remov")))
988
- return "files-deleted";
989
- if (text.includes("next step") || text === "next actions")
990
- return "next-steps";
991
- if (text.includes("critical context") || text === "important context")
992
- return "critical-context";
993
- if (text === "topics" || text.includes("topics covered"))
994
- return "topics";
995
- if (text.includes("open loop") || text.includes("unresolved"))
996
- return "open-loops";
997
- if (text.includes("changes since") || text === "changes")
998
- return "changes";
999
- if (text.includes("verification"))
1000
- return "verification-note";
1001
- return "unknown";
1002
- }
1003
-
1004
- // src/domain/summary-parse.ts
1005
- var HEADING_RE = /^(#{1,3})\s+(.+?)\s*$/;
1006
- function summaryEvidenceLine(value, maxLength) {
1007
- return value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, " ").replace(/\s+/g, " ").trim().replace(/^(?:(?:#{1,6}|[-*+]|>)\s+)+/, "").slice(0, maxLength).trim();
1008
- }
1009
- function summaryPathLine(value) {
1010
- return JSON.stringify(value);
1011
- }
1012
- function compactPathLine(value, maxLength, digest) {
1013
- const minimal = JSON.stringify("#" + digest);
1014
- if (minimal.length >= maxLength)
1015
- return minimal;
1016
- const chars = Array.from(value.replace(/\\/g, "/"));
1017
- let low = 0;
1018
- let high = chars.length;
1019
- let best = minimal;
1020
- while (low <= high) {
1021
- const length = Math.floor((low + high) / 2);
1022
- const candidate = JSON.stringify("\u2026/" + chars.slice(-length).join("") + "#" + digest);
1023
- if (candidate.length <= maxLength) {
1024
- best = candidate;
1025
- low = length + 1;
1026
- } else {
1027
- high = length - 1;
1028
- }
1029
- }
1030
- return best;
1031
- }
1032
- function buildSummaryPathEvidence(paths, budgetTokens = PROFILES.balanced.summaryBudgetTokens) {
1033
- const unique = Array.from(new Set(paths.filter(Boolean)));
1034
- if (!unique.length)
1035
- return new Map;
1036
- const full = unique.map((path) => [path, summaryPathLine(path)]);
1037
- const minimumPerLine = JSON.stringify("#" + "x".repeat(12)).length + 3;
1038
- const budgetChars = Math.max(unique.length * minimumPerLine, Math.min(20000, Math.max(4000, Math.floor(budgetTokens * 2))));
1039
- if (full.reduce((total, [, line]) => total + line.length + 3, 0) <= budgetChars) {
1040
- return new Map(full);
1041
- }
1042
- const digests = new Map;
1043
- const owners = new Map;
1044
- for (const path of unique) {
1045
- const fullDigest = createHash("sha256").update(path).digest("base64url");
1046
- let digest = fullDigest.slice(0, 12);
1047
- const owner = owners.get(digest);
1048
- if (owner && owner !== path) {
1049
- digest = fullDigest;
1050
- digests.set(owner, createHash("sha256").update(owner).digest("base64url"));
1051
- }
1052
- owners.set(digest, path);
1053
- digests.set(path, digest);
1054
- }
1055
- const perPath = Math.max(JSON.stringify("#" + "x".repeat(12)).length, Math.floor((budgetChars - unique.length * 3) / unique.length));
1056
- return new Map(unique.map((path) => [
1057
- path,
1058
- compactPathLine(path, perPath, digests.get(path) ?? "")
1059
- ]));
1060
- }
1061
- function mergeBodies(first, second) {
1062
- const seen = new Set;
1063
- return [first, second].filter(Boolean).flatMap((body) => body.split(`
1064
- `)).filter((line) => seen.has(line) ? false : (seen.add(line), true)).join(`
1065
- `).trim();
1066
- }
1067
- function parseSummary(markdown) {
1068
- const sections = [];
1069
- const lines = markdown.split(`
1070
- `);
1071
- let currentHeading = "";
1072
- let currentKind = "unknown";
1073
- let bodyLines = [];
1074
- let fence = null;
1075
- let started = false;
1076
- const flush = () => {
1077
- if (!started)
1078
- return;
1079
- const body = bodyLines.join(`
1080
- `).trim();
1081
- const existing = currentKind === "unknown" ? undefined : sections.find((s) => s.kind === currentKind);
1082
- if (existing)
1083
- existing.body = mergeBodies(existing.body, body);
1084
- else
1085
- sections.push({
1086
- kind: currentKind,
1087
- heading: currentHeading.trim(),
1088
- body
1089
- });
1090
- };
1091
- for (const line of lines) {
1092
- const fenceMatch = line.match(/^\s{0,3}(`{3,}|~{3,})(.*)$/);
1093
- if (fenceMatch) {
1094
- const marker = fenceMatch[1][0];
1095
- const markerLength = fenceMatch[1].length;
1096
- if (!fence) {
1097
- fence = { marker, length: markerLength };
1098
- } else if (marker === fence.marker && markerLength >= fence.length && !fenceMatch[2].trim()) {
1099
- fence = null;
1100
- }
1101
- if (started)
1102
- bodyLines.push(line);
1103
- continue;
1104
- }
1105
- if (!fence) {
1106
- const heading = line.match(HEADING_RE);
1107
- if (heading) {
1108
- const kind = classifyHeading(heading[2]);
1109
- if (heading[1].length <= 2 || kind !== "unknown") {
1110
- flush();
1111
- currentHeading = "## " + heading[2].trim();
1112
- currentKind = kind;
1113
- bodyLines = [];
1114
- started = true;
1115
- continue;
1116
- }
1117
- }
1118
- }
1119
- if (started)
1120
- bodyLines.push(line);
1121
- }
1122
- flush();
1123
- return { sections };
1124
- }
1125
- function findSection(summary, kind) {
1126
- const parsed = typeof summary === "string" ? parseSummary(summary) : summary;
1127
- return parsed.sections.find((s) => s.kind === kind);
1128
- }
1129
-
1130
- // src/utils/extraction.ts
1131
- function nestedToolCallId(wrapperId, messageIndex, toolIndex, nestedId) {
1132
- return typeof nestedId === "string" ? nestedId : wrapperId ? wrapperId + "_" + toolIndex : ID_PREFIX.MULTI_TOOL_USE_SYNTHETIC + messageIndex + "_" + toolIndex;
1133
- }
1134
- function flattenToolCallBlock(b) {
1135
- if (!isToolCallBlock(b))
1136
- return [];
1137
- if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
1138
- return b.arguments.tool_uses.map((u) => {
1139
- const recipient = u?.recipient_name ?? "";
1140
- return {
1141
- name: recipient.replace(/^functions\./, ""),
1142
- id: u?.id ?? undefined,
1143
- arguments: u?.parameters ?? {}
1144
- };
1145
- });
1146
- }
1147
- return [{ name: b.name, id: b.id, arguments: b.arguments }];
1148
- }
1149
- function extractText(content) {
1150
- if (typeof content === "string")
1151
- return content;
1152
- if (!Array.isArray(content))
1153
- return "";
1154
- return content.map((b) => {
1155
- if (typeof b === "string")
1156
- return b;
1157
- if (isTextBlock(b))
1158
- return b.text;
1159
- return "";
1160
- }).join("");
1161
- }
1162
- function buildToolCallIndex(msgs) {
1163
- const idx = new Map;
1164
- for (let i = 0;i < msgs.length; i++) {
1165
- const m = msgs[i];
1166
- if (m.role !== "assistant")
1167
- continue;
1168
- const blocks = Array.isArray(m.content) ? m.content : [];
1169
- for (const b of blocks) {
1170
- if (!isToolCallBlock(b))
1171
- continue;
1172
- if (b.id) {
1173
- idx.set(b.id, { name: b.name, arguments: b.arguments, msgIndex: i });
1174
- }
1175
- if (b.name === "multi_tool_use.parallel" && Array.isArray(b.arguments?.tool_uses)) {
1176
- const nested = flattenToolCallBlock(b);
1177
- for (let t = 0;t < nested.length; t++) {
1178
- const tool = nested[t];
1179
- const id = nestedToolCallId(b.id, i, t, tool.id);
1180
- idx.set(id, {
1181
- name: tool.name,
1182
- arguments: tool.arguments,
1183
- msgIndex: i
1184
- });
1185
- }
1186
- }
1187
- }
1188
- }
1189
- return idx;
1190
- }
1191
- var CONSTRAINT_PATTERNS = [
1192
- {
1193
- re: /\b(?:must|need|require|has to|important)\b.*\b(?:be|use|have|include|support)\b/i,
1194
- cat: "requirement",
1195
- conf: TUNING.CONFIDENCE_HIGH
1196
- },
1197
- {
1198
- re: /\b(?:don't|never|avoid|shouldn't|must not|do not|no\s+(?:need|want))\b/i,
1199
- cat: "prohibition",
1200
- conf: TUNING.CONFIDENCE_MEDIUM
1201
- },
1202
- {
1203
- re: /\b(?:prefer|like|want|would rather|should)\b.*\b(?:use|be|have|with)\b/i,
1204
- cat: "preference",
1205
- conf: TUNING.CONFIDENCE_LOW
1206
- },
1207
- {
1208
- re: /(?<![A-Za-z0-9_])(?:yapma|kullanma|sak\u0131n|sak\u0131nha|asla(?:\s+(?:kullanma|yapma|getirme))?|bunu yapma)(?![A-Za-z0-9_])/iu,
1209
- cat: "prohibition",
1210
- conf: TUNING.CONFIDENCE_MEDIUM
1211
- },
1212
- {
1213
- re: /(?<![A-Za-z0-9_])(?:kritik|kritikal|\u00F6nemli|onemli|\u015Fart|sart|zorunlu|\u015Fart ko\u015Ful|\u00F6nemli \u015Fart|kesinlikle|kesinlikle \u015Fart|b\u00F6yle olsun|b\u00F6yle yap\u0131n|\u015F\u00F6yle olsun|\u015F\u00F6yle yap\u0131n)(?![A-Za-z0-9_])/iu,
1214
- cat: "requirement",
1215
- conf: TUNING.CONFIDENCE_MEDIUM
1216
- },
1217
- {
1218
- re: /(?<![A-Za-z0-9_])(?:tercih|isterim|olsun|kullanal\u0131m|yapal\u0131m|istiyorum)(?![A-Za-z0-9_])/iu,
1219
- cat: "preference",
1220
- conf: TUNING.CONFIDENCE_LOW
1221
- }
1222
- ];
1223
- function isDiagnosticConstraintText(text) {
1224
- const candidate = text.replace(/^\s*[-*]\s+/, "").trim();
1225
- return /^(?:\[[^\]]+\]\s*)?(?:npm\s+(?:error|warn|notice|audit|verbose|info)\b|(?:rg|grep):|command exited\b)/i.test(candidate);
1226
- }
1227
- var OWN_OUTPUT_RE = /(?:verification stopped apply|yield check stopped apply|smart compact failed|do not bypass verification|conversation unchanged|review \/smart-compact metrics|restart pi with debug=smart-compact)/i;
1228
- var COMPLETED_CHECKLIST_RE = /^\[[xX\u2713\u2714]\]\s*\S/;
1229
- function isNonLiveConstraintText(text) {
1230
- const candidate = text.replace(/^\s*[-*]\s+/, "").trim();
1231
- return isDiagnosticConstraintText(candidate) || isCompactionStatusText(candidate) || OWN_OUTPUT_RE.test(candidate) || COMPLETED_CHECKLIST_RE.test(candidate);
1232
- }
1233
- function isCompactionStatusText(text) {
1234
- const candidate = summaryEvidenceLine(text, TRUNC.MESSAGE).replace(/^["'`]+/, "").trim();
1235
- return /^(?:EESV Compact\b|Smart compact (?:skipped|prepared|run finished)\b|Auto-compacting\b)/i.test(candidate);
1236
- }
1237
-
1238
- // src/infra/clock.ts
1239
- var systemClock = {
1240
- now: () => Date.now()
1241
- };
1242
-
1243
- // src/infra/llm-client.ts
1244
- var _complete = null;
1245
- var _completeSimple = null;
1246
- var _stream = null;
1247
- var _streamSimple = null;
1248
- async function resolveComplete() {
1249
- if (_complete)
1250
- return _complete;
1251
- const mod = await import("@earendil-works/pi-ai/compat");
1252
- const fn = mod.complete;
1253
- if (typeof fn !== "function")
1254
- throw new Error("smart-compact: pi-ai /compat did not export complete()");
1255
- _complete = fn;
1256
- return fn;
1257
- }
1258
- async function resolveCompleteSimple() {
1259
- if (_completeSimple)
1260
- return _completeSimple;
1261
- const mod = await import("@earendil-works/pi-ai/compat");
1262
- const fn = mod.completeSimple;
1263
- if (typeof fn !== "function")
1264
- throw new Error("smart-compact: pi-ai /compat did not export completeSimple()");
1265
- _completeSimple = fn;
1266
- return fn;
1267
- }
1268
- async function resolveStream() {
1269
- if (_stream)
1270
- return _stream;
1271
- const mod = await import("@earendil-works/pi-ai/compat");
1272
- if (typeof mod.stream !== "function")
1273
- throw new Error("smart-compact: pi-ai /compat did not export stream()");
1274
- _stream = mod.stream;
1275
- return _stream;
1276
- }
1277
- async function resolveStreamSimple() {
1278
- if (_streamSimple)
1279
- return _streamSimple;
1280
- const mod = await import("@earendil-works/pi-ai/compat");
1281
- if (typeof mod.streamSimple !== "function")
1282
- throw new Error("smart-compact: pi-ai /compat did not export streamSimple()");
1283
- _streamSimple = mod.streamSimple;
1284
- return _streamSimple;
1285
- }
1286
- function isChatGptCodex(model) {
1287
- if (model.api !== "openai-codex-responses")
1288
- return false;
1289
- return !model.baseUrl || model.baseUrl.includes("chatgpt.com");
1290
- }
1291
- function withCodexWireLimit(model, opts) {
1292
- if (model.api !== "openai-codex-responses" || isChatGptCodex(model) || !opts.maxTokens)
1293
- return opts;
1294
- const previous = opts.onPayload;
1295
- return {
1296
- ...opts,
1297
- onPayload: async (payload, requestModel) => {
1298
- const transformed = await previous?.(payload, requestModel);
1299
- const body = transformed ?? payload;
1300
- return body && typeof body === "object" ? {
1301
- ...body,
1302
- max_output_tokens: opts.maxTokens
1303
- } : body;
1304
- }
1305
- };
1306
- }
1307
- function resolveCodexWatchdogMs(maxTokens, configuredMs = 0) {
1308
- if (configuredMs > 0)
1309
- return configuredMs;
1310
- return Math.min(90000, Math.max(15000, 1e4 + (maxTokens ?? 4096) * 8));
1311
- }
1312
- function streamedChars(event) {
1313
- if (event.type === "text_delta" || event.type === "thinking_delta" || event.type === "toolcall_delta") {
1314
- return event.delta.length;
1315
- }
1316
- return 0;
1317
- }
1318
- function assertSuccessful(message) {
1319
- if (message.stopReason === "error" || message.stopReason === "aborted") {
1320
- throw new Error(message.errorMessage || "LLM request failed");
1321
- }
1322
- return message;
1323
- }
1324
- function resolveProviderWatchdogMs(provider, maxTokens, configuredMs = 0) {
1325
- const multiplier = provider && configuredMs <= 0 ? getProviderCaps(provider).timeoutMultiplier : 1;
1326
- return Math.round(resolveCodexWatchdogMs(maxTokens, configuredMs) * multiplier);
1327
- }
1328
- async function withProviderDeadline(opts, invoke, provider) {
1329
- if (opts.signal?.aborted)
1330
- throw new Error("LLM request aborted before dispatch");
1331
- const controller = new AbortController;
1332
- const watchdogMs = resolveProviderWatchdogMs(provider, opts.maxTokens, opts.codexWatchdogMs);
1333
- const abort = Promise.withResolvers();
1334
- const abortFromCaller = () => {
1335
- controller.abort(opts.signal?.reason);
1336
- abort.reject(new Error("LLM request aborted by caller"));
1337
- };
1338
- opts.signal?.addEventListener("abort", abortFromCaller, { once: true });
1339
- const timeout = Promise.withResolvers();
1340
- const timer = setTimeout(() => {
1341
- controller.abort("provider-watchdog");
1342
- timeout.reject(new Error("Provider watchdog stopped generation after " + watchdogMs + "ms"));
1343
- }, watchdogMs);
1344
- if (typeof timer === "object" && "unref" in timer)
1345
- timer.unref();
1346
- try {
1347
- return await Promise.race([
1348
- invoke({ ...opts, signal: controller.signal }),
1349
- abort.promise,
1350
- timeout.promise
1351
- ]);
1352
- } finally {
1353
- clearTimeout(timer);
1354
- opts.signal?.removeEventListener("abort", abortFromCaller);
1355
- }
1356
- }
1357
- async function completeChatGptCodex(model, body, opts) {
1358
- const controller = new AbortController;
1359
- const watchdogMs = resolveCodexWatchdogMs(opts.maxTokens, opts.codexWatchdogMs);
1360
- let watchdogReason = null;
1361
- let visibleChars = 0;
1362
- const abortFromCaller = () => controller.abort(opts.signal?.reason);
1363
- opts.signal?.addEventListener("abort", abortFromCaller, { once: true });
1364
- if (opts.signal?.aborted)
1365
- abortFromCaller();
1366
- const timer = setTimeout(() => {
1367
- watchdogReason = "time";
1368
- controller.abort("codex-watchdog");
1369
- }, watchdogMs);
1370
- timer.unref?.();
1371
- try {
1372
- const limited = { ...opts, signal: controller.signal };
1373
- const events = opts.reasoning === undefined ? (await resolveStream())(model, body, limited) : (await resolveStreamSimple())(model, body, limited);
1374
- let final;
1375
- for await (const event of events) {
1376
- visibleChars += streamedChars(event);
1377
- if (!watchdogReason && opts.maxTokens && visibleChars > opts.maxTokens * 3) {
1378
- watchdogReason = "visible-output";
1379
- controller.abort("codex-visible-output-cap");
1380
- }
1381
- if (event.type === "done")
1382
- final = event.message;
1383
- else if (event.type === "error")
1384
- final = event.error;
1385
- }
1386
- if (watchdogReason) {
1387
- throw new Error("Codex " + watchdogReason + " watchdog stopped generation after " + watchdogMs + "ms / " + visibleChars + " streamed chars");
1388
- }
1389
- if (!final)
1390
- throw new Error("Codex stream ended without a final message");
1391
- return assertSuccessful(final);
1392
- } finally {
1393
- clearTimeout(timer);
1394
- opts.signal?.removeEventListener("abort", abortFromCaller);
1395
- }
1396
- }
1397
- var rawLlmClient = {
1398
- complete: async (model, body, originalOpts) => {
1399
- const opts = withCodexWireLimit(model, originalOpts);
1400
- return withProviderDeadline(opts, async (bounded) => {
1401
- if (isChatGptCodex(model))
1402
- return completeChatGptCodex(model, body, bounded);
1403
- const response = bounded.reasoning === undefined ? await (await resolveComplete())(model, body, bounded) : await (await resolveCompleteSimple())(model, body, bounded);
1404
- return assertSuccessful(response);
1405
- }, model.provider);
1406
- }
1407
- };
1408
- var defaultLlmClient = rawLlmClient;
1409
- var _client = defaultLlmClient;
1410
- function getLlmClient() {
1411
- return _client;
1412
- }
1413
-
1414
- // src/infra/services.ts
1415
- import crypto from "crypto";
1416
-
1417
- // src/domain/scrub.ts
1418
- var SECRET_PATTERNS = [
1419
- {
1420
- kind: "private-key",
1421
- regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g
1422
- },
1423
- { kind: "aws-access-key", regex: /\bAKIA[0-9A-Z]{16}\b/g },
1424
- { kind: "google-api-key", regex: /\bAIza[0-9A-Za-z_-]{30,}\b/g },
1425
- { kind: "stripe-key", regex: /\b[rs]k_(?:live|test)_[0-9A-Za-z]{16,}\b/g },
1426
- { kind: "gitlab-token", regex: /\bglpat-[0-9A-Za-z_-]{20,}\b/g },
1427
- { kind: "npm-token", regex: /\bnpm_[0-9A-Za-z]{30,}\b/g },
1428
- { kind: "github-token", regex: /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g },
1429
- { kind: "api-key", regex: /\bsk-(?:ant-)?[A-Za-z0-9_-]{20,}\b/g },
1430
- { kind: "slack-token", regex: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
1431
- {
1432
- kind: "jwt",
1433
- regex: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g
1434
- },
1435
- {
1436
- kind: "bearer-token",
1437
- regex: /\bBearer\s+[A-Za-z0-9._~+/-]{12,}=*/gi,
1438
- replacement: () => "Bearer [REDACTED:bearer-token]"
1439
- },
1440
- {
1441
- kind: "connection-password",
1442
- regex: /\b([a-z][a-z0-9+.-]*:\/\/[^:\s/@]+:)[^@\s/]+(@)/gi,
1443
- replacement: (prefix, suffix) => prefix + "[REDACTED:password]" + suffix
1444
- },
1445
- {
1446
- kind: "credential",
1447
- regex: /\b((?:[A-Za-z0-9]+[_-])*(?:api[_-]?key|access[_-]?token|auth[_-]?token|token|password|passwd|secret(?:[_-]?(?:access)?[_-]?key)?|client[_-]?secret)(?:[_-][A-Za-z0-9]+)*)\s*([:=])\s*["']?([^\s"']{16,})["']?/gi,
1448
- replacement: (name, separator, value, match) => /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(value) ? match : name + separator + "[REDACTED:credential]"
1449
- }
1450
- ];
1451
- function passesLuhn(candidate) {
1452
- const digits = candidate.replace(/\D/g, "");
1453
- if (digits.length < 13 || digits.length > 19)
1454
- return false;
1455
- if (/^(\d)\1+$/.test(digits))
1456
- return false;
1457
- let sum = 0, double = false;
1458
- for (let i = digits.length - 1;i >= 0; i--) {
1459
- let d = digits.charCodeAt(i) - 48;
1460
- if (double) {
1461
- d *= 2;
1462
- if (d > 9)
1463
- d -= 9;
1464
- }
1465
- sum += d;
1466
- double = !double;
1467
- }
1468
- return sum % 10 === 0;
1469
- }
1470
- var PII_PATTERNS = [
1471
- { kind: "email", regex: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi },
1472
- {
1473
- kind: "payment-card",
1474
- regex: /\b(?:\d[ -]*?){13,19}\b/g,
1475
- replacement: (candidate) => passesLuhn(candidate) ? "[REDACTED:payment-card]" : candidate
1476
- },
1477
- { kind: "phone", regex: /(?<![\w.])(?:\+?\d[\d ()-]{8,}\d)(?![\w.])/g }
1478
- ];
1479
- function redact(text, patterns) {
1480
- const counts = new Map;
1481
- let value = text;
1482
- for (const pattern of patterns) {
1483
- value = value.replace(pattern.regex, (...args) => {
1484
- const match = String(args[0]);
1485
- let replacement = "[REDACTED:" + pattern.kind + "]";
1486
- if (pattern.replacement) {
1487
- const groups = args.slice(1, -2).map(String);
1488
- replacement = pattern.replacement(...groups, match);
1489
- }
1490
- if (replacement === match)
1491
- return match;
1492
- counts.set(pattern.kind, (counts.get(pattern.kind) ?? 0) + 1);
1493
- return replacement;
1494
- });
1495
- }
1496
- return {
1497
- value,
1498
- findings: [...counts].map(([kind, count]) => ({ kind, count }))
1499
- };
1500
- }
1501
- function mergeFindings(target, findings) {
1502
- for (const finding of findings)
1503
- target.set(finding.kind, (target.get(finding.kind) ?? 0) + finding.count);
1504
- }
1505
- var SECRET_KEY_NAMES = {
1506
- api_key: true,
1507
- apikey: true,
1508
- access_token: true,
1509
- auth_token: true,
1510
- authorization: true,
1511
- password: true,
1512
- passwd: true,
1513
- secret: true,
1514
- secret_key: true,
1515
- secret_access_key: true,
1516
- client_secret: true,
1517
- private_key: true,
1518
- database_url: true,
1519
- connection_string: true,
1520
- token: true,
1521
- refresh_token: true,
1522
- session_token: true,
1523
- credential: true,
1524
- credentials: true,
1525
- cookie: true,
1526
- set_cookie: true,
1527
- otp: true,
1528
- one_time_password: true,
1529
- passcode: true
1530
- };
1531
- function normalizeObjectKey(key) {
1532
- return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
1533
- }
1534
- function isSecretBearingKey(key) {
1535
- const normalized = normalizeObjectKey(key);
1536
- if (SECRET_KEY_NAMES[normalized])
1537
- return true;
1538
- return /(?:^|_)(?:api_key|access_token|auth_token|password|passwd|secret_access_key|client_secret|private_key|refresh_token|session_token|one_time_password|passcode)(?:_|$)/.test(normalized);
1539
- }
1540
-
1541
- class SecretScrubber {
1542
- secretsEnabled;
1543
- piiEnabled;
1544
- total = 0;
1545
- constructor(secretsEnabled = true, piiEnabled = false) {
1546
- this.secretsEnabled = secretsEnabled;
1547
- this.piiEnabled = piiEnabled;
1548
- }
1549
- scrubText(text) {
1550
- let value = text;
1551
- const findings = new Map;
1552
- if (this.secretsEnabled) {
1553
- const result = redact(value, SECRET_PATTERNS);
1554
- value = result.value;
1555
- mergeFindings(findings, result.findings);
1556
- }
1557
- if (this.piiEnabled) {
1558
- const result = redact(value, PII_PATTERNS);
1559
- value = result.value;
1560
- mergeFindings(findings, result.findings);
1561
- }
1562
- const merged = [...findings].map(([kind, count]) => ({ kind, count }));
1563
- this.total += merged.reduce((sum, finding) => sum + finding.count, 0);
1564
- return { value, findings: merged };
1565
- }
1566
- scrubValue(input) {
1567
- const findings = new Map;
1568
- const seen = new WeakMap;
1569
- const recordCredential = () => {
1570
- findings.set("credential", (findings.get("credential") ?? 0) + 1);
1571
- this.total++;
1572
- };
1573
- const visit = (value) => {
1574
- if (typeof value === "string") {
1575
- const result = this.scrubText(value);
1576
- mergeFindings(findings, result.findings);
1577
- return result.value;
1578
- }
1579
- if (value == null || typeof value !== "object")
1580
- return value;
1581
- const cached = seen.get(value);
1582
- if (cached !== undefined)
1583
- return cached;
1584
- if (Array.isArray(value)) {
1585
- const output = [];
1586
- seen.set(value, output);
1587
- for (const item of value)
1588
- output.push(visit(item));
1589
- return output;
1590
- }
1591
- const output = {};
1592
- seen.set(value, output);
1593
- for (const [key, item] of Object.entries(value)) {
1594
- const carriesSecret = typeof item === "string" && item.length >= 8;
1595
- if (this.secretsEnabled && isSecretBearingKey(key) && carriesSecret) {
1596
- output[key] = "[REDACTED:credential]";
1597
- recordCredential();
1598
- } else {
1599
- output[key] = visit(item);
1600
- }
1601
- }
1602
- return output;
1603
- };
1604
- const value = visit(input);
1605
- return {
1606
- value,
1607
- findings: [...findings].map(([kind, count]) => ({ kind, count }))
1608
- };
1609
- }
1610
- count() {
1611
- return this.total;
1612
- }
1613
- }
1614
-
1615
- // src/infra/services.ts
1616
- class ToolSupportCache {
1617
- ttlMs;
1618
- maxEntries;
1619
- entries = new Map;
1620
- constructor(ttlMs = ONE_HOUR_MS, maxEntries = 128) {
1621
- this.ttlMs = ttlMs;
1622
- this.maxEntries = maxEntries;
1623
- }
1624
- get(key, now) {
1625
- const entry = this.entries.get(key);
1626
- if (!entry)
1627
- return;
1628
- if (now - entry.timestamp > this.ttlMs) {
1629
- this.entries.delete(key);
1630
- return;
1631
- }
1632
- this.entries.delete(key);
1633
- this.entries.set(key, entry);
1634
- return entry.result;
1635
- }
1636
- set(key, value, now) {
1637
- this.entries.delete(key);
1638
- this.entries.set(key, { result: value, timestamp: now });
1639
- while (this.entries.size > Math.max(1, this.maxEntries)) {
1640
- const oldest = this.entries.keys().next().value;
1641
- if (oldest === undefined)
1642
- break;
1643
- this.entries.delete(oldest);
1644
- }
1645
- }
1646
- clear() {
1647
- this.entries.clear();
1648
- }
1649
- size() {
1650
- return this.entries.size;
1651
- }
1652
- }
1653
-
1654
- class MetricsSink {
1655
- buf = [];
1656
- maxEntries;
1657
- constructor(maxEntries = METRICS_BUFFER_MAX) {
1658
- this.maxEntries = maxEntries;
1659
- }
1660
- record(metric) {
1661
- this.buf.push(metric);
1662
- if (this.buf.length > this.maxEntries) {
1663
- this.buf.splice(0, this.buf.length - Math.floor(this.maxEntries / 2));
1664
- }
1665
- }
1666
- snapshot() {
1667
- return [...this.buf];
1668
- }
1669
- clear() {
1670
- this.buf.length = 0;
1671
- }
1672
- summary() {
1673
- const n = this.buf.length;
1674
- if (!n)
1675
- return { totalCalls: 0, totalInput: 0, totalOutput: 0, totalCacheHit: 0, totalCacheWrite: 0, avgLatency: 0, cacheHitRate: 0 };
1676
- let totalInput = 0, totalOutput = 0, totalCacheHit = 0, totalCacheWrite = 0, totalLatency = 0;
1677
- for (const m of this.buf) {
1678
- totalInput += m.inputTokens;
1679
- totalOutput += m.outputTokens;
1680
- totalCacheHit += m.cacheHitTokens;
1681
- totalCacheWrite += m.cacheWriteTokens ?? 0;
1682
- totalLatency += m.latencyMs;
1683
- }
1684
- const promptInput = totalInput + totalCacheHit + totalCacheWrite;
1685
- const cacheHitRate = promptInput > 0 ? totalCacheHit / promptInput : 0;
1686
- return {
1687
- totalCalls: n,
1688
- totalInput,
1689
- totalOutput,
1690
- totalCacheHit,
1691
- totalCacheWrite,
1692
- avgLatency: Math.round(totalLatency / n),
1693
- cacheHitRate
1694
- };
1695
- }
1696
- }
1697
-
1698
- class BudgetExceededError extends Error {
1699
- reason;
1700
- constructor(reason) {
1701
- super("Smart Compact " + reason + " budget exhausted");
1702
- this.reason = reason;
1703
- this.name = "BudgetExceededError";
1704
- }
1705
- }
1706
-
1707
- class BudgetGuard {
1708
- maxCalls;
1709
- maxLatencyMs;
1710
- clock;
1711
- maxInputTokens;
1712
- maxOutputTokens;
1713
- calls = 0;
1714
- inputTokens = 0;
1715
- outputTokens = 0;
1716
- reservedOutputTokens = 0;
1717
- startedAt;
1718
- lastReason = null;
1719
- constructor(maxCalls = 0, maxLatencyMs = 0, clock = systemClock, maxInputTokens = 0, maxOutputTokens = 0) {
1720
- this.maxCalls = maxCalls;
1721
- this.maxLatencyMs = maxLatencyMs;
1722
- this.clock = clock;
1723
- this.maxInputTokens = maxInputTokens;
1724
- this.maxOutputTokens = maxOutputTokens;
1725
- this.startedAt = clock.now();
1726
- }
1727
- reserveCall(estimatedInputTokens = 0, expectedOutputTokens = 0) {
1728
- if (this.maxLatencyMs > 0 && this.clock.now() - this.startedAt >= this.maxLatencyMs) {
1729
- this.lastReason = "latency";
1730
- throw new BudgetExceededError("latency");
1731
- }
1732
- if (this.maxCalls > 0 && this.calls >= this.maxCalls) {
1733
- this.lastReason = "calls";
1734
- throw new BudgetExceededError("calls");
1735
- }
1736
- const outputReservation = Math.max(0, expectedOutputTokens);
1737
- if (this.maxInputTokens > 0 && this.inputTokens + estimatedInputTokens > this.maxInputTokens || this.maxOutputTokens > 0 && (this.outputTokens + this.reservedOutputTokens >= this.maxOutputTokens || this.outputTokens + this.reservedOutputTokens + outputReservation > this.maxOutputTokens)) {
1738
- this.lastReason = "tokens";
1739
- throw new BudgetExceededError("tokens");
1740
- }
1741
- this.calls++;
1742
- this.inputTokens += estimatedInputTokens;
1743
- this.reservedOutputTokens += outputReservation;
1744
- return outputReservation;
1745
- }
1746
- reconcileInput(estimated, actual) {
1747
- this.inputTokens = Math.max(0, this.inputTokens - estimated + actual);
1748
- if (this.maxInputTokens > 0 && this.inputTokens >= this.maxInputTokens)
1749
- this.lastReason = "tokens";
1750
- }
1751
- reconcileOutput(reserved, actual) {
1752
- this.reservedOutputTokens = Math.max(0, this.reservedOutputTokens - Math.max(0, reserved));
1753
- this.outputTokens += Math.max(0, actual);
1754
- if (this.maxOutputTokens > 0 && this.outputTokens + this.reservedOutputTokens >= this.maxOutputTokens)
1755
- this.lastReason = "tokens";
1756
- }
1757
- commitFailedOutput(reserved) {
1758
- const amount = Math.max(0, reserved);
1759
- this.reservedOutputTokens = Math.max(0, this.reservedOutputTokens - amount);
1760
- this.outputTokens += amount;
1761
- if (this.maxOutputTokens > 0 && this.outputTokens >= this.maxOutputTokens)
1762
- this.lastReason = "tokens";
1763
- }
1764
- recordOutput(actual) {
1765
- this.reconcileOutput(0, actual);
1766
- }
1767
- setLimits(maxCalls, maxInputTokens, maxOutputTokens = 0) {
1768
- this.maxCalls = maxCalls;
1769
- this.maxInputTokens = maxInputTokens;
1770
- this.maxOutputTokens = maxOutputTokens;
1771
- }
1772
- callCount() {
1773
- return this.calls;
1774
- }
1775
- remainingCalls() {
1776
- return this.maxCalls > 0 ? Math.max(0, this.maxCalls - this.calls) : Number.POSITIVE_INFINITY;
1777
- }
1778
- inputTokenCount() {
1779
- return this.inputTokens;
1780
- }
1781
- outputTokenCount() {
1782
- return this.outputTokens;
1783
- }
1784
- remainingOutputTokens() {
1785
- return this.maxOutputTokens > 0 ? Math.max(0, this.maxOutputTokens - this.outputTokens - this.reservedOutputTokens) : Number.POSITIVE_INFINITY;
1786
- }
1787
- reason() {
1788
- return this.lastReason;
1789
- }
1790
- }
1791
-
1792
- class ExtractionCacheStats {
1793
- hits = 0;
1794
- misses = 0;
1795
- recordHit() {
1796
- this.hits++;
1797
- }
1798
- recordMiss() {
1799
- this.misses++;
1800
- }
1801
- snapshot() {
1802
- const total = this.hits + this.misses;
1803
- return { hits: this.hits, misses: this.misses, hitRate: total > 0 ? this.hits / total : 0 };
1804
- }
1805
- clear() {
1806
- this.hits = 0;
1807
- this.misses = 0;
1808
- }
1809
- }
1810
- function makeCompactSessionId() {
1811
- return "sc-" + Date.now().toString(36) + "-" + crypto.randomBytes(4).toString("hex");
1812
- }
1813
- function createServices(overrides = {}) {
1814
- return {
1815
- clock: overrides.clock ?? systemClock,
1816
- llm: overrides.llm ?? { complete: (...args) => getLlmClient().complete(...args) },
1817
- toolSupport: overrides.toolSupport ?? new ToolSupportCache,
1818
- metrics: overrides.metrics ?? new MetricsSink,
1819
- extractionCacheStats: overrides.extractionCacheStats ?? new ExtractionCacheStats,
1820
- tokenCalibration: overrides.tokenCalibration ?? new TokenCalibrationStore,
1821
- budget: overrides.budget ?? new BudgetGuard,
1822
- scrubber: overrides.scrubber ?? new SecretScrubber,
1823
- thinkingLevels: overrides.thinkingLevels ?? {
1824
- summaryThinkingLevel: DEFAULT_CONFIG.summaryThinkingLevel,
1825
- segmentationThinkingLevel: DEFAULT_CONFIG.segmentationThinkingLevel
1826
- },
1827
- codexWatchdogMs: overrides.codexWatchdogMs ?? DEFAULT_CONFIG.codexMaxCallMs,
1828
- compactSessionId: overrides.compactSessionId ?? makeCompactSessionId()
1829
- };
1830
- }
1831
- var processToolSupport = new ToolSupportCache;
1832
- var processTokenCalibration = new TokenCalibrationStore;
1833
- var _default = createServices();
1834
-
1835
- // src/domain/telemetry.ts
1836
- function p95(values) {
1837
- if (!values.length)
1838
- return 0;
1839
- const sorted = [...values].sort((a, b) => a - b);
1840
- return sorted[Math.max(0, Math.ceil(sorted.length * 0.95) - 1)] ?? 0;
1841
- }
1842
- function stats(entries, damage) {
1843
- const evidence = entries.filter((entry) => entry.status !== "dry-run");
1844
- const successfulRuns = evidence.filter((entry) => entry.status === "success");
1845
- const quality = successfulRuns.filter((entry) => typeof entry.verificationScore === "number");
1846
- const appliedRunIds = new Set(successfulRuns.filter((entry) => typeof entry.runId === "string" && entry.runId.length >= 8).map((entry) => entry.runId));
1847
- const observedScores = new Map;
1848
- for (const observation of damage) {
1849
- if (!observation.runId || !appliedRunIds.has(observation.runId) || typeof observation.damageScore !== "number" || !Number.isFinite(observation.damageScore))
1850
- continue;
1851
- observedScores.set(observation.runId, Math.max(observedScores.get(observation.runId) ?? 0, Math.max(0, Math.min(100, observation.damageScore))));
1852
- }
1853
- const damaging = [...observedScores.values()].filter((score) => score > 0).length;
1854
- return {
1855
- runs: entries.length,
1856
- appliedRuns: evidence.length,
1857
- successRate: evidence.length ? successfulRuns.length / evidence.length : 1,
1858
- avgQuality: quality.length ? quality.reduce((sum, entry) => sum + (entry.verificationScore ?? 0), 0) / quality.length : null,
1859
- qualityCoverage: evidence.length ? quality.length / evidence.length : 0,
1860
- p95LatencyMs: p95(evidence.map((entry) => entry.durationMs ?? entry.avgLatency).filter(Number.isFinite)),
1861
- avgTokens: evidence.length ? evidence.reduce((sum, entry) => sum + entry.totalInput + entry.totalCacheHit + (entry.totalCacheWrite ?? 0) + entry.totalOutput, 0) / evidence.length : 0,
1862
- fallbackRate: evidence.length ? evidence.filter((entry) => entry.method === "heuristic" || Array.isArray(entry.providerRoutes) && entry.providerRoutes.some((route) => route.successes < route.calls)).length / evidence.length : 0,
1863
- damageRate: observedScores.size ? damaging / observedScores.size : 0,
1864
- damageCoverage: successfulRuns.length ? observedScores.size / successfulRuns.length : 0
1865
- };
1866
- }
1867
- function roundStats(value) {
1868
- return {
1869
- ...value,
1870
- successRate: Math.round(value.successRate * 1000) / 1000,
1871
- avgQuality: value.avgQuality == null ? null : Math.round(value.avgQuality * 10) / 10,
1872
- qualityCoverage: Math.round(value.qualityCoverage * 1000) / 1000,
1873
- p95LatencyMs: Math.round(value.p95LatencyMs),
1874
- avgTokens: Math.round(value.avgTokens),
1875
- fallbackRate: Math.round(value.fallbackRate * 1000) / 1000,
1876
- damageRate: Math.round(value.damageRate * 1000) / 1000,
1877
- damageCoverage: Math.round(value.damageCoverage * 1000) / 1000
1878
- };
1879
- }
1880
- function assessCanary(entries, damageEntries, options) {
1881
- const minCanaryRuns = Math.max(5, options.minCanaryRuns ?? 20);
1882
- const canaryEntries = entries.filter((entry) => entry.metricsSchemaVersion === 2 && entry.version === options.version && entry.releaseChannel === "canary").slice(-Math.max(100, minCanaryRuns));
1883
- const baselineEntries = entries.filter((entry) => entry.metricsSchemaVersion === 2 && (entry.releaseChannel ?? "stable") === "stable").slice(-(options.baselineRuns ?? Math.max(50, minCanaryRuns * 2)));
1884
- const baseline = stats(baselineEntries, damageEntries);
1885
- const canary = stats(canaryEntries, damageEntries);
1886
- const triggers = [];
1887
- const failureBaseline = 1 - baseline.successRate;
1888
- const failureCanary = 1 - canary.successRate;
1889
- if (canary.appliedRuns >= 3 && (failureCanary > 0.050001 || failureCanary - failureBaseline >= 0.050001)) {
1890
- triggers.push({
1891
- metric: "failure-rate",
1892
- baseline: failureBaseline,
1893
- canary: failureCanary,
1894
- threshold: failureCanary > 0.050001 ? ">5% absolute" : "+5pp regression"
1895
- });
1896
- }
1897
- if (canary.avgQuality != null && (canary.avgQuality < 85 || baseline.avgQuality != null && baseline.avgQuality - canary.avgQuality >= 5)) {
1898
- triggers.push({
1899
- metric: "quality",
1900
- baseline: baseline.avgQuality ?? 0,
1901
- canary: canary.avgQuality,
1902
- threshold: canary.avgQuality < 85 ? "<85 absolute" : "-5 points"
1903
- });
1904
- }
1905
- if (baseline.p95LatencyMs >= 1000 && canary.p95LatencyMs >= baseline.p95LatencyMs * 1.5) {
1906
- triggers.push({ metric: "latency", baseline: baseline.p95LatencyMs, canary: canary.p95LatencyMs, threshold: "+50% p95" });
1907
- }
1908
- if (baseline.avgTokens >= 1000 && canary.avgTokens >= baseline.avgTokens * 1.5) {
1909
- triggers.push({ metric: "tokens", baseline: baseline.avgTokens, canary: canary.avgTokens, threshold: "+50%" });
1910
- }
1911
- if (canary.fallbackRate - baseline.fallbackRate >= 0.1) {
1912
- triggers.push({ metric: "fallback", baseline: baseline.fallbackRate, canary: canary.fallbackRate, threshold: "+10pp" });
1913
- }
1914
- if (canary.damageRate - baseline.damageRate >= 0.1) {
1915
- triggers.push({ metric: "damage", baseline: baseline.damageRate, canary: canary.damageRate, threshold: "+10pp" });
1916
- }
1917
- const canarySampleAdequacy = Math.min(1, canary.appliedRuns / minCanaryRuns);
1918
- const baselineSampleAdequacy = Math.min(1, baseline.appliedRuns / Math.max(20, minCanaryRuns));
1919
- const dataConfidence = Math.round(100 * (canarySampleAdequacy * 0.25 + baselineSampleAdequacy * 0.15 + canary.qualityCoverage * canarySampleAdequacy * 0.2 + canary.damageCoverage * canarySampleAdequacy * 0.2 + baseline.damageCoverage * baselineSampleAdequacy * 0.2));
1920
- const reasons = [];
1921
- let decision = "hold";
1922
- if (triggers.length && canary.appliedRuns >= 3) {
1923
- decision = "rollback";
1924
- reasons.push(...triggers.map((trigger) => trigger.metric + " crossed " + trigger.threshold));
1925
- } else if (canary.appliedRuns < minCanaryRuns) {
1926
- reasons.push("need " + (minCanaryRuns - canary.appliedRuns) + " more canary runs with applied outcomes");
1927
- } else if (baseline.appliedRuns < Math.max(20, minCanaryRuns)) {
1928
- reasons.push("stable baseline is too small");
1929
- } else if (canary.qualityCoverage < 0.7) {
1930
- reasons.push("schema-v2 quality coverage is below 70%");
1931
- } else if (canary.damageCoverage < 0.7) {
1932
- reasons.push("correlated canary damage-observation coverage is below 70%");
1933
- } else if (baseline.damageCoverage < 0.7) {
1934
- reasons.push("correlated stable damage-observation coverage is below 70%");
1935
- } else if ((canary.avgQuality ?? 0) < 85) {
1936
- reasons.push("absolute verifier quality is below 85");
1937
- } else if (canary.successRate < 0.949999) {
1938
- reasons.push("absolute success rate is below 95%");
1939
- } else {
1940
- decision = "promote";
1941
- reasons.push("sample, absolute quality, reliability, latency, token, fallback, and damage gates passed");
1942
- }
1943
- return {
1944
- version: options.version,
1945
- decision,
1946
- dataConfidence,
1947
- baseline: roundStats(baseline),
1948
- canary: roundStats(canary),
1949
- triggers,
1950
- reasons
1951
- };
1952
- }
1953
- function safeMetricLabel(value, fallback) {
1954
- if (typeof value !== "string" || !/^[\w./:@+-]{1,160}$/.test(value))
1955
- return fallback;
1956
- return value;
1957
- }
1958
- var TELEMETRY_FAILURE_KINDS = new Set([
1959
- "cancelled",
1960
- "timeout",
1961
- "rate-limit",
1962
- "authentication",
1963
- "budget",
1964
- "output-limit",
1965
- "provider",
1966
- "persistence",
1967
- "validation",
1968
- "verification",
1969
- "yield",
1970
- "internal"
1971
- ]);
1972
- function isTelemetryFailureKind(value) {
1973
- return typeof value === "string" && TELEMETRY_FAILURE_KINDS.has(value);
1974
- }
1975
- function buildPrivacySafeTelemetry(entries, damageEntries, options) {
1976
- const groups = new Map;
1977
- const failures = {};
1978
- for (const entry of entries) {
1979
- const version = safeMetricLabel(entry.version, "legacy");
1980
- const channel = entry.releaseChannel === "canary" ? "canary" : "stable";
1981
- const provider = safeMetricLabel(entry.provider, "unknown");
1982
- const rawModel = safeMetricLabel(entry.model, "unknown");
1983
- const model = rawModel.startsWith(provider + "/") ? rawModel.slice(provider.length + 1) : rawModel;
1984
- const key = [version, channel, provider, model].join("\x00");
1985
- const group = groups.get(key) ?? {
1986
- version,
1987
- channel,
1988
- provider,
1989
- model,
1990
- runs: 0,
1991
- successes: 0,
1992
- quality: 0,
1993
- qualityRuns: 0,
1994
- latency: 0,
1995
- input: 0,
1996
- output: 0
1997
- };
1998
- group.runs++;
1999
- if (entry.status === "success" || entry.status === "dry-run")
2000
- group.successes++;
2001
- if (entry.metricsSchemaVersion === 2 && typeof entry.verificationScore === "number") {
2002
- group.quality += entry.verificationScore;
2003
- group.qualityRuns++;
2004
- }
2005
- group.latency += entry.avgLatency;
2006
- group.input += entry.totalInput + entry.totalCacheHit + (entry.totalCacheWrite ?? 0);
2007
- group.output += entry.totalOutput;
2008
- groups.set(key, group);
2009
- if (isTelemetryFailureKind(entry.failureKind)) {
2010
- failures[entry.failureKind] = (failures[entry.failureKind] ?? 0) + 1;
2011
- }
2012
- }
2013
- const aggregates = [...groups.values()].map((group) => ({
2014
- version: group.version,
2015
- channel: group.channel,
2016
- provider: group.provider,
2017
- model: group.model,
2018
- runs: group.runs,
2019
- successes: group.successes,
2020
- avgQuality: group.qualityRuns ? Math.round(group.quality / group.qualityRuns * 10) / 10 : null,
2021
- avgLatencyMs: group.runs ? Math.round(group.latency / group.runs) : 0,
2022
- inputTokens: group.input,
2023
- outputTokens: group.output
2024
- })).sort((a, b) => b.runs - a.runs || a.version.localeCompare(b.version));
2025
- return {
2026
- generatedAt: new Date().toISOString(),
2027
- totalRuns: entries.length,
2028
- aggregates,
2029
- failures,
2030
- canary: assessCanary(entries, damageEntries, options),
2031
- privacy: "aggregate-only; no session ids, project ids, prompts, summaries, paths, or error text"
2032
- };
2033
- }
2034
- function formatPrivacySafeTelemetry(report) {
2035
- const lines = [
2036
- "# Smart Compact Telemetry",
2037
- "",
2038
- "Privacy: " + report.privacy + ".",
2039
- "",
2040
- "| Version | Channel | Provider/model | Runs | Success | Quality | Latency | Input | Output |",
2041
- "|---|---|---|---:|---:|---:|---:|---:|---:|"
2042
- ];
2043
- for (const item of report.aggregates) {
2044
- lines.push("| " + item.version + " | " + item.channel + " | " + item.provider + "/" + item.model + " | " + item.runs + " | " + item.successes + "/" + item.runs + " | " + (item.avgQuality == null ? "n/a" : item.avgQuality.toFixed(1)) + " | " + item.avgLatencyMs + "ms | " + item.inputTokens + " | " + item.outputTokens + " |");
2045
- }
2046
- lines.push("", "## Canary: " + report.canary.decision.toUpperCase() + " (data confidence " + report.canary.dataConfidence + "%)", "");
2047
- const baseline = report.canary.baseline;
2048
- const canary = report.canary.canary;
2049
- lines.push("| Gate | Stable baseline | Canary |", "|---|---:|---:|", "| Runs (total/applied) | " + baseline.runs + "/" + baseline.appliedRuns + " | " + canary.runs + "/" + canary.appliedRuns + " |", "| Success | " + Math.round(baseline.successRate * 100) + "% | " + Math.round(canary.successRate * 100) + "% |", "| Verify quality | " + (baseline.avgQuality ?? "n/a") + " | " + (canary.avgQuality ?? "n/a") + " |", "| p95 duration | " + baseline.p95LatencyMs + "ms | " + canary.p95LatencyMs + "ms |", "| Avg tokens | " + baseline.avgTokens + " | " + canary.avgTokens + " |", "| Fallback | " + Math.round(baseline.fallbackRate * 100) + "% | " + Math.round(canary.fallbackRate * 100) + "% |", "| Damage | " + Math.round(baseline.damageRate * 100) + "% | " + Math.round(canary.damageRate * 100) + "% |", "| Damage observed | " + Math.round(baseline.damageCoverage * 100) + "% | " + Math.round(canary.damageCoverage * 100) + "% |", "");
2050
- for (const reason of report.canary.reasons)
2051
- lines.push("- " + reason);
2052
- const failureText = Object.entries(report.failures).map(([kind, count]) => kind + "=" + count).join(", ");
2053
- lines.push("", "Failures: " + (failureText || "none classified"));
2054
- return lines.join(`
2055
- `);
2056
- }
2057
-
2058
- // src/utils/cache.ts
2059
- var INTERNAL_PHASES = new Set([
2060
- "explore-retry",
2061
- "explore-direct",
2062
- "single-pass",
2063
- "batch",
2064
- "assemble",
2065
- "patch"
2066
- ]);
2067
- var SEGMENTATION_PHASES = new Set([
2068
- "probe",
2069
- "explore",
2070
- "explore-loop",
2071
- "explore-retry",
2072
- "explore-direct"
2073
- ]);
2074
- function readMetricsLog(limit = 100) {
2075
- try {
2076
- const logPath = metricsLogFile();
2077
- if (!fs2.existsSync(logPath))
2078
- return [];
2079
- const stat = fs2.statSync(logPath);
2080
- const TAIL_CHUNK = 64 * 1024;
2081
- const wantBytes = Math.min(stat.size, Math.max(TAIL_CHUNK, limit * 8 * 512));
2082
- const startPos = Math.max(0, stat.size - wantBytes);
2083
- const fd = fs2.openSync(logPath, "r");
2084
- try {
2085
- const buf = Buffer.alloc(wantBytes);
2086
- const bytesRead = fs2.readSync(fd, buf, 0, wantBytes, startPos);
2087
- let text = buf.subarray(0, bytesRead).toString("utf8");
2088
- if (startPos > 0) {
2089
- const nl = text.indexOf(`
2090
- `);
2091
- if (nl >= 0)
2092
- text = text.slice(nl + 1);
2093
- }
2094
- const lines = text.split(`
2095
- `).filter(Boolean);
2096
- const entries = [];
2097
- for (const line of lines) {
2098
- try {
2099
- entries.push(JSON.parse(line));
2100
- } catch {
2101
- warn("Skipping corrupt compact metrics line");
2102
- }
2103
- }
2104
- return entries.slice(-limit);
2105
- } finally {
2106
- fs2.closeSync(fd);
2107
- }
2108
- } catch (e) {
2109
- warn("readMetricsLog failed", e);
2110
- return [];
2111
- }
2112
- }
2113
-
2114
- // scripts/provider-eval.ts
2115
- var minArg = process.argv.find((arg) => arg.startsWith("--min-samples="));
2116
- var minSamples = minArg ? Number(minArg.split("=")[1]) : 5;
2117
- if (!Number.isInteger(minSamples) || minSamples < 2) {
2118
- console.error("--min-samples must be an integer >= 2");
2119
- process.exit(1);
2120
- }
2121
- var report = evaluateProviderMetrics(readMetricsLog(1e4), { minSamples });
2122
- console.log(process.argv.includes("--json") ? JSON.stringify(report, null, 2) : formatProviderEvaluation(report));