pi-ghost-autocomplete 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +561 -0
  2. package/README.md +464 -236
  3. package/dist/bench-cli.js +1 -47
  4. package/dist/bench.d.ts +137 -5
  5. package/dist/bench.js +1 -241
  6. package/dist/config.d.ts +47 -1
  7. package/dist/context-aggregator.d.ts +18 -6
  8. package/dist/cycle-manager.d.ts +83 -0
  9. package/dist/entropy-stop.d.ts +105 -0
  10. package/dist/eval-dataset.d.ts +55 -0
  11. package/dist/fuzzy-matcher.d.ts +12 -1
  12. package/dist/ghost-editor.d.ts +332 -6
  13. package/dist/index.d.ts +21 -1
  14. package/dist/index.js +1 -210
  15. package/dist/intent.d.ts +179 -0
  16. package/dist/metrics.d.ts +103 -3
  17. package/dist/path-index.d.ts +4 -1
  18. package/dist/path-matcher.d.ts +13 -1
  19. package/dist/profile-store.d.ts +85 -0
  20. package/dist/profile.d.ts +167 -0
  21. package/dist/providers/cloud-provider.d.ts +14 -2
  22. package/dist/providers/local-provider.d.ts +12 -2
  23. package/dist/providers/openai-compat-provider.d.ts +35 -2
  24. package/dist/providers/race.d.ts +62 -2
  25. package/dist/providers/types.d.ts +109 -2
  26. package/dist/recent-paths.d.ts +4 -1
  27. package/dist/replay-cli.js +2 -0
  28. package/dist/replay.d.ts +102 -0
  29. package/package.json +90 -79
  30. package/dist/bench-cli.d.ts +0 -3
  31. package/dist/bench-cli.d.ts.map +0 -1
  32. package/dist/bench-cli.js.map +0 -1
  33. package/dist/bench.d.ts.map +0 -1
  34. package/dist/bench.js.map +0 -1
  35. package/dist/config.d.ts.map +0 -1
  36. package/dist/config.js +0 -187
  37. package/dist/config.js.map +0 -1
  38. package/dist/context-aggregator.d.ts.map +0 -1
  39. package/dist/context-aggregator.js +0 -58
  40. package/dist/context-aggregator.js.map +0 -1
  41. package/dist/fuzzy-matcher.d.ts.map +0 -1
  42. package/dist/fuzzy-matcher.js +0 -160
  43. package/dist/fuzzy-matcher.js.map +0 -1
  44. package/dist/ghost-editor.d.ts.map +0 -1
  45. package/dist/ghost-editor.js +0 -445
  46. package/dist/ghost-editor.js.map +0 -1
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/index.js.map +0 -1
  49. package/dist/metrics.d.ts.map +0 -1
  50. package/dist/metrics.js +0 -98
  51. package/dist/metrics.js.map +0 -1
  52. package/dist/path-index.d.ts.map +0 -1
  53. package/dist/path-index.js +0 -148
  54. package/dist/path-index.js.map +0 -1
  55. package/dist/path-matcher.d.ts.map +0 -1
  56. package/dist/path-matcher.js +0 -85
  57. package/dist/path-matcher.js.map +0 -1
  58. package/dist/providers/cloud-provider.d.ts.map +0 -1
  59. package/dist/providers/cloud-provider.js +0 -52
  60. package/dist/providers/cloud-provider.js.map +0 -1
  61. package/dist/providers/local-provider.d.ts.map +0 -1
  62. package/dist/providers/local-provider.js +0 -36
  63. package/dist/providers/local-provider.js.map +0 -1
  64. package/dist/providers/openai-compat-provider.d.ts.map +0 -1
  65. package/dist/providers/openai-compat-provider.js +0 -128
  66. package/dist/providers/openai-compat-provider.js.map +0 -1
  67. package/dist/providers/openai-completions-runner.d.ts +0 -28
  68. package/dist/providers/openai-completions-runner.d.ts.map +0 -1
  69. package/dist/providers/openai-completions-runner.js +0 -46
  70. package/dist/providers/openai-completions-runner.js.map +0 -1
  71. package/dist/providers/race.d.ts.map +0 -1
  72. package/dist/providers/race.js +0 -128
  73. package/dist/providers/race.js.map +0 -1
  74. package/dist/providers/truncate.d.ts +0 -7
  75. package/dist/providers/truncate.d.ts.map +0 -1
  76. package/dist/providers/truncate.js +0 -39
  77. package/dist/providers/truncate.js.map +0 -1
  78. package/dist/providers/types.d.ts.map +0 -1
  79. package/dist/providers/types.js +0 -2
  80. package/dist/providers/types.js.map +0 -1
  81. package/dist/recent-paths.d.ts.map +0 -1
  82. package/dist/recent-paths.js +0 -114
  83. package/dist/recent-paths.js.map +0 -1
