pi-ghost-autocomplete 0.3.1 → 0.3.2

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 (155) hide show
  1. package/README.md +101 -6
  2. package/dist/bench-cli.js +81 -5
  3. package/dist/bench-cli.js.map +1 -1
  4. package/dist/bench.d.ts +130 -2
  5. package/dist/bench.d.ts.map +1 -1
  6. package/dist/bench.js +285 -4
  7. package/dist/bench.js.map +1 -1
  8. package/dist/cache.d.ts +124 -0
  9. package/dist/cache.d.ts.map +1 -0
  10. package/dist/cache.js +173 -0
  11. package/dist/cache.js.map +1 -0
  12. package/dist/candidate-rerank.d.ts +57 -0
  13. package/dist/candidate-rerank.d.ts.map +1 -0
  14. package/dist/candidate-rerank.js +78 -0
  15. package/dist/candidate-rerank.js.map +1 -0
  16. package/dist/config.d.ts +43 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +148 -18
  19. package/dist/config.js.map +1 -1
  20. package/dist/context-aggregator.d.ts +14 -5
  21. package/dist/context-aggregator.d.ts.map +1 -1
  22. package/dist/context-aggregator.js +38 -9
  23. package/dist/context-aggregator.js.map +1 -1
  24. package/dist/context-selector.d.ts +128 -0
  25. package/dist/context-selector.d.ts.map +1 -0
  26. package/dist/context-selector.js +300 -0
  27. package/dist/context-selector.js.map +1 -0
  28. package/dist/cycle-manager.d.ts +102 -0
  29. package/dist/cycle-manager.d.ts.map +1 -0
  30. package/dist/cycle-manager.js +168 -0
  31. package/dist/cycle-manager.js.map +1 -0
  32. package/dist/edit-history-ring.d.ts +148 -0
  33. package/dist/edit-history-ring.d.ts.map +1 -0
  34. package/dist/edit-history-ring.js +262 -0
  35. package/dist/edit-history-ring.js.map +1 -0
  36. package/dist/entropy-stop.d.ts +102 -0
  37. package/dist/entropy-stop.d.ts.map +1 -0
  38. package/dist/entropy-stop.js +141 -0
  39. package/dist/entropy-stop.js.map +1 -0
  40. package/dist/eval-dataset.d.ts +66 -0
  41. package/dist/eval-dataset.d.ts.map +1 -0
  42. package/dist/eval-dataset.js +127 -0
  43. package/dist/eval-dataset.js.map +1 -0
  44. package/dist/fast-path-engine.d.ts +286 -0
  45. package/dist/fast-path-engine.d.ts.map +1 -0
  46. package/dist/fast-path-engine.js +341 -0
  47. package/dist/fast-path-engine.js.map +1 -0
  48. package/dist/fuzzy-matcher.d.ts +8 -0
  49. package/dist/fuzzy-matcher.d.ts.map +1 -1
  50. package/dist/fuzzy-matcher.js +5 -3
  51. package/dist/fuzzy-matcher.js.map +1 -1
  52. package/dist/ghost-editor.d.ts +321 -4
  53. package/dist/ghost-editor.d.ts.map +1 -1
  54. package/dist/ghost-editor.js +1190 -144
  55. package/dist/ghost-editor.js.map +1 -1
  56. package/dist/index.d.ts +17 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +81 -2
  59. package/dist/index.js.map +1 -1
  60. package/dist/keystroke-timer.d.ts +59 -0
  61. package/dist/keystroke-timer.d.ts.map +1 -0
  62. package/dist/keystroke-timer.js +94 -0
  63. package/dist/keystroke-timer.js.map +1 -0
  64. package/dist/metrics-builder.d.ts +71 -0
  65. package/dist/metrics-builder.d.ts.map +1 -0
  66. package/dist/metrics-builder.js +71 -0
  67. package/dist/metrics-builder.js.map +1 -0
  68. package/dist/metrics.d.ts +99 -2
  69. package/dist/metrics.d.ts.map +1 -1
  70. package/dist/metrics.js +131 -17
  71. package/dist/metrics.js.map +1 -1
  72. package/dist/path-matcher.d.ts +9 -0
  73. package/dist/path-matcher.d.ts.map +1 -1
  74. package/dist/path-matcher.js +5 -2
  75. package/dist/path-matcher.js.map +1 -1
  76. package/dist/profile-store.d.ts +82 -0
  77. package/dist/profile-store.d.ts.map +1 -0
  78. package/dist/profile-store.js +199 -0
  79. package/dist/profile-store.js.map +1 -0
  80. package/dist/profile.d.ts +185 -0
  81. package/dist/profile.d.ts.map +1 -0
  82. package/dist/profile.js +434 -0
  83. package/dist/profile.js.map +1 -0
  84. package/dist/providers/cloud-provider.d.ts +10 -1
  85. package/dist/providers/cloud-provider.d.ts.map +1 -1
  86. package/dist/providers/cloud-provider.js +71 -14
  87. package/dist/providers/cloud-provider.js.map +1 -1
  88. package/dist/providers/local-provider.d.ts +8 -1
  89. package/dist/providers/local-provider.d.ts.map +1 -1
  90. package/dist/providers/local-provider.js +15 -3
  91. package/dist/providers/local-provider.js.map +1 -1
  92. package/dist/providers/openai-compat-provider.d.ts +11 -1
  93. package/dist/providers/openai-compat-provider.d.ts.map +1 -1
  94. package/dist/providers/openai-compat-provider.js +93 -6
  95. package/dist/providers/openai-compat-provider.js.map +1 -1
  96. package/dist/providers/openai-completions-runner.d.ts +25 -1
  97. package/dist/providers/openai-completions-runner.d.ts.map +1 -1
  98. package/dist/providers/openai-completions-runner.js +109 -14
  99. package/dist/providers/openai-completions-runner.js.map +1 -1
  100. package/dist/providers/race.d.ts +58 -1
  101. package/dist/providers/race.d.ts.map +1 -1
  102. package/dist/providers/race.js +317 -4
  103. package/dist/providers/race.js.map +1 -1
  104. package/dist/providers/truncate.d.ts.map +1 -1
  105. package/dist/providers/truncate.js +20 -2
  106. package/dist/providers/truncate.js.map +1 -1
  107. package/dist/providers/types.d.ts +96 -1
  108. package/dist/providers/types.d.ts.map +1 -1
  109. package/dist/providers/types.js +59 -1
  110. package/dist/providers/types.js.map +1 -1
  111. package/dist/provisional-manager.d.ts +93 -0
  112. package/dist/provisional-manager.d.ts.map +1 -0
  113. package/dist/provisional-manager.js +98 -0
  114. package/dist/provisional-manager.js.map +1 -0
  115. package/dist/provisional.d.ts +150 -0
  116. package/dist/provisional.d.ts.map +1 -0
  117. package/dist/provisional.js +238 -0
  118. package/dist/provisional.js.map +1 -0
  119. package/dist/regret-tracker.d.ts +84 -0
  120. package/dist/regret-tracker.d.ts.map +1 -0
  121. package/dist/regret-tracker.js +145 -0
  122. package/dist/regret-tracker.js.map +1 -0
  123. package/dist/replay-cli.d.ts +13 -0
  124. package/dist/replay-cli.d.ts.map +1 -0
  125. package/dist/replay-cli.js +167 -0
  126. package/dist/replay-cli.js.map +1 -0
  127. package/dist/replay.d.ts +107 -0
  128. package/dist/replay.d.ts.map +1 -0
  129. package/dist/replay.js +225 -0
  130. package/dist/replay.js.map +1 -0
  131. package/dist/reranker.d.ts +181 -0
  132. package/dist/reranker.d.ts.map +1 -0
  133. package/dist/reranker.js +324 -0
  134. package/dist/reranker.js.map +1 -0
  135. package/dist/session-trie.d.ts +103 -0
  136. package/dist/session-trie.d.ts.map +1 -0
  137. package/dist/session-trie.js +280 -0
  138. package/dist/session-trie.js.map +1 -0
  139. package/dist/speculative.d.ts +74 -0
  140. package/dist/speculative.d.ts.map +1 -0
  141. package/dist/speculative.js +107 -0
  142. package/dist/speculative.js.map +1 -0
  143. package/dist/tool-summarize.d.ts +40 -0
  144. package/dist/tool-summarize.d.ts.map +1 -0
  145. package/dist/tool-summarize.js +245 -0
  146. package/dist/tool-summarize.js.map +1 -0
  147. package/dist/trie-sanitize.d.ts +22 -0
  148. package/dist/trie-sanitize.d.ts.map +1 -0
  149. package/dist/trie-sanitize.js +142 -0
  150. package/dist/trie-sanitize.js.map +1 -0
  151. package/dist/versions.d.ts +23 -0
  152. package/dist/versions.d.ts.map +1 -0
  153. package/dist/versions.js +31 -0
  154. package/dist/versions.js.map +1 -0
  155. package/package.json +3 -2
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Accept-regret tracker. PRD §M5a "Regret detection".
3
+ *
4
+ * After an `accepted` or `accepted-partial` outcome, the editor watches
5
+ * for >50% of the inserted characters to be deleted within
6
+ * `PI_GHOST_REGRET_WINDOW_MS` (default 3000 ms). When detected, the
7
+ * metric record is flagged `acceptRegret=true` with
8
+ * `regretDeletedChars=N` before it's logged.
9
+ *
10
+ * Pure state machine — no timers, no I/O. Caller drives the clock and
11
+ * decides when to flush.
12
+ *
13
+ * Lifecycle:
14
+ * 1. `arm(record, snapshot, now)` — stash the staged metric, capture
15
+ * the pre-insert text snapshot + insert range. Returns a handle
16
+ * the caller stores.
17
+ * 2. `observe(handle, currentText, now)` — call on each text change.
18
+ * Returns `{flush, regret, deletedChars}` indicating whether the
19
+ * record is ready to log.
20
+ * 3. `expire(handle, now)` — call when the window deadline fires.
21
+ * Returns the final stats so the caller can flush a record that
22
+ * never tripped the regret threshold.
23
+ */
24
+ import type { MetricsRecord } from "./metrics.js";
25
+ export declare const DEFAULT_REGRET_WINDOW_MS = 3000;
26
+ export declare const REGRET_THRESHOLD = 0.5;
27
+ export interface RegretHandle {
28
+ record: MetricsRecord;
29
+ /** Inserted text, captured at arm time. */
30
+ insertedText: string;
31
+ /** Anchor: characters BEFORE the insert. Used to locate the slice
32
+ * in the current text via a startsWith check. */
33
+ textBefore: string;
34
+ /** Anchor: characters AFTER the insert. */
35
+ textAfter: string;
36
+ /** Deadline ms (absolute) past which `expire` should be called. */
37
+ deadlineMs: number;
38
+ }
39
+ export interface RegretSnapshot {
40
+ textBefore: string;
41
+ insertedText: string;
42
+ textAfter: string;
43
+ }
44
+ export interface ObserveResult {
45
+ /** True when the caller should log the record now. */
46
+ flush: boolean;
47
+ /** True when >50% of inserted text was deleted within the window. */
48
+ regret: boolean;
49
+ /** Number of inserted chars currently deleted (0..insertedText.length). */
50
+ deletedChars: number;
51
+ }
52
+ /**
53
+ * Arm a regret watch. `snapshot` captures the editor state right
54
+ * after the insert: the three slices around the insertion point.
55
+ * `windowMs` is added to `now` to compute the deadline.
56
+ */
57
+ export declare function arm(record: MetricsRecord, snapshot: RegretSnapshot, now: number, windowMs?: number): RegretHandle;
58
+ /**
59
+ * Inspect the current editor text against the handle. Returns whether
60
+ * to flush (because regret was detected OR the deadline passed) plus
61
+ * the regret stats to attach to the record.
62
+ *
63
+ * Algorithm: find the inserted range by anchoring on `textBefore` and
64
+ * `textAfter`. The middle slice between those two anchors is the
65
+ * "currently-remaining inserted text". If anchors moved (e.g. user
66
+ * typed in front of the insert), we treat the deletion as "all of
67
+ * it" — the regret window is conservative.
68
+ */
69
+ export declare function observe(handle: RegretHandle, currentText: string, now: number): ObserveResult;
70
+ /**
71
+ * Hard-flush at deadline. Same as `observe` but always reports
72
+ * `flush: true`. Useful when the caller drives the deadline via a
73
+ * timer rather than per-keystroke text observation.
74
+ */
75
+ export declare function expire(handle: RegretHandle, currentText: string): ObserveResult;
76
+ /**
77
+ * Attach the regret outcome to the staged record. Returns a new
78
+ * record with `acceptRegret` + `regretDeletedChars` set (when
79
+ * `deletedChars > 0`).
80
+ */
81
+ export declare function attachToRecord(handle: RegretHandle, outcome: ObserveResult): MetricsRecord;
82
+ /** Parse `PI_GHOST_REGRET_WINDOW_MS`. Falls back to default on invalid. */
83
+ export declare function parseRegretWindowEnv(raw: string | undefined): number;
84
+ //# sourceMappingURL=regret-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regret-tracker.d.ts","sourceRoot":"","sources":["../src/regret-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,gBAAgB,MAAM,CAAC;AAEpC,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,aAAa,CAAC;IACtB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB;sDACkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC7B,sDAAsD;IACtD,KAAK,EAAE,OAAO,CAAC;IACf,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB,2EAA2E;IAC3E,YAAY,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,GAAG,CAClB,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAiC,GACzC,YAAY,CAQd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,CAY7F;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,GAAG,aAAa,CAM/E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,GACpB,aAAa,CAOf;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAKpE"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Accept-regret tracker. PRD §M5a "Regret detection".
3
+ *
4
+ * After an `accepted` or `accepted-partial` outcome, the editor watches
5
+ * for >50% of the inserted characters to be deleted within
6
+ * `PI_GHOST_REGRET_WINDOW_MS` (default 3000 ms). When detected, the
7
+ * metric record is flagged `acceptRegret=true` with
8
+ * `regretDeletedChars=N` before it's logged.
9
+ *
10
+ * Pure state machine — no timers, no I/O. Caller drives the clock and
11
+ * decides when to flush.
12
+ *
13
+ * Lifecycle:
14
+ * 1. `arm(record, snapshot, now)` — stash the staged metric, capture
15
+ * the pre-insert text snapshot + insert range. Returns a handle
16
+ * the caller stores.
17
+ * 2. `observe(handle, currentText, now)` — call on each text change.
18
+ * Returns `{flush, regret, deletedChars}` indicating whether the
19
+ * record is ready to log.
20
+ * 3. `expire(handle, now)` — call when the window deadline fires.
21
+ * Returns the final stats so the caller can flush a record that
22
+ * never tripped the regret threshold.
23
+ */
24
+ export const DEFAULT_REGRET_WINDOW_MS = 3000;
25
+ export const REGRET_THRESHOLD = 0.5;
26
+ /**
27
+ * Arm a regret watch. `snapshot` captures the editor state right
28
+ * after the insert: the three slices around the insertion point.
29
+ * `windowMs` is added to `now` to compute the deadline.
30
+ */
31
+ export function arm(record, snapshot, now, windowMs = DEFAULT_REGRET_WINDOW_MS) {
32
+ return {
33
+ record,
34
+ insertedText: snapshot.insertedText,
35
+ textBefore: snapshot.textBefore,
36
+ textAfter: snapshot.textAfter,
37
+ deadlineMs: now + windowMs,
38
+ };
39
+ }
40
+ /**
41
+ * Inspect the current editor text against the handle. Returns whether
42
+ * to flush (because regret was detected OR the deadline passed) plus
43
+ * the regret stats to attach to the record.
44
+ *
45
+ * Algorithm: find the inserted range by anchoring on `textBefore` and
46
+ * `textAfter`. The middle slice between those two anchors is the
47
+ * "currently-remaining inserted text". If anchors moved (e.g. user
48
+ * typed in front of the insert), we treat the deletion as "all of
49
+ * it" — the regret window is conservative.
50
+ */
51
+ export function observe(handle, currentText, now) {
52
+ const stats = computeDeletion(handle, currentText);
53
+ const deletedChars = stats.deletedChars;
54
+ const totalInserted = handle.insertedText.length;
55
+ const ratio = totalInserted > 0 ? deletedChars / totalInserted : 0;
56
+ const regret = totalInserted > 0 && ratio > REGRET_THRESHOLD;
57
+ const deadlineReached = now >= handle.deadlineMs;
58
+ return {
59
+ flush: regret || deadlineReached,
60
+ regret,
61
+ deletedChars,
62
+ };
63
+ }
64
+ /**
65
+ * Hard-flush at deadline. Same as `observe` but always reports
66
+ * `flush: true`. Useful when the caller drives the deadline via a
67
+ * timer rather than per-keystroke text observation.
68
+ */
69
+ export function expire(handle, currentText) {
70
+ const stats = computeDeletion(handle, currentText);
71
+ const totalInserted = handle.insertedText.length;
72
+ const ratio = totalInserted > 0 ? stats.deletedChars / totalInserted : 0;
73
+ const regret = totalInserted > 0 && ratio > REGRET_THRESHOLD;
74
+ return { flush: true, regret, deletedChars: stats.deletedChars };
75
+ }
76
+ /**
77
+ * Attach the regret outcome to the staged record. Returns a new
78
+ * record with `acceptRegret` + `regretDeletedChars` set (when
79
+ * `deletedChars > 0`).
80
+ */
81
+ export function attachToRecord(handle, outcome) {
82
+ const out = { ...handle.record };
83
+ out.acceptRegret = outcome.regret;
84
+ if (outcome.deletedChars > 0) {
85
+ out.regretDeletedChars = outcome.deletedChars;
86
+ }
87
+ return out;
88
+ }
89
+ /** Parse `PI_GHOST_REGRET_WINDOW_MS`. Falls back to default on invalid. */
90
+ export function parseRegretWindowEnv(raw) {
91
+ if (raw === undefined)
92
+ return DEFAULT_REGRET_WINDOW_MS;
93
+ const v = Number(raw);
94
+ if (!Number.isFinite(v) || v <= 0)
95
+ return DEFAULT_REGRET_WINDOW_MS;
96
+ return Math.floor(v);
97
+ }
98
+ /**
99
+ * Compute how many inserted chars are missing from `currentText`. Two
100
+ * cases:
101
+ * 1. Anchors match: count chars of `insertedText` that disappeared
102
+ * from the middle slice.
103
+ * 2. Anchors moved (user mutated outside the insert range or
104
+ * restructured the line): treat as full deletion. Conservative —
105
+ * the user obviously didn't keep the suggestion in place.
106
+ *
107
+ * The "missing chars" count uses leading-match against the inserted
108
+ * text, so partial deletions from the right side are detected
109
+ * (typical backspace behavior).
110
+ */
111
+ function computeDeletion(handle, currentText) {
112
+ const { textBefore, insertedText, textAfter } = handle;
113
+ if (!currentText.startsWith(textBefore)) {
114
+ // Anchor before the insert moved → assume full deletion.
115
+ return { deletedChars: insertedText.length };
116
+ }
117
+ const rest = currentText.slice(textBefore.length);
118
+ // `rest` should start with some prefix of `insertedText` followed
119
+ // by `textAfter`. Find the boundary by searching for `textAfter`
120
+ // from the right side.
121
+ const afterIdx = textAfter ? rest.indexOf(textAfter) : rest.length;
122
+ if (afterIdx < 0) {
123
+ // `textAfter` not found — anchor moved.
124
+ return { deletedChars: insertedText.length };
125
+ }
126
+ const remainingInsert = rest.slice(0, afterIdx);
127
+ // Count the leading-match between remainingInsert and insertedText.
128
+ // Chars beyond the match are treated as deleted (the user replaced
129
+ // them, which still counts as regret).
130
+ const keptLen = leadingMatch(remainingInsert, insertedText);
131
+ const deletedFromEnd = insertedText.length - keptLen;
132
+ // If the user typed past the anchor (remainingInsert > insertedText
133
+ // after kept prefix), we count those extra chars as "still kept"
134
+ // only if they aren't from the insertion — but we can't distinguish
135
+ // retroactive typing from kept insertion. Conservative: kept-only.
136
+ return { deletedChars: Math.max(0, deletedFromEnd) };
137
+ }
138
+ function leadingMatch(a, b) {
139
+ const n = Math.min(a.length, b.length);
140
+ let i = 0;
141
+ while (i < n && a[i] === b[i])
142
+ i++;
143
+ return i;
144
+ }
145
+ //# sourceMappingURL=regret-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regret-tracker.js","sourceRoot":"","sources":["../src/regret-tracker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAC7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,GAAG,CAAC;AA8BpC;;;;GAIG;AACH,MAAM,UAAU,GAAG,CAClB,MAAqB,EACrB,QAAwB,EACxB,GAAW,EACX,WAAmB,wBAAwB;IAE3C,OAAO;QACN,MAAM;QACN,YAAY,EAAE,QAAQ,CAAC,YAAY;QACnC,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,SAAS,EAAE,QAAQ,CAAC,SAAS;QAC7B,UAAU,EAAE,GAAG,GAAG,QAAQ;KAC1B,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,OAAO,CAAC,MAAoB,EAAE,WAAmB,EAAE,GAAW;IAC7E,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IACxC,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;IACjD,MAAM,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,aAAa,GAAG,CAAC,IAAI,KAAK,GAAG,gBAAgB,CAAC;IAC7D,MAAM,eAAe,GAAG,GAAG,IAAI,MAAM,CAAC,UAAU,CAAC;IACjD,OAAO;QACN,KAAK,EAAE,MAAM,IAAI,eAAe;QAChC,MAAM;QACN,YAAY;KACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,MAAoB,EAAE,WAAmB;IAC/D,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACnD,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC;IACjD,MAAM,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,MAAM,GAAG,aAAa,GAAG,CAAC,IAAI,KAAK,GAAG,gBAAgB,CAAC;IAC7D,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC;AAClE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC7B,MAAoB,EACpB,OAAsB;IAEtB,MAAM,GAAG,GAAkB,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;IAChD,GAAG,CAAC,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAClC,IAAI,OAAO,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QAC9B,GAAG,CAAC,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAC/C,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,oBAAoB,CAAC,GAAuB;IAC3D,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,wBAAwB,CAAC;IACvD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACnE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,eAAe,CAAC,MAAoB,EAAE,WAAmB;IACjE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC;IACvD,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACzC,yDAAyD;QACzD,OAAO,EAAE,YAAY,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC;IAC9C,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAClD,kEAAkE;IAClE,iEAAiE;IACjE,uBAAuB;IACvB,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;IACnE,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAClB,wCAAwC;QACxC,OAAO,EAAE,YAAY,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC;IAC9C,CAAC;IACD,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAChD,oEAAoE;IACpE,mEAAmE;IACnE,uCAAuC;IACvC,MAAM,OAAO,GAAG,YAAY,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;IAC5D,MAAM,cAAc,GAAG,YAAY,CAAC,MAAM,GAAG,OAAO,CAAC;IACrD,oEAAoE;IACpE,iEAAiE;IACjE,oEAAoE;IACpE,mEAAmE;IACnE,OAAO,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,CAAC;AACtD,CAAC;AAED,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACzC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAAE,CAAC,EAAE,CAAC;IACnC,OAAO,CAAC,CAAC;AACV,CAAC"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `pi-ghost-replay` — offline replay against a captured eval dataset.
4
+ * PRD §M5c.
5
+ *
6
+ * Reads `.pi/ghost-autocomplete/eval.jsonl` (or path from argv[0]),
7
+ * runs each example against a provider built from the active env
8
+ * config, and prints a summary. Supports `--json` for machine-readable
9
+ * output and `--redact` to suppress raw text in the printed report so
10
+ * the output is safe to share.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=replay-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-cli.d.ts","sourceRoot":"","sources":["../src/replay-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `pi-ghost-replay` — offline replay against a captured eval dataset.
4
+ * PRD §M5c.
5
+ *
6
+ * Reads `.pi/ghost-autocomplete/eval.jsonl` (or path from argv[0]),
7
+ * runs each example against a provider built from the active env
8
+ * config, and prints a summary. Supports `--json` for machine-readable
9
+ * output and `--redact` to suppress raw text in the printed report so
10
+ * the output is safe to share.
11
+ */
12
+ import { resolve } from "node:path";
13
+ import { readConfigFromEnv } from "./config.js";
14
+ import { loadExamples } from "./eval-dataset.js";
15
+ import { CloudProvider } from "./providers/cloud-provider.js";
16
+ import { LocalProvider } from "./providers/local-provider.js";
17
+ import { OpenAICompatProvider } from "./providers/openai-compat-provider.js";
18
+ import { RaceProvider } from "./providers/race.js";
19
+ import { wrapAsMulti } from "./providers/types.js";
20
+ import { summarizeReplay } from "./replay.js";
21
+ const DEFAULT_PATH = ".pi/ghost-autocomplete/eval.jsonl";
22
+ async function main() {
23
+ const args = process.argv.slice(2);
24
+ if (args.includes("-h") || args.includes("--help")) {
25
+ process.stdout.write(usage());
26
+ return;
27
+ }
28
+ const wantJson = args.includes("--json");
29
+ const wantRedact = args.includes("--redact");
30
+ const limitFlag = args.find((a) => a.startsWith("--limit="));
31
+ const limit = limitFlag ? Number.parseInt(limitFlag.slice("--limit=".length), 10) : 0;
32
+ const positional = args.filter((a) => !a.startsWith("-"));
33
+ const target = resolve(process.cwd(), positional[0] ?? DEFAULT_PATH);
34
+ const config = readConfigFromEnv();
35
+ const provider = buildProviderFromConfig(config);
36
+ if (!provider) {
37
+ process.stderr.write("pi-ghost-replay: no provider available from current env. Set PI_GHOST_* config and retry.\n");
38
+ process.exit(2);
39
+ }
40
+ const examples = await loadExamples(target);
41
+ if (examples.length === 0) {
42
+ process.stderr.write(`pi-ghost-replay: no examples in ${target}\n`);
43
+ process.exit(2);
44
+ }
45
+ const slice = limit > 0 ? examples.slice(0, limit) : examples;
46
+ const results = [];
47
+ for (const e of slice) {
48
+ const r = await replayOne(provider, e);
49
+ results.push(r);
50
+ }
51
+ const summary = summarizeReplay(results);
52
+ if (wantJson) {
53
+ process.stdout.write(`${JSON.stringify({ provider: provider.name, summary }, null, 2)}\n`);
54
+ }
55
+ else {
56
+ process.stdout.write(formatReplay(provider.name, summary, results, wantRedact));
57
+ }
58
+ process.exit(0);
59
+ }
60
+ const TIMEOUT_MS = 30_000; // 30 seconds per replay
61
+ async function replayOne(provider, example) {
62
+ const start = Date.now();
63
+ const controller = new AbortController();
64
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
65
+ try {
66
+ const set = await wrapAsMulti(provider, {
67
+ editorText: example.prefix,
68
+ editorTextBeforeCursor: example.prefix,
69
+ // Replay doesn't reconstitute the per-message structure today —
70
+ // the on-disk `context` is the flattened render. The CLI passes
71
+ // it as a single user message so the provider's prompt-builder
72
+ // still sees something coherent.
73
+ recentMessages: example.context
74
+ ? [{ role: "user", text: example.context }]
75
+ : [],
76
+ cwd: process.cwd(),
77
+ signal: controller.signal,
78
+ });
79
+ clearTimeout(timeoutId);
80
+ const latencyMs = Date.now() - start;
81
+ const actual = set?.candidates[0]?.text ?? "";
82
+ return {
83
+ example,
84
+ actual,
85
+ latencyMs,
86
+ candidates: set ? set.candidates.map((c) => c.text) : [],
87
+ };
88
+ }
89
+ catch (err) {
90
+ clearTimeout(timeoutId);
91
+ const latencyMs = Date.now() - start;
92
+ const isAbort = controller.signal.aborted || (err instanceof Error && err.name === "AbortError");
93
+ const message = isAbort ? `timeout after ${TIMEOUT_MS}ms` : err instanceof Error ? err.message : String(err);
94
+ return { example, actual: "", latencyMs, error: message };
95
+ }
96
+ }
97
+ function buildProviderFromConfig(config) {
98
+ if (!config.enabled)
99
+ return null;
100
+ const p = config.provider;
101
+ if (p.kind === "race") {
102
+ const members = [];
103
+ for (const m of p.members) {
104
+ if (m.kind === "cloud")
105
+ members.push(new CloudProvider(m));
106
+ else if (m.kind === "openai-compat")
107
+ members.push(new OpenAICompatProvider(m));
108
+ else if (m.kind === "local")
109
+ members.push(new LocalProvider(m));
110
+ else
111
+ throw new Error(`buildProviderFromConfig: unsupported race member kind "${m.kind}"`);
112
+ }
113
+ if (members.length === 0)
114
+ return null;
115
+ return new RaceProvider(members, {
116
+ pauseMs: p.pauseMs,
117
+ cooldownMs: p.cooldownMs,
118
+ cutoffMs: p.cutoffMs,
119
+ });
120
+ }
121
+ if (p.kind === "cloud")
122
+ return new CloudProvider(p);
123
+ if (p.kind === "openai-compat")
124
+ return new OpenAICompatProvider(p);
125
+ if (p.kind === "local")
126
+ return new LocalProvider(p);
127
+ throw new Error(`buildProviderFromConfig: unsupported provider kind "${p.kind}"`);
128
+ }
129
+ function formatReplay(providerName, s, results, redact) {
130
+ const lines = [];
131
+ lines.push(`pi-ghost-replay against ${providerName}`);
132
+ lines.push(` examples=${s.count} errors=${s.errors}`);
133
+ lines.push(` exactMatchRate=${(s.exactMatchRate * 100).toFixed(1)}%`);
134
+ lines.push(` meanPrefixMatchLen=${s.meanPrefixMatchLen.toFixed(2)}`);
135
+ lines.push(` meanEditDistance=${s.meanEditDistance.toFixed(2)}`);
136
+ lines.push(` saved@1=${s.meanSavedAt1.toFixed(3)} saved@3=${s.meanSavedAt3.toFixed(3)} saved@5=${s.meanSavedAt5.toFixed(3)}`);
137
+ lines.push(` prefixRecall@1=${(s.prefixRecallAt1 * 100).toFixed(1)}% @3=${(s.prefixRecallAt3 * 100).toFixed(1)}% @5=${(s.prefixRecallAt5 * 100).toFixed(1)}%`);
138
+ lines.push(` latency p50=${s.p50LatencyMs.toFixed(0)}ms p95=${s.p95LatencyMs.toFixed(0)}ms`);
139
+ if (!redact) {
140
+ lines.push("");
141
+ lines.push(" Sample examples:");
142
+ for (const r of results.slice(0, 5)) {
143
+ const tag = r.error ? `ERR ${r.error}` : r.actual === r.example.expected ? "OK" : "DIFF";
144
+ lines.push(` [${tag}] prefix="${truncate(r.example.prefix, 30)}" expected="${truncate(r.example.expected, 40)}" actual="${truncate(r.actual, 40)}"`);
145
+ }
146
+ }
147
+ return `${lines.join("\n")}\n`;
148
+ }
149
+ function truncate(s, n) {
150
+ return s.length > n ? `${s.slice(0, n - 1)}…` : s;
151
+ }
152
+ function usage() {
153
+ return [
154
+ "pi-ghost-replay [path] [--json] [--redact] [--limit=N]",
155
+ "",
156
+ " path Eval JSONL file (default: .pi/ghost-autocomplete/eval.jsonl)",
157
+ " --json Emit JSON instead of formatted text",
158
+ " --redact Omit raw prefix/expected/actual snippets from the report",
159
+ " --limit=N Replay only the first N examples (faster smoke test)",
160
+ " -h Show this help",
161
+ "",
162
+ "Provider is built from the same PI_GHOST_* env vars the extension uses.",
163
+ "",
164
+ ].join("\n");
165
+ }
166
+ void main();
167
+ //# sourceMappingURL=replay-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay-cli.js","sourceRoot":"","sources":["../src/replay-cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAoB,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,YAAY,EAAoB,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAsB,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,eAAe,EAAyC,MAAM,aAAa,CAAC;AAErF,MAAM,YAAY,GAAG,mCAAmC,CAAC;AAEzD,KAAK,UAAU,IAAI;IAClB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QAC9B,OAAO;IACR,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC;IAErE,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACjD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,6FAA6F,CAC7F,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,MAAM,IAAI,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAE9D,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,MAAM,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAEzC,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5F,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,wBAAwB;AAEnD,KAAK,UAAU,SAAS,CAAC,QAAuB,EAAE,OAAoB;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,UAAU,CAAC,CAAC;IACnE,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE;YACvC,UAAU,EAAE,OAAO,CAAC,MAAM;YAC1B,sBAAsB,EAAE,OAAO,CAAC,MAAM;YACtC,gEAAgE;YAChE,gEAAgE;YAChE,+DAA+D;YAC/D,iCAAiC;YACjC,cAAc,EAAE,OAAO,CAAC,OAAO;gBAC9B,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;gBAC3C,CAAC,CAAC,EAAE;YACL,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,MAAM,EAAE,UAAU,CAAC,MAAM;SACzB,CAAC,CAAC;QACH,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACrC,MAAM,MAAM,GAAG,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,EAAE,CAAC;QAC9C,OAAO;YACN,OAAO;YACP,MAAM;YACN,SAAS;YACT,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;SACxD,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,YAAY,CAAC,SAAS,CAAC,CAAC;QACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC;QACrC,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;QACjG,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,iBAAiB,UAAU,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7G,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC3D,CAAC;AACF,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAmB;IACnD,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC;IAC1B,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACvB,MAAM,OAAO,GAAoB,EAAE,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;iBACtD,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe;gBAAE,OAAO,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;iBAC1E,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;;gBAC3D,MAAM,IAAI,KAAK,CAAC,0DAA2D,CAAsB,CAAC,IAAI,GAAG,CAAC,CAAC;QACjH,CAAC;QACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,OAAO,IAAI,YAAY,CAAC,OAAO,EAAE;YAChC,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;SACpB,CAAC,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,CAAC,IAAI,KAAK,eAAe;QAAE,OAAO,IAAI,oBAAoB,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,KAAK,CAAC,uDAAwD,CAAsB,CAAC,IAAI,GAAG,CAAC,CAAC;AACzG,CAAC;AAED,SAAS,YAAY,CACpB,YAAoB,EACpB,CAAgB,EAChB,OAAuB,EACvB,MAAe;IAEf,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,2BAA2B,YAAY,EAAE,CAAC,CAAC;IACtD,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACvD,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,cAAc,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACvE,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtE,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClE,KAAK,CAAC,IAAI,CACT,aAAa,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAClH,CAAC;IACF,KAAK,CAAC,IAAI,CACT,oBAAoB,CAAC,CAAC,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CACnJ,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9F,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;YACzF,KAAK,CAAC,IAAI,CACT,QAAQ,GAAG,aAAa,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,aAAa,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAC3I,CAAC;QACH,CAAC;IACF,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,CAAS;IACrC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,KAAK;IACb,OAAO;QACN,wDAAwD;QACxD,EAAE;QACF,4EAA4E;QAC5E,mDAAmD;QACnD,wEAAwE;QACxE,oEAAoE;QACpE,8BAA8B;QAC9B,EAAE;QACF,yEAAyE;QACzE,EAAE;KACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Replay engine — pure functions. PRD §M5c.
3
+ *
4
+ * Given an `EvalExample` and a candidate continuation produced by a
5
+ * provider variant under test, compute per-example quality metrics. The
6
+ * caller drives the actual provider invocation (this module stays I/O-free
7
+ * so it's trivially unit-testable and deterministic for fixture providers).
8
+ */
9
+ import type { EvalExample } from "./eval-dataset.js";
10
+ export interface ReplayResult {
11
+ /** Example replayed. */
12
+ example: EvalExample;
13
+ /** Top candidate produced by the provider variant. */
14
+ actual: string;
15
+ /** Latency in ms for this single call. */
16
+ latencyMs: number;
17
+ /** Optional non-top candidates for saved@k metrics. */
18
+ candidates?: string[];
19
+ /** Set when the provider call errored (e.g. rate-limit, timeout). */
20
+ error?: string;
21
+ }
22
+ export interface ExampleMetrics {
23
+ exactMatch: boolean;
24
+ prefixMatchLen: number;
25
+ editDistance: number;
26
+ /** saved@k where k is the candidate-rank threshold. */
27
+ savedAt1: number;
28
+ savedAt3: number;
29
+ savedAt5: number;
30
+ /**
31
+ * Enhancement #6 (ChaI-TeA): prefix-recall@k. 1 if any of the top-k
32
+ * candidates starts with the first identifier-or-punctuation chunk of
33
+ * the expected continuation, else 0. Stricter than saved@k for short
34
+ * tokens because it requires a literal prefix match on the very first
35
+ * token rather than a partial leading-char overlap.
36
+ */
37
+ prefixRecallAt1: 0 | 1;
38
+ prefixRecallAt3: 0 | 1;
39
+ prefixRecallAt5: 0 | 1;
40
+ }
41
+ /**
42
+ * Score one replay. `exactMatch` is the strict equality test;
43
+ * `prefixMatchLen` is the longest common leading-character run between
44
+ * the actual and the expected continuation; `editDistance` is plain
45
+ * Levenshtein. `savedAt{1,3,5}` is the PRD's `chars-saved / max(1, L)`
46
+ * over the top-k candidates.
47
+ */
48
+ export declare function evaluateExample(result: ReplayResult): ExampleMetrics;
49
+ /**
50
+ * First "identifier-or-punctuation" chunk of `s`. Mirrors the editor's
51
+ * `nextWordChunk` behavior without importing from ghost-editor (replay
52
+ * stays I/O-free). Skips leading whitespace, then either consumes an
53
+ * identifier run (`[A-Za-z0-9_]+`) or one punctuation char.
54
+ *
55
+ * Returns "" only when `s` has no non-whitespace content. The result
56
+ * INCLUDES the leading whitespace so that callers can `startsWith` the
57
+ * raw candidate against it without re-trimming.
58
+ */
59
+ export declare function firstTokenPrefix(s: string): string;
60
+ /**
61
+ * 1 if any of `candidates[0..n)` starts with `firstTokenPrefix(expected)`,
62
+ * else 0. PRD §M5b extension — ChaI-TeA's per-token recall metric. The
63
+ * binary 0/1 shape (vs. fractional `saved@k`) makes the rate interpretable
64
+ * directly as "fraction of expected first-tokens recovered."
65
+ */
66
+ export declare function prefixRecallAtN(candidates: string[], expected: string, n: number): 0 | 1;
67
+ /**
68
+ * Number of leading characters where `a` and `b` agree. Used as the
69
+ * `prefixMatchLen` quality metric (PRD §M5b).
70
+ */
71
+ export declare function leadingMatch(a: string, b: string): number;
72
+ /**
73
+ * `saved@k = max(0, charsSaved - acceptCost) / max(1, L)` per PRD §M5b,
74
+ * where `charsSaved` is the longest leading-match among ranks 1..k and
75
+ * `acceptCost = 1 + cycles` (one Right + cycle keystrokes). `k <= 0`
76
+ * returns 0; ranks beyond the array are skipped.
77
+ */
78
+ export declare function savedAtK(candidates: string[], expected: string, k: number): number;
79
+ /**
80
+ * Classic Levenshtein distance with single-row DP. O(n*m) time, O(min)
81
+ * space. Used by replay to flag near-matches in the bench.
82
+ */
83
+ export declare function levenshtein(a: string, b: string): number;
84
+ export interface ReplaySummary {
85
+ count: number;
86
+ errors: number;
87
+ exactMatchRate: number;
88
+ meanPrefixMatchLen: number;
89
+ meanEditDistance: number;
90
+ meanSavedAt1: number;
91
+ meanSavedAt3: number;
92
+ meanSavedAt5: number;
93
+ /** Enhancement #6 (ChaI-TeA): prefix-recall@k means over non-error replays. */
94
+ prefixRecallAt1: number;
95
+ prefixRecallAt3: number;
96
+ prefixRecallAt5: number;
97
+ p50LatencyMs: number;
98
+ p95LatencyMs: number;
99
+ }
100
+ /**
101
+ * Aggregate per-example metrics + latencies into a single report. Skips
102
+ * error results from quality metrics but includes them in `count`/`errors`
103
+ * for transparency.
104
+ */
105
+ export declare function summarizeReplay(results: ReplayResult[]): ReplaySummary;
106
+ export { quantile } from "./bench.js";
107
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,YAAY;IAC5B,wBAAwB;IACxB,OAAO,EAAE,WAAW,CAAC;IACrB,sDAAsD;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC;IACvB,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC;IACvB,eAAe,EAAE,CAAC,GAAG,CAAC,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAkBpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAalD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,CAUxF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBlF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAwBxD;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,aAAa,CA8CtE;AAuBD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}