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.
- package/README.md +101 -6
- package/dist/bench-cli.js +81 -5
- package/dist/bench-cli.js.map +1 -1
- package/dist/bench.d.ts +130 -2
- package/dist/bench.d.ts.map +1 -1
- package/dist/bench.js +285 -4
- package/dist/bench.js.map +1 -1
- package/dist/cache.d.ts +124 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +173 -0
- package/dist/cache.js.map +1 -0
- package/dist/candidate-rerank.d.ts +57 -0
- package/dist/candidate-rerank.d.ts.map +1 -0
- package/dist/candidate-rerank.js +78 -0
- package/dist/candidate-rerank.js.map +1 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +148 -18
- package/dist/config.js.map +1 -1
- package/dist/context-aggregator.d.ts +14 -5
- package/dist/context-aggregator.d.ts.map +1 -1
- package/dist/context-aggregator.js +38 -9
- package/dist/context-aggregator.js.map +1 -1
- package/dist/context-selector.d.ts +128 -0
- package/dist/context-selector.d.ts.map +1 -0
- package/dist/context-selector.js +300 -0
- package/dist/context-selector.js.map +1 -0
- package/dist/cycle-manager.d.ts +102 -0
- package/dist/cycle-manager.d.ts.map +1 -0
- package/dist/cycle-manager.js +168 -0
- package/dist/cycle-manager.js.map +1 -0
- package/dist/edit-history-ring.d.ts +148 -0
- package/dist/edit-history-ring.d.ts.map +1 -0
- package/dist/edit-history-ring.js +262 -0
- package/dist/edit-history-ring.js.map +1 -0
- package/dist/entropy-stop.d.ts +102 -0
- package/dist/entropy-stop.d.ts.map +1 -0
- package/dist/entropy-stop.js +141 -0
- package/dist/entropy-stop.js.map +1 -0
- package/dist/eval-dataset.d.ts +66 -0
- package/dist/eval-dataset.d.ts.map +1 -0
- package/dist/eval-dataset.js +127 -0
- package/dist/eval-dataset.js.map +1 -0
- package/dist/fast-path-engine.d.ts +286 -0
- package/dist/fast-path-engine.d.ts.map +1 -0
- package/dist/fast-path-engine.js +341 -0
- package/dist/fast-path-engine.js.map +1 -0
- package/dist/fuzzy-matcher.d.ts +8 -0
- package/dist/fuzzy-matcher.d.ts.map +1 -1
- package/dist/fuzzy-matcher.js +5 -3
- package/dist/fuzzy-matcher.js.map +1 -1
- package/dist/ghost-editor.d.ts +321 -4
- package/dist/ghost-editor.d.ts.map +1 -1
- package/dist/ghost-editor.js +1190 -144
- package/dist/ghost-editor.js.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +81 -2
- package/dist/index.js.map +1 -1
- package/dist/keystroke-timer.d.ts +59 -0
- package/dist/keystroke-timer.d.ts.map +1 -0
- package/dist/keystroke-timer.js +94 -0
- package/dist/keystroke-timer.js.map +1 -0
- package/dist/metrics-builder.d.ts +71 -0
- package/dist/metrics-builder.d.ts.map +1 -0
- package/dist/metrics-builder.js +71 -0
- package/dist/metrics-builder.js.map +1 -0
- package/dist/metrics.d.ts +99 -2
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +131 -17
- package/dist/metrics.js.map +1 -1
- package/dist/path-matcher.d.ts +9 -0
- package/dist/path-matcher.d.ts.map +1 -1
- package/dist/path-matcher.js +5 -2
- package/dist/path-matcher.js.map +1 -1
- package/dist/profile-store.d.ts +82 -0
- package/dist/profile-store.d.ts.map +1 -0
- package/dist/profile-store.js +199 -0
- package/dist/profile-store.js.map +1 -0
- package/dist/profile.d.ts +185 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +434 -0
- package/dist/profile.js.map +1 -0
- package/dist/providers/cloud-provider.d.ts +10 -1
- package/dist/providers/cloud-provider.d.ts.map +1 -1
- package/dist/providers/cloud-provider.js +71 -14
- package/dist/providers/cloud-provider.js.map +1 -1
- package/dist/providers/local-provider.d.ts +8 -1
- package/dist/providers/local-provider.d.ts.map +1 -1
- package/dist/providers/local-provider.js +15 -3
- package/dist/providers/local-provider.js.map +1 -1
- package/dist/providers/openai-compat-provider.d.ts +11 -1
- package/dist/providers/openai-compat-provider.d.ts.map +1 -1
- package/dist/providers/openai-compat-provider.js +93 -6
- package/dist/providers/openai-compat-provider.js.map +1 -1
- package/dist/providers/openai-completions-runner.d.ts +25 -1
- package/dist/providers/openai-completions-runner.d.ts.map +1 -1
- package/dist/providers/openai-completions-runner.js +109 -14
- package/dist/providers/openai-completions-runner.js.map +1 -1
- package/dist/providers/race.d.ts +58 -1
- package/dist/providers/race.d.ts.map +1 -1
- package/dist/providers/race.js +317 -4
- package/dist/providers/race.js.map +1 -1
- package/dist/providers/truncate.d.ts.map +1 -1
- package/dist/providers/truncate.js +20 -2
- package/dist/providers/truncate.js.map +1 -1
- package/dist/providers/types.d.ts +96 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +59 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/provisional-manager.d.ts +93 -0
- package/dist/provisional-manager.d.ts.map +1 -0
- package/dist/provisional-manager.js +98 -0
- package/dist/provisional-manager.js.map +1 -0
- package/dist/provisional.d.ts +150 -0
- package/dist/provisional.d.ts.map +1 -0
- package/dist/provisional.js +238 -0
- package/dist/provisional.js.map +1 -0
- package/dist/regret-tracker.d.ts +84 -0
- package/dist/regret-tracker.d.ts.map +1 -0
- package/dist/regret-tracker.js +145 -0
- package/dist/regret-tracker.js.map +1 -0
- package/dist/replay-cli.d.ts +13 -0
- package/dist/replay-cli.d.ts.map +1 -0
- package/dist/replay-cli.js +167 -0
- package/dist/replay-cli.js.map +1 -0
- package/dist/replay.d.ts +107 -0
- package/dist/replay.d.ts.map +1 -0
- package/dist/replay.js +225 -0
- package/dist/replay.js.map +1 -0
- package/dist/reranker.d.ts +181 -0
- package/dist/reranker.d.ts.map +1 -0
- package/dist/reranker.js +324 -0
- package/dist/reranker.js.map +1 -0
- package/dist/session-trie.d.ts +103 -0
- package/dist/session-trie.d.ts.map +1 -0
- package/dist/session-trie.js +280 -0
- package/dist/session-trie.js.map +1 -0
- package/dist/speculative.d.ts +74 -0
- package/dist/speculative.d.ts.map +1 -0
- package/dist/speculative.js +107 -0
- package/dist/speculative.js.map +1 -0
- package/dist/tool-summarize.d.ts +40 -0
- package/dist/tool-summarize.d.ts.map +1 -0
- package/dist/tool-summarize.js +245 -0
- package/dist/tool-summarize.js.map +1 -0
- package/dist/trie-sanitize.d.ts +22 -0
- package/dist/trie-sanitize.d.ts.map +1 -0
- package/dist/trie-sanitize.js +142 -0
- package/dist/trie-sanitize.js.map +1 -0
- package/dist/versions.d.ts +23 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +31 -0
- package/dist/versions.js.map +1 -0
- 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":"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"}
|
|
@@ -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":"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"}
|
package/dist/replay.d.ts
ADDED
|
@@ -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"}
|