@@ -0,0 +1,105 @@
1
+ /**
2
+ * SPDX-License-Identifier: SSPL-1.0
3
+ * SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
4
+ */
5
+ /**
6
+ * Entropy-based early stop. PRD §M7.
7
+ *
8
+ * Streams `(token, -logprob)` pairs; cuts when a token's `-logprob` spikes
9
+ * above a learned baseline AND the token is whitespace/punctuation
10
+ * (natural cut points). Returns the text up to (not including) the
11
+ * cutting token.
12
+ *
13
+ * Pure state machine — no I/O, no provider coupling. Caller drives the
14
+ * stream and acts on the returned `StopDecision`.
15
+ *
16
+ * Capability gate (PRD §M7):
17
+ * Mercury Edit FIM: no logprobs → caller skips this module
18
+ * OpenAI chat: supported
19
+ * Groq/Ollama: model-dependent
20
+ *
21
+ * Disabled when fewer than `warmup` tokens have been seen — no statistical
22
+ * baseline yet. Hard cap at `maxTokens` regardless.
23
+ */
24
+ export declare const DEFAULT_WARMUP_TOKENS = 5;
25
+ export declare const DEFAULT_SIGMA_MULT = 1.5;
26
+ export declare const DEFAULT_MAX_TOKENS = 64;
27
+ export interface EntropyStopperOpts {
28
+ /** Number of leading tokens used to seed the mean+std. */
29
+ warmupTokens?: number;
30
+ /** Sigma multiplier for the spike threshold. */
31
+ sigmaMult?: number;
32
+ /** Hard cap on total tokens observed before forced stop. */
33
+ maxTokens?: number;
34
+ }
35
+ export interface StopDecision {
36
+ /** True iff caller should stop streaming now. */
37
+ stop: boolean;
38
+ /**
39
+ * When `stop`, this is the reason. `entropy-spike` means a natural
40
+ * cut token tripped the threshold; `max-tokens` means the hard cap
41
+ * fired regardless of entropy.
42
+ */
43
+ reason?: "entropy-spike" | "max-tokens";
44
+ /**
45
+ * Running mean of `-logprob` after warmup. Useful for telemetry;
46
+ * undefined while still in warmup.
47
+ */
48
+ mean?: number;
49
+ /** Running standard deviation; undefined during warmup. */
50
+ std?: number;
51
+ }
52
+ export declare class EntropyStopper {
53
+ private readonly warmup;
54
+ private readonly sigma;
55
+ private readonly cap;
56
+ private readonly stats;
57
+ private tokensSeen;
58
+ constructor(opts?: EntropyStopperOpts);
59
+ /**
60
+ * Feed one streaming token + its `-logprob` (higher = more
61
+ * surprising / less confident). Returns whether the caller should
62
+ * stop the stream BEFORE this token. The caller's accumulated text
63
+ * up to (not including) this token is the "accept" boundary.
64
+ *
65
+ * Order of evaluation:
66
+ * 1. Hard cap: token index >= cap → stop
67
+ * 2. Warmup: token index < warmup → feed stats, no stop
68
+ * 3. Spike: -logprob > mean + sigma*std AND token is natural cut
69
+ * → stop
70
+ * 4. Else: feed stats, no stop
71
+ */
72
+ observe(token: string, neglogprob: number): StopDecision;
73
+ /**
74
+ * Diagnostic snapshot — count + current mean/std. Returns `null`
75
+ * mean/std until the warmup window has filled enough samples for a
76
+ * meaningful estimate.
77
+ */
78
+ snapshot(): {
79
+ tokensSeen: number;
80
+ mean: number | null;
81
+ std: number | null;
82
+ };
83
+ }
84
+ /**
85
+ * True iff `token` is a natural cut point — whitespace, end-of-clause
86
+ * punctuation, or a closing bracket. PRD §M7 limits entropy-driven
87
+ * cutoffs to these to avoid mid-identifier truncation.
88
+ */
89
+ export declare function isNaturalCutToken(token: string): boolean;
90
+ /**
91
+ * Run a stream through an entropy stopper. Returns the prefix of joined
92
+ * tokens up to (but not including) the stop point. When the stream ends
93
+ * naturally without a stop, returns the full concatenation.
94
+ *
95
+ * Exposed as a convenience for tests + future provider wiring; editor
96
+ * integration is deferred until pi-ai exposes per-token logprob events.
97
+ */
98
+ export declare function runWithStop(tokens: Iterable<{
99
+ token: string;
100
+ neglogprob: number;
101
+ }>, opts?: EntropyStopperOpts): {
102
+ text: string;
103
+ stopped: boolean;
104
+ reason?: "entropy-spike" | "max-tokens";
105
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * SPDX-License-Identifier: SSPL-1.0
3
+ * SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
4
+ */
5
+ export declare const EVAL_REL = ".pi/ghost-autocomplete/eval.jsonl";
6
+ /** Char positions at which to sample prefixes from a committed line. */
7
+ export declare const PREFIX_SAMPLE_POSITIONS: readonly number[];
8
+ /** Beyond `LONG_LINE_START`, sample every `LONG_LINE_STEP` chars. */
9
+ export declare const LONG_LINE_START = 20;
10
+ export declare const LONG_LINE_STEP = 10;
11
+ export interface EvalExample {
12
+ ts: number;
13
+ /** Single-line committed text the sample was drawn from. */
14
+ committed: string;
15
+ /** Char-position sample point on `committed`. */
16
+ prefixLen: number;
17
+ /** Substring `committed[0..prefixLen]` — the LLM input. */
18
+ prefix: string;
19
+ /** Substring `committed[prefixLen..end]` — the ground truth continuation. */
20
+ expected: string;
21
+ /** Provider context (renderable). May be the empty string. */
22
+ context: string;
23
+ /** Provider/version fingerprints active at capture. */
24
+ providerFingerprint: string;
25
+ versionFingerprint: string;
26
+ /** Provider name (e.g. "ghost-mercury-edit"). */
27
+ providerName: string;
28
+ }
29
+ /**
30
+ * Generate sample (prefix, expected) splits from a committed line. PRD
31
+ * §M5c: positions `[3, 5, 8, 12, 20]` plus every 10 chars beyond 20.
32
+ * Returns empty when the committed text is too short for any sample.
33
+ */
34
+ export declare function samplePrefixes(committed: string): Array<{
35
+ prefixLen: number;
36
+ prefix: string;
37
+ expected: string;
38
+ }>;
39
+ /**
40
+ * Build eval examples for a single commit. Drops sensitive messages
41
+ * entirely. `context` should already be rendered by the caller (the
42
+ * provider's recent-messages block joined into a single string) — keeps
43
+ * this module decoupled from provider context shape.
44
+ */
45
+ export declare function buildExamplesFromCommit(committed: string, meta: {
46
+ context: string;
47
+ providerFingerprint: string;
48
+ versionFingerprint: string;
49
+ providerName: string;
50
+ }, ts?: number): EvalExample[];
51
+ export declare function appendExamples(cwd: string, examples: EvalExample[]): Promise<void>;
52
+ export declare function loadExamples(cwd: string): Promise<EvalExample[]>;
53
+ export declare function parseExamples(body: string): EvalExample[];
54
+ /** Parse `PI_GHOST_EVAL_CAPTURE`. Returns true only when set to "1". */
55
+ export declare function parseEvalCaptureEnv(raw: string | undefined): boolean;
@@ -1,6 +1,18 @@
1
+ /**
2
+ * SPDX-License-Identifier: SSPL-1.0
3
+ * SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
4
+ */
1
5
  import type { PathIndex } from "./path-index.js";
2
6
  export interface FindFuzzyOptions {
3
7
  recentPaths?: Set<string> | null;
8
+ /**
9
+ * PRD §M4 — bounded persistent-path boost. Caller wires
10
+ * `(path) => profileStore.profilePathBoost(path)` which returns
11
+ * `[0, 0.2]`. Multiplied by 10 to give it integer-scale weight (max
12
+ * +2) that nudges fuzzy tie-breakers without overriding subtree (+5)
13
+ * or short-path (+5) bonuses.
14
+ */
15
+ profileBoost?: ((path: string) => number) | null;
4
16
  }
5
17
  export type FuzzyBand = "basename-prefix" | "fuzzy-basename" | "fuzzy-path";
6
18
  export interface FuzzyToken {
@@ -47,4 +59,3 @@ export declare function hasActionTrigger(prefix: string): boolean;
47
59
  * 70-84). Scores < 70 should not be shown deterministically.
48
60
  */
49
61
  export declare function findFuzzyFilename(index: PathIndex, token: string, opts?: FindFuzzyOptions): FuzzyMatch | null;
50
- //# sourceMappingURL=fuzzy-matcher.d.ts.map
@@ -1,46 +1,373 @@
1
+ /**
2
+ * SPDX-License-Identifier: SSPL-1.0
3
+ * SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
4
+ */
1
5
  import { CustomEditor, type ExtensionContext } from "@earendil-works/pi-coding-agent";
2
6
  import type { KeybindingsManager } from "@earendil-works/pi-coding-agent";
3
7
  import { type EditorTheme, type TUI } from "@earendil-works/pi-tui";
4
- import type { GhostConfig } from "./config.js";
8
+ import type { GhostConfig, ProviderConfig } from "./config.js";
9
+ import { type RecentMessage } from "./context-aggregator.js";
5
10
  import type { MetricsLogger } from "./metrics.js";
6
11
  import type { PathIndex } from "./path-index.js";
7
- import type { GhostProvider } from "./providers/types.js";
12
+ import type { ProfileStore } from "./profile-store.js";
13
+ export { cycleCandidateIdx, nextWordChunk } from "./cycle-manager.js";
14
+ import { type GhostProvider } from "./providers/types.js";
15
+ /**
16
+ * #5 helper — classify a stale event as divergent or typed-through given
17
+ * the ghost state at render time and the post-change text. Returns true
18
+ * when the user typed characters that are NOT a prefix of the suggested
19
+ * ghost. Pure / exported for testability — the editor wraps this in
20
+ * `recordDivergentTyped` to track the consecutive run.
21
+ */
22
+ export declare function isTypedPastDivergent(ghost: string, ghostForText: string, textAfterChange: string): boolean;
23
+ /** Parse `PI_GHOST_SHORT_HOVER_MS`. Falls back to the default on invalid
24
+ * or non-positive input. Exposed for tests; called once per dismiss. */
25
+ export declare function parseShortHoverEnv(raw: string | undefined): number;
8
26
  export declare class GhostEditor extends CustomEditor {
9
27
  private provider;
10
28
  private readonly config;
11
29
  private readonly extCtx;
12
30
  private readonly metrics;
13
31
  private readonly onError;
14
- private pathIndex;
32
+ private profile;
15
33
  private ghostText;
16
34
  private ghostForText;
35
+ private readonly keystrokeTimer;
36
+ /**
37
+ * Tracks consecutive divergent stales on the same line for the #5
38
+ * typed-past negative signal. Reset by any accept or a non-divergent
39
+ * (i.e., user-typed-through) stale. Carries the original `(prefix,
40
+ * ghost)` so a single penalty at threshold can be applied against the
41
+ * pair that was actually being typed-past.
42
+ */
43
+ private divergentRun;
17
44
  private ghostReplaceFrom;
18
45
  private debounceTimer;
19
46
  private inflight;
20
47
  private lastTextSeen;
48
+ /** Last classified intent, set during handleTextChange and consumed
49
+ * by buildPredictionContext when the LLM fires after debounce. */
50
+ private currentIntent;
51
+ /** Ring buffer of recently-dismissed deterministic completions,
52
+ * used to suppress re-showing the same path in the same context. */
53
+ private readonly dismissCache;
21
54
  private disposed;
22
55
  private shownMetric;
23
56
  private recentPaths;
24
57
  private recentPathsAt;
25
- constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, extCtx: ExtensionContext, config: GhostConfig, provider: GhostProvider | null, metrics?: MetricsLogger | null, pathIndex?: PathIndex | null, onError?: ((err: Error) => void) | null);
58
+ private readonly fastPath;
59
+ private readonly providerFingerprint;
60
+ private readonly versionFingerprint;
61
+ /** Tracks the prefix shown alongside the current ghost so commit-time
62
+ * reconciliation can decide accepted vs divergent. */
63
+ private lastAcceptedGhost;
64
+ /** PRD §M3e/f — CycleManager owns the ranked candidate list, cycling (Alt+]/Alt+[),
65
+ * word-level partial accepts (Ctrl+Right), and related accounting. */
66
+ private readonly cycleMgr;
67
+ /** PRD §M5a — regret detection after accept. Pending while the
68
+ * window is open; flushed on regret or deadline. */
69
+ private pendingRegret;
70
+ private regretTimer;
71
+ constructor(tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager, extCtx: ExtensionContext, config: GhostConfig, provider: GhostProvider | null, metrics?: MetricsLogger | null, pathIndex?: PathIndex | null, onError?: ((err: Error) => void) | null, profile?: ProfileStore | null);
26
72
  setPathIndex(index: PathIndex | null): void;
73
+ setProfile(profile: ProfileStore | null): void;
74
+ /**
75
+ * PRD §M3g — diagnostic snapshot for the `/ghost` command. Pure read,
76
+ * no side effects. Numbers are point-in-time; the editor doesn't keep
77
+ * a running window. `cooldownMs` is reported when the active provider
78
+ * is a `RaceProvider`; otherwise omitted.
79
+ */
80
+ getDiagnostics(): GhostDiagnostics;
81
+ /**
82
+ * Build the edit-history section of `/ghost` diag. Snapshot is taken at
83
+ * call time; `topAdded` / `topRemoved` expose token *lists only* (no
84
+ * raw text — privacy contract from spec §6.5).
85
+ */
86
+ private buildEditHistoryDiag;
27
87
  setProvider(provider: GhostProvider | null): void;
28
88
  dispose(): void;
29
89
  handleInput(data: string): void;
90
+ /**
91
+ * Right Arrow → full ghost accept (append-mode appends, replace-mode
92
+ * replaces the typed token). Seeds the trie + profile with the accepted
93
+ * (prefix, ghost) pair for append-mode only — replace-mode ghosts are
94
+ * structurally incompatible with the trie's append model.
95
+ */
96
+ private handleFullAccept;
97
+ /**
98
+ * PRD §M3f — Ctrl+Right consumes the next word of the ghost.
99
+ * Append-mode only; replace-mode ghosts (fuzzy/path) accept atomically
100
+ * via Right. Returns true iff the keystroke was claimed (chunk inserted
101
+ * or final-chunk path taken). A no-op move (empty chunk) falls through.
102
+ */
103
+ private handlePartialWordAccept;
104
+ /**
105
+ * PRD §M3e — Alt+]/Alt+[ cycle through ranked candidates. Append-mode
106
+ * only and only when more than one candidate exists. Clamp at edges
107
+ * (no wrap). An edge press is a silent no-op: ghost is preserved
108
+ * (see `maybeDismissGhost`) and cycledCount is NOT incremented since
109
+ * the user didn't actually cycle.
110
+ */
111
+ private handleCycle;
112
+ /**
113
+ * Keys we explicitly own preserve the ghost; everything else dismisses.
114
+ * Alt+]/Alt+[ always preserve (match VS Code: alt+] on a single
115
+ * candidate is a silent no-op, ghost stays). Ctrl+Right preserves
116
+ * ONLY in append-mode — for replace-mode (fuzzy/path) ghosts,
117
+ * Ctrl+Right is normal cursor word-right nav, so the ghost should
118
+ * dismiss before the parent editor moves the cursor.
119
+ */
120
+ private maybeDismissGhost;
121
+ /**
122
+ * Observe post-`super.handleInput` text change: feed the keystroke timer
123
+ * (single-char insertions only), check pending regret, and dispatch
124
+ * to `handleTextChange` for the next prediction cycle.
125
+ */
126
+ private observePostInputTextChange;
127
+ /**
128
+ * Enhancement #1 — capture an edit event into the FastPathEngine's
129
+ * `EditHistoryRing`. Diffs `prev` vs `next` to derive the minimal
130
+ * `{added, removed}` slice, sanitizes both sides (event dropped on a
131
+ * sensitive hit), tokenizes, and records.
132
+ *
133
+ * No-op when `PI_GHOST_EDIT_HISTORY=0` (master switch read at engine
134
+ * construction). Single-buffer URI sentinel `"prompt"` — the editor
135
+ * owns one document.
136
+ */
137
+ private recordEditEvent;
138
+ /**
139
+ * Build the editProximity scorer bound to the current cursor + a
140
+ * fresh `EditHistoryIndex` snapshot. Returns `null` when edit-history
141
+ * is disabled OR the ring is empty (no signal to surface). Captured
142
+ * per-prediction-cycle so the scorer is consistent across all
143
+ * candidates in a single rerank.
144
+ */
145
+ private buildEditProximityScorer;
30
146
  render(width: number): string[];
31
147
  private cursorAtEnd;
32
148
  private shouldShowGhost;
33
149
  private clearGhost;
34
150
  private cancelInflight;
151
+ /**
152
+ * #5 — track consecutive divergent stales on the same prefix family.
153
+ * A non-divergent (i.e., typed-through) stale resets the run because
154
+ * the user was on-track with the suggestion. At threshold we apply a
155
+ * trie penalty against the original `(prefix, ghost)` and stamp the
156
+ * current `shownMetric` so the resulting stale record carries the
157
+ * `typedPastDivergent` flag. Only one penalty per run — the run resets
158
+ * so a long string of divergent typing doesn't double-charge.
159
+ */
160
+ private recordDivergentTyped;
35
161
  private handleTextChange;
36
- private tryDeterministic;
162
+ /**
163
+ * Extension host hook: notify the editor that a user prompt has been
164
+ * committed (Enter pressed, message submitted). Seeds the session trie
165
+ * with prefix→remainder shingles from the committed text and reconciles
166
+ * any in-flight accepted ghost against the actual commit (for divergent
167
+ * dismiss accounting in a future iteration).
168
+ *
169
+ * Sanitization happens inside the trie. The hook is safe to call with raw
170
+ * user text — secrets are filtered before insertion.
171
+ */
172
+ onPromptCommitted(text: string): void;
173
+ /**
174
+ * Single-line prefix at the cursor, used as the trie key. Stripping
175
+ * earlier lines keeps trie entries small and keeps "prefix" semantics
176
+ * consistent across single-line commits vs multi-line edits.
177
+ */
178
+ private lastSingleLinePrefix;
179
+ /**
180
+ * Assemble the recent-message context for a prediction. Combines:
181
+ * - M6a: opt-in tool entries via `getRecentMessages({ includeTools })`
182
+ * - M6c: type-aware summarization + cap on tool count
183
+ * - M6b: relevance-pruned selection by BM25 + recency
184
+ *
185
+ * Returns oldest-first messages suitable for provider rendering. Sized
186
+ * by the M6d env knobs; falls back to `config.provider.maxRecentMessages`
187
+ * when context scanning is effectively disabled.
188
+ */
189
+ private buildSelectedContext;
190
+ private textBeforeCursor;
191
+ private applyServedGhost;
192
+ /**
193
+ * Shared ghost-state mutation used by every "show this ghost now" path
194
+ * (served, deterministic). Sets the 7 ghost-related fields atomically.
195
+ * `replaceFrom` is null for append-mode ghosts; non-null for fuzzy/path
196
+ * replace-mode. Does NOT touch `shownMetric` — callers build that with
197
+ * source-specific fields.
198
+ */
199
+ private setShownGhost;
200
+ /**
201
+ * PRD §M4 — slash-command fast-path. Runs before the minChars gate and
202
+ * before the provider check: profile-local lookup, no LLM, useful even
203
+ * at 1-char prefixes (`/`). Returns true when a slash-completion was
204
+ * served. Profile must exist; line must start with `/`.
205
+ */
206
+ private trySlashFastPath;
207
+ private tryDeterministicWithLines;
37
208
  private getRecentPaths;
38
209
  private applyDeterministic;
39
210
  private replaceTokenAtCursor;
211
+ /**
212
+ * PRD §M3.5b — arm a provisional ghost. Engine owns the timer + env
213
+ * gate + picker construction (uses its own trie/lastGhosts plus the
214
+ * supplied profile). Editor supplies the request-scoped `canRender`
215
+ * predicate and the `onShow` UI mutator — both close over `ctrl` +
216
+ * `textAtSchedule` so they stay consistent with the in-flight request.
217
+ */
218
+ private armProvisional;
40
219
  private requestPrediction;
220
+ /**
221
+ * Refresh context + fingerprints + build the provider request bag at
222
+ * fire-time. Session messages may have arrived during the debounce
223
+ * window; the cache key we persist must match what the provider actually
224
+ * sees, otherwise a later identical keystroke would miss its own cached
225
+ * result. Pure context construction — no LLM call.
226
+ */
227
+ private buildPredictionContext;
228
+ /**
229
+ * Post-provider-call disposition: stamp TTFT, run the post-flight gate,
230
+ * sanitize empty results, rerank, apply confidence gate, reconcile with
231
+ * any displayed provisional, and commit. Each early-return emits the
232
+ * appropriate metric; the happy path lands in `commitDisplayedGhost`.
233
+ *
234
+ * Extracted from `requestPrediction` as part of refactor #57 phase 1 so
235
+ * the orchestrator stays a thin sequence (build → await → finalize).
236
+ */
237
+ private finalizeProviderResult;
238
+ /**
239
+ * Centralized post-provider-call disposition. Returns `null` when the
240
+ * result is good to render; otherwise the abort reason the caller should
241
+ * report via `emitMetric`. Clears `this.inflight` only on the happy path
242
+ * (matches the prior in-line behavior at the first non-aborted check) so
243
+ * an `aborted` return leaves `inflight` for the catch / next request to
244
+ * deal with.
245
+ */
246
+ private postFlightCheck;
247
+ /**
248
+ * PRD §M3.5a — invoke the provider in either streaming or multi-candidate
249
+ * mode and (when streaming) drive incremental UI paint via `onChunk`.
250
+ * Stamps TTFT on the first stream chunk and returns it for the caller to
251
+ * attach to the pending metric.
252
+ *
253
+ * - Streaming path: provider exposes `predictStream` AND lacks
254
+ * `predictMulti`. Used by Mercury FIM streaming + LocalProvider.
255
+ * - Multi path: everything else (openai-compat FIM, race with sync
256
+ * members). `onChunk` is ignored.
257
+ *
258
+ * Extracted from `requestPrediction` as part of refactor #57 so the
259
+ * orchestrator stays focused on rerank / reconcile / commit.
260
+ */
261
+ private runPredictionPath;
262
+ /**
263
+ * Commit a successfully-reconciled LLM ghost to all on-screen + fast-path
264
+ * structures: ranked candidate list, ghost text, cache, speculative ring,
265
+ * and the pending metric record. Extracted from `requestPrediction` as
266
+ * part of refactor #57 to keep the orchestrator readable.
267
+ *
268
+ * Cache + speculative ring store the *reranked* top — a subsequent cache
269
+ * hit must reproduce what the user actually saw, not the provider's
270
+ * pre-rerank order.
271
+ */
272
+ private commitDisplayedGhost;
41
273
  private emitMetric;
42
274
  private finalizeShown;
275
+ /**
276
+ * Detach + clear the pending-regret watch (handle + timer) atomically
277
+ * and return the captured handle. Callers decide how to resolve the
278
+ * outcome (`observeRegret`, `expireRegret`, or a forced no-regret on
279
+ * Enter-commit) and log the resulting record.
280
+ *
281
+ * Returns null when no watch was armed — callers can short-circuit.
282
+ */
283
+ private takePendingRegret;
284
+ private resolvePendingRegret;
285
+ /**
286
+ * Flush any pending regret-watched record. Called by the timer, by a
287
+ * later accept that supersedes the prior window, or by dispose.
288
+ * Detects regret against the *current* editor text via `expireRegret`
289
+ * so the final record reflects whatever the user did in the window.
290
+ */
291
+ private flushRegret;
43
292
  }
