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,102 @@
1
+ /**
2
+ * CycleManager — owns multi-candidate state and word-level partial-accept
3
+ * gestures for the GREAT Ghost autocomplete.
4
+ *
5
+ * PRD §M3e (Alt+]/Alt+[ cycling with clamp-at-edges, no wrap) +
6
+ * PRD §M3f (Ctrl+Right word-by-word acceptance in append mode).
7
+ *
8
+ * Design notes:
9
+ *
10
+ * - Candidate list is stored read-only (frozen) so that future refactors
11
+ * don't accidentally mutate ranked text. Per-candidate consumption from
12
+ * word-level accepts is tracked separately in `consumed[idx]`.
13
+ * - `current` returns `string | null` — `null` distinguishes "no candidates
14
+ * installed" from "current candidate is empty after full consumption".
15
+ * - `acceptPartialWordAppend()` is named to make the append-mode invariant
16
+ * impossible to forget at call sites. Replace-mode ghosts (fuzzy/path)
17
+ * accept atomically; word-by-word doesn't apply.
18
+ * - No hidden auto-reset: caller is responsible for `reset()` after the
19
+ * final-chunk accept so the lifecycle is explicit.
20
+ *
21
+ * Extracted as part of refactor #57 so `GhostEditor` stays thin.
22
+ */
23
+ /**
24
+ * Pure helper: compute the next index for Alt+]/Alt[+ cycle.
25
+ * Returns null when the move would go past an edge (no wrap, per PRD).
26
+ */
27
+ export declare function cycleCandidateIdx(currentIdx: number, len: number, dir: 1 | -1): number | null;
28
+ /**
29
+ * Extract the next lexical "word" chunk from the start of a ghost string.
30
+ * Skips leading whitespace, then either consumes an identifier run (`\w+`)
31
+ * OR a single non-word non-space char so code like `foo("bar")` accepts as
32
+ * `foo`, `(`, `"`, `bar`, `"`, `)` rather than gulping the whole
33
+ * expression. Used by Ctrl+Right partial accept.
34
+ *
35
+ * Returns the empty string when the ghost is empty or contains only
36
+ * whitespace — caller treats that as a no-op.
37
+ */
38
+ export declare function nextWordChunk(ghost: string): string;
39
+ /**
40
+ * CycleManager — small mutable facade over CycleState.
41
+ * All mutation goes through the manager so editor code stays declarative.
42
+ */
43
+ export declare class CycleManager {
44
+ private state;
45
+ reset(): void;
46
+ /**
47
+ * Install a fresh ranked candidate list for a newly shown ghost.
48
+ * Always resets indices, cycledCount, partialAccepts, and consumed
49
+ * offsets. The stored list is frozen — partial-accepts shrink the
50
+ * per-candidate `consumed` offset, never the underlying text.
51
+ */
52
+ setCandidates(texts: string[]): void;
53
+ /**
54
+ * Read-only view of the ranked candidate list as installed. Stable across
55
+ * partial accepts (consumption is tracked separately via `current`).
56
+ */
57
+ get candidates(): readonly string[];
58
+ /**
59
+ * Currently displayed ghost text — the un-consumed remainder of the
60
+ * candidate at `candidateIdx`. Returns `null` when no candidates are
61
+ * installed; returns `""` when the current candidate has been fully
62
+ * consumed by partial accepts (caller must then `reset()`).
63
+ */
64
+ get current(): string | null;
65
+ get cycledCount(): number;
66
+ get partialAccepts(): number;
67
+ get candidateIdx(): number;
68
+ /**
69
+ * Attempt a cycle in the given direction. Returns true if the ghost text
70
+ * actually changed (i.e., a legal non-edge move occurred). Each
71
+ * candidate retains its own `consumed` offset across cycles so the user
72
+ * can partial-accept on A, cycle to B, cycle back to A and resume.
73
+ */
74
+ cycle(dir: 1 | -1): boolean;
75
+ /**
76
+ * Perform a word-level partial accept on the currently displayed
77
+ * candidate. APPEND MODE ONLY — callers must check
78
+ * `!ghostReplaceFrom && cursorAtEnd()` before invoking. Naming
79
+ * deliberately includes `Append` so the invariant is impossible to
80
+ * forget at the call site.
81
+ *
82
+ * Returns the chunk that should be inserted now, plus the remaining
83
+ * ghost suffix (or `null` if this was the final chunk). When `remaining`
84
+ * is `null` the caller is responsible for finalizing and calling
85
+ * `reset()` — the manager does NOT auto-reset so lifecycle stays
86
+ * explicit.
87
+ */
88
+ acceptPartialWordAppend(): {
89
+ chunk: string;
90
+ remaining: string | null;
91
+ };
92
+ /**
93
+ * Snapshot for diagnostics / metrics (no raw candidate text leaked).
94
+ */
95
+ snapshot(): {
96
+ count: number;
97
+ idx: number;
98
+ cycled: number;
99
+ partials: number;
100
+ };
101
+ }
102
+ //# sourceMappingURL=cycle-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cycle-manager.d.ts","sourceRoot":"","sources":["../src/cycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AA4BH;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAK7F;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAInD;AAED;;;GAGG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,KAAK,CAA4B;IAEzC,KAAK,IAAI,IAAI;IAIb;;;;;OAKG;IACH,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAWpC;;;OAGG;IACH,IAAI,UAAU,IAAI,SAAS,MAAM,EAAE,CAElC;IAED;;;;;OAKG;IACH,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,CAI3B;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO;IAc3B;;;;;;;;;;;;OAYG;IACH,uBAAuB,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;IAgBtE;;OAEG;IACH,QAAQ,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;CAQ5E"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * CycleManager — owns multi-candidate state and word-level partial-accept
3
+ * gestures for the GREAT Ghost autocomplete.
4
+ *
5
+ * PRD §M3e (Alt+]/Alt+[ cycling with clamp-at-edges, no wrap) +
6
+ * PRD §M3f (Ctrl+Right word-by-word acceptance in append mode).
7
+ *
8
+ * Design notes:
9
+ *
10
+ * - Candidate list is stored read-only (frozen) so that future refactors
11
+ * don't accidentally mutate ranked text. Per-candidate consumption from
12
+ * word-level accepts is tracked separately in `consumed[idx]`.
13
+ * - `current` returns `string | null` — `null` distinguishes "no candidates
14
+ * installed" from "current candidate is empty after full consumption".
15
+ * - `acceptPartialWordAppend()` is named to make the append-mode invariant
16
+ * impossible to forget at call sites. Replace-mode ghosts (fuzzy/path)
17
+ * accept atomically; word-by-word doesn't apply.
18
+ * - No hidden auto-reset: caller is responsible for `reset()` after the
19
+ * final-chunk accept so the lifecycle is explicit.
20
+ *
21
+ * Extracted as part of refactor #57 so `GhostEditor` stays thin.
22
+ */
23
+ function emptyState() {
24
+ return {
25
+ originals: Object.freeze([]),
26
+ consumed: [],
27
+ candidateIdx: 0,
28
+ cycledCount: 0,
29
+ partialAccepts: 0,
30
+ };
31
+ }
32
+ /**
33
+ * Pure helper: compute the next index for Alt+]/Alt[+ cycle.
34
+ * Returns null when the move would go past an edge (no wrap, per PRD).
35
+ */
36
+ export function cycleCandidateIdx(currentIdx, len, dir) {
37
+ if (len <= 1)
38
+ return null;
39
+ const next = currentIdx + dir;
40
+ if (next < 0 || next >= len)
41
+ return null;
42
+ return next;
43
+ }
44
+ /**
45
+ * Extract the next lexical "word" chunk from the start of a ghost string.
46
+ * Skips leading whitespace, then either consumes an identifier run (`\w+`)
47
+ * OR a single non-word non-space char so code like `foo("bar")` accepts as
48
+ * `foo`, `(`, `"`, `bar`, `"`, `)` rather than gulping the whole
49
+ * expression. Used by Ctrl+Right partial accept.
50
+ *
51
+ * Returns the empty string when the ghost is empty or contains only
52
+ * whitespace — caller treats that as a no-op.
53
+ */
54
+ export function nextWordChunk(ghost) {
55
+ if (!ghost)
56
+ return "";
57
+ const m = /^\s*(\w+|[^\s\w])/.exec(ghost);
58
+ return m ? m[0] : "";
59
+ }
60
+ /**
61
+ * CycleManager — small mutable facade over CycleState.
62
+ * All mutation goes through the manager so editor code stays declarative.
63
+ */
64
+ export class CycleManager {
65
+ state = emptyState();
66
+ reset() {
67
+ this.state = emptyState();
68
+ }
69
+ /**
70
+ * Install a fresh ranked candidate list for a newly shown ghost.
71
+ * Always resets indices, cycledCount, partialAccepts, and consumed
72
+ * offsets. The stored list is frozen — partial-accepts shrink the
73
+ * per-candidate `consumed` offset, never the underlying text.
74
+ */
75
+ setCandidates(texts) {
76
+ const originals = Object.freeze(texts.length > 0 ? [...texts] : []);
77
+ this.state = {
78
+ originals,
79
+ consumed: new Array(originals.length).fill(0),
80
+ candidateIdx: 0,
81
+ cycledCount: 0,
82
+ partialAccepts: 0,
83
+ };
84
+ }
85
+ /**
86
+ * Read-only view of the ranked candidate list as installed. Stable across
87
+ * partial accepts (consumption is tracked separately via `current`).
88
+ */
89
+ get candidates() {
90
+ return this.state.originals;
91
+ }
92
+ /**
93
+ * Currently displayed ghost text — the un-consumed remainder of the
94
+ * candidate at `candidateIdx`. Returns `null` when no candidates are
95
+ * installed; returns `""` when the current candidate has been fully
96
+ * consumed by partial accepts (caller must then `reset()`).
97
+ */
98
+ get current() {
99
+ const o = this.state.originals[this.state.candidateIdx];
100
+ if (o === undefined)
101
+ return null;
102
+ return o.slice(this.state.consumed[this.state.candidateIdx] ?? 0);
103
+ }
104
+ get cycledCount() {
105
+ return this.state.cycledCount;
106
+ }
107
+ get partialAccepts() {
108
+ return this.state.partialAccepts;
109
+ }
110
+ get candidateIdx() {
111
+ return this.state.candidateIdx;
112
+ }
113
+ /**
114
+ * Attempt a cycle in the given direction. Returns true if the ghost text
115
+ * actually changed (i.e., a legal non-edge move occurred). Each
116
+ * candidate retains its own `consumed` offset across cycles so the user
117
+ * can partial-accept on A, cycle to B, cycle back to A and resume.
118
+ */
119
+ cycle(dir) {
120
+ const nextIdx = cycleCandidateIdx(this.state.candidateIdx, this.state.originals.length, dir);
121
+ if (nextIdx === null) {
122
+ return false;
123
+ }
124
+ this.state.candidateIdx = nextIdx;
125
+ this.state.cycledCount++;
126
+ return true;
127
+ }
128
+ /**
129
+ * Perform a word-level partial accept on the currently displayed
130
+ * candidate. APPEND MODE ONLY — callers must check
131
+ * `!ghostReplaceFrom && cursorAtEnd()` before invoking. Naming
132
+ * deliberately includes `Append` so the invariant is impossible to
133
+ * forget at the call site.
134
+ *
135
+ * Returns the chunk that should be inserted now, plus the remaining
136
+ * ghost suffix (or `null` if this was the final chunk). When `remaining`
137
+ * is `null` the caller is responsible for finalizing and calling
138
+ * `reset()` — the manager does NOT auto-reset so lifecycle stays
139
+ * explicit.
140
+ */
141
+ acceptPartialWordAppend() {
142
+ const cur = this.current;
143
+ if (cur === null || cur.length === 0) {
144
+ return { chunk: "", remaining: null };
145
+ }
146
+ const chunk = nextWordChunk(cur);
147
+ if (!chunk) {
148
+ return { chunk: "", remaining: null };
149
+ }
150
+ const idx = this.state.candidateIdx;
151
+ this.state.consumed[idx] = (this.state.consumed[idx] ?? 0) + chunk.length;
152
+ this.state.partialAccepts++;
153
+ const tail = cur.slice(chunk.length);
154
+ return { chunk, remaining: tail.length === 0 ? null : tail };
155
+ }
156
+ /**
157
+ * Snapshot for diagnostics / metrics (no raw candidate text leaked).
158
+ */
159
+ snapshot() {
160
+ return {
161
+ count: this.state.originals.length,
162
+ idx: this.state.candidateIdx,
163
+ cycled: this.state.cycledCount,
164
+ partials: this.state.partialAccepts,
165
+ };
166
+ }
167
+ }
168
+ //# sourceMappingURL=cycle-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cycle-manager.js","sourceRoot":"","sources":["../src/cycle-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Edit-history ring + aggregated index. Enhancement #1
3
+ * (Next-Edit-Prediction motivated). See `docs/edit-history-signals-spec.md`.
4
+ *
5
+ * Records the most recent K edits (default 32) made in-session, then
6
+ * exposes:
7
+ * - a `snapshot()` of newest-first events with age filtering, and
8
+ * - an `index()` of decayed token frequencies + per-URI line proximity.
9
+ *
10
+ * The reranker reads `index()` to compute `editProximityBoost`; the M6
11
+ * context selector reads it (opt-in) for a token-overlap locality bit.
12
+ *
13
+ * Privacy: events are pre-sanitized at capture (`containsSensitive` on
14
+ * the raw added/removed text — caller is responsible for invoking it
15
+ * before calling `record`). Only tokens + scalars + URIs survive. No
16
+ * raw text is ever stored on the ring. The ring lives in-process only —
17
+ * no persistence in v0.5.
18
+ */
19
+ export declare const TOKEN_CAP_PER_EVENT = 16;
20
+ export declare const DEFAULT_CAPACITY = 32;
21
+ export declare const DEFAULT_MAX_AGE_MS: number;
22
+ export declare const DEFAULT_HALF_LIFE_MS: number;
23
+ export interface EditEvent {
24
+ ts: number;
25
+ uri: string;
26
+ rangeStartLine: number;
27
+ addedTokens: string[];
28
+ removedTokens: string[];
29
+ netCharDelta: number;
30
+ }
31
+ export interface EditHistoryIndex {
32
+ addedFreq: Map<string, number>;
33
+ removedFreq: Map<string, number>;
34
+ recentUris: Set<string>;
35
+ lastEditLineByUri: Map<string, number>;
36
+ totalEvents: number;
37
+ }
38
+ export interface EditHistoryOpts {
39
+ capacity?: number;
40
+ maxAgeMs?: number;
41
+ halfLifeMs?: number;
42
+ }
43
+ /**
44
+ * Tokenize text using the same regex as `profile.ts` (`\b[\w][\w-]*\b`).
45
+ * Code identifiers are case-sensitive — we do NOT lowercase. Result is
46
+ * capped at `TOKEN_CAP_PER_EVENT` to bound memory per event.
47
+ */
48
+ export declare function tokenize(text: string): string[];
49
+ /**
50
+ * Exponential time-decay weight: 1.0 at `ageMs=0`, halves every
51
+ * `halfLifeMs`. Pure helper for tests + index().
52
+ */
53
+ export declare function timeDecayWeight(ageMs: number, halfLifeMs?: number): number;
54
+ export declare class EditHistoryRing {
55
+ private readonly events;
56
+ readonly capacity: number;
57
+ readonly maxAgeMs: number;
58
+ readonly halfLifeMs: number;
59
+ constructor(opts?: EditHistoryOpts);
60
+ /**
61
+ * Append an event. `ts` defaults to `Date.now()`. Caller is responsible
62
+ * for sanitization — the ring does not re-validate. Overflow drops the
63
+ * oldest event.
64
+ */
65
+ record(ev: Omit<EditEvent, "ts"> & {
66
+ ts?: number;
67
+ }): void;
68
+ /**
69
+ * Newest-first list of events not older than `maxAgeMs`. Returns a
70
+ * copy — safe for the caller to retain.
71
+ *
72
+ * Scans without early-break: we cannot assume `record()` was called in
73
+ * ts-monotonic order (a backfill or test clock could insert older
74
+ * events after newer ones). At capacity=32 the scan cost is trivial.
75
+ */
76
+ snapshot(now?: number): EditEvent[];
77
+ /**
78
+ * Aggregated index of recent edit signals, with exponential time-decay
79
+ * applied to token frequencies. URIs touched within `maxAgeMs` are
80
+ * collected; the *most recent* edit line per URI is kept (used by the
81
+ * reranker for line-proximity scoring).
82
+ */
83
+ index(now?: number): EditHistoryIndex;
84
+ clear(): void;
85
+ get size(): number;
86
+ }
87
+ /** Default per-entry size cap. 1 MB — large enough for any realistic
88
+ * source file, small enough that 16 × 1MB = 16MB worst-case is bounded. */
89
+ export declare const DEFAULT_PRE_EDIT_MAX_BYTES: number;
90
+ /**
91
+ * Per-document pre-edit text cache. Holds the most recent `capacity`
92
+ * document texts so we can derive the *removed* slice of an edit before
93
+ * the on-disk text mutates. LRU-bounded to avoid pinning every project
94
+ * file in RAM. v0.5 stores transiently in process only — never persisted.
95
+ *
96
+ * Per-entry size is capped at `maxBytesPerEntry` (default 1 MB). Texts
97
+ * exceeding the cap are stored as empty string — the next edit on that
98
+ * document will derive empty `removedTokens`, which is the same
99
+ * degradation as a fresh LRU miss. This bounds worst-case memory at
100
+ * `capacity * maxBytesPerEntry` regardless of project file sizes.
101
+ */
102
+ export declare class PreEditTextCache {
103
+ private readonly map;
104
+ readonly capacity: number;
105
+ readonly maxBytesPerEntry: number;
106
+ constructor(capacity?: number, maxBytesPerEntry?: number);
107
+ /** Returns the snapshot for `uri` (or `""` if absent) without touching LRU order. */
108
+ peek(uri: string): string;
109
+ /**
110
+ * Update the snapshot. Move-to-front semantics; oldest is evicted when
111
+ * capacity is exceeded. Texts exceeding `maxBytesPerEntry` are stored
112
+ * as empty string (the next edit on this URI gets no `removedTokens`,
113
+ * matching the LRU-miss degradation path).
114
+ *
115
+ * Byte length uses UTF-16 code units (`text.length`), not UTF-8 bytes —
116
+ * the cap is approximate but stable across machines.
117
+ */
118
+ update(uri: string, text: string): void;
119
+ clear(): void;
120
+ get size(): number;
121
+ }
122
+ /**
123
+ * Spec §5.1 — compute the raw `editProximityScore` for a candidate. Pure
124
+ * function. Returns 0 when the ring is empty (zero events). Output is
125
+ * un-normalized: the reranker's min-max normalization re-maps the value
126
+ * across the candidate set so absolute scale doesn't matter — only the
127
+ * relative ordering does. The same un-normalized value is reported on
128
+ * the `MetricRecord` for observability.
129
+ *
130
+ * `currentUri` / `currentLine` describe the user's cursor at predict
131
+ * time. The prompt-buffer editor uses a static `"prompt"` URI sentinel
132
+ * and the current cursor line.
133
+ */
134
+ export declare function editProximityScore(text: string, index: EditHistoryIndex, currentUri: string, currentLine: number): number;
135
+ /**
136
+ * Pure character-diff helper. Returns the minimal `{ added, removed }`
137
+ * slice between two strings by stripping the common prefix + suffix.
138
+ * Caller is responsible for `containsSensitive` checks before tokenizing
139
+ * the result. Returns empty strings for both sides when texts are equal.
140
+ */
141
+ export declare function diffStrings(prev: string, next: string): {
142
+ added: string;
143
+ removed: string;
144
+ };
145
+ export declare function readEditHistoryEnv(env?: Record<string, string | undefined>): Required<EditHistoryOpts> & {
146
+ enabled: boolean;
147
+ };
148
+ //# sourceMappingURL=edit-history-ring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-history-ring.d.ts","sourceRoot":"","sources":["../src/edit-history-ring.ts"],"names":[],"mappings":"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"}