293
+ /**
294
+ * Render a recent-messages array into a single string for eval capture
295
+ * (PRD §M5c). Format mirrors the provider context shape — `role: text`
296
+ * joined by newlines — so a CLI replay can feed it back to the same
297
+ * provider. Tool entries get a `tool ${name}:` label.
298
+ */
299
+ export declare function renderContextForEval(recent: RecentMessage[]): string;
300
+ export interface GhostDiagnostics {
301
+ provider: string | null;
302
+ providerFingerprint: string;
303
+ versionFingerprint: string;
304
+ /** PRD §M3g — capability flags detected from the active provider. */
305
+ capabilities: {
306
+ streaming: boolean;
307
+ multiCandidate: boolean;
308
+ preflight: boolean;
309
+ /** PRD §M7 — provider populates per-token `-logprob` on stream events. */
310
+ logprobs: boolean;
311
+ };
312
+ cache: {
313
+ size: number;
314
+ hits: number;
315
+ misses: number;
316
+ hitRate: number;
317
+ };
318
+ trie: {
319
+ prefixes: number;
320
+ candidates: number;
321
+ insertsSinceDecay: number;
322
+ };
323
+ profile: {
324
+ enabled: boolean;
325
+ ngrams: number;
326
+ paths: number;
327
+ slash: number;
328
+ accepts: number;
329
+ decayDays: number;
330
+ };
331
+ race: {
332
+ cooldown: {
333
+ active: boolean;
334
+ remainingMs: number;
335
+ };
336
+ members: Array<{
337
+ name: string;
338
+ slowCount: number;
339
+ wins: number;
340
+ skipRemaining: number;
341
+ }>;
342
+ } | null;
343
+ ghostShown: boolean;
344
+ /** PRD §M5a — regret window state. `null` when no accept is being
345
+ * watched; otherwise the remaining ms until the window closes. */
346
+ pendingRegret: {
347
+ remainingMs: number;
348
+ insertedLen: number;
349
+ } | null;
350
+ /**
351
+ * Enhancement #1 — edit-history snapshot for `/ghost` diag. `enabled`
352
+ * mirrors `PI_GHOST_EDIT_HISTORY`; the remaining fields are populated
353
+ * even when disabled (they'll just report zeros).
354
+ */
355
+ editHistory: {
356
+ enabled: boolean;
357
+ events: number;
358
+ capacity: number;
359
+ uris: number;
360
+ topAdded: string[];
361
+ topRemoved: string[];
362
+ oldestAgeMs: number | null;
363
+ halfLifeMs: number;
364
+ };
365
+ }
366
+ /**
367
+ * Build a stable provider fingerprint from the config. For race configs, fold
368
+ * each member into the `extras` map so member rotation invalidates cache.
369
+ */
370
+ export declare function providerFingerprintFromConfig(cfg: ProviderConfig): string;
44
371
  /**
45
372
  * Pure helper: replace the slice `[from.col, end-of-line]` on `from.line` of
46
373
  * `text` with `replacement`. Exported for testability.
@@ -62,4 +389,3 @@ export declare function visualWidth(s: string): number;
62
389
  * Returns the empty string if `maxCols <= 0` or no characters fit.
63
390
  */
64
391
  export declare function clipToWidth(s: string, maxCols: number): string;
65
- //# sourceMappingURL=ghost-editor.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,18 @@
1
+ /**
2
+ * SPDX-License-Identifier: SSPL-1.0
3
+ * SPDX-FileCopyrightText: 2025 Alessandro Pungitore <alessandro@pungitore.ch>
4
+ */
1
5
  import type { ExtensionFactory } from "@earendil-works/pi-coding-agent";
6
+ import { type GhostConfig } from "./config.js";
7
+ import type { GhostDiagnostics } from "./ghost-editor.js";
2
8
  declare const ghost: ExtensionFactory;
9
+ /**
10
+ * PRD §M3g — render the diagnostic snapshot from `GhostEditor.getDiagnostics`
11
+ * alongside the provider config. The output is a single notify-friendly
12
+ * string; sections are bullet-indented two spaces deep so they nest under
13
+ * the leading "Pi Ghost diagnostics" header.
14
+ */
15
+ export declare function describeDiagnostics(config: GhostConfig, diag: GhostDiagnostics | null): string;
3
16
  export default ghost;
4
17
  export { CloudProvider } from "./providers/cloud-provider.js";
5
18
  export { LocalProvider } from "./providers/local-provider.js";
@@ -8,9 +21,17 @@ export type { PreflightResult } from "./providers/openai-compat-provider.js";
8
21
  export { RaceProvider, firstNonNull } from "./providers/race.js";
9
22
  export type { RaceConfig, RaceTrace } from "./providers/race.js";
10
23
  export { GhostEditor, computeReplacement } from "./ghost-editor.js";
24
+ export type { GhostDiagnostics } from "./ghost-editor.js";
11
25
  export type { GhostConfig, ProviderConfig, CloudProviderConfig, LocalProviderConfig, OpenAICompatProviderConfig, RaceProviderConfig } from "./config.js";
12
26
  export type { GhostProvider, GhostRequest } from "./providers/types.js";
13
27
  export { MetricsLogger, readMetricsConfigFromEnv } from "./metrics.js";
28
+ export { ProfileStore } from "./profile-store.js";
29
+ export { appendExamples, buildExamplesFromCommit, loadExamples, parseEvalCaptureEnv, parseExamples, samplePrefixes, EVAL_REL, } from "./eval-dataset.js";
30
+ export type { EvalExample } from "./eval-dataset.js";
31
+ export { evaluateExample, leadingMatch, levenshtein, savedAtK, summarizeReplay, } from "./replay.js";
32
+ export type { ExampleMetrics, ReplayResult, ReplaySummary } from "./replay.js";
33
+ export { DEFAULT_MAX_TOKENS as ENTROPY_DEFAULT_MAX_TOKENS, DEFAULT_SIGMA_MULT as ENTROPY_DEFAULT_SIGMA_MULT, DEFAULT_WARMUP_TOKENS as ENTROPY_DEFAULT_WARMUP_TOKENS, EntropyStopper, isNaturalCutToken, runWithStop, } from "./entropy-stop.js";
34
+ export type { EntropyStopperOpts, StopDecision } from "./entropy-stop.js";
14
35
  export type { MetricsRecord, MetricsConfig, RequestResult, AcceptanceOutcome, ProviderLatency, RejectionReason, CompletionMode } from "./metrics.js";
15
36
  export { buildPathIndex } from "./path-index.js";
16
37
  export type { PathIndex } from "./path-index.js";
@@ -22,4 +43,3 @@ export type { FindPathPrefixOptions } from "./path-matcher.js";
22
43
  export { extractRecentPaths, getRoleWeightedRecent, recentBoost } from "./recent-paths.js";
23
44
  export { formatReport, loadMetrics, parseMetricsText, quantile, runBenchmark, summarize, THRESHOLDS, } from "./bench.js";
24
45
  export type { BenchmarkReport, ModeStats, ProviderStats } from "./bench.js";
25
- //# sourceMappingURL=index.d.ts.map