pi-ghost-autocomplete 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +236 -0
  2. package/dist/bench-cli.d.ts +3 -0
  3. package/dist/bench-cli.d.ts.map +1 -0
  4. package/dist/bench-cli.js +48 -0
  5. package/dist/bench-cli.js.map +1 -0
  6. package/dist/bench.d.ts +93 -0
  7. package/dist/bench.d.ts.map +1 -0
  8. package/dist/bench.js +241 -0
  9. package/dist/bench.js.map +1 -0
  10. package/dist/config.d.ts +55 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +187 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/context-aggregator.d.ts +14 -0
  15. package/dist/context-aggregator.d.ts.map +1 -0
  16. package/dist/context-aggregator.js +58 -0
  17. package/dist/context-aggregator.js.map +1 -0
  18. package/dist/fuzzy-matcher.d.ts +50 -0
  19. package/dist/fuzzy-matcher.d.ts.map +1 -0
  20. package/dist/fuzzy-matcher.js +160 -0
  21. package/dist/fuzzy-matcher.js.map +1 -0
  22. package/dist/ghost-editor.d.ts +65 -0
  23. package/dist/ghost-editor.d.ts.map +1 -0
  24. package/dist/ghost-editor.js +445 -0
  25. package/dist/ghost-editor.js.map +1 -0
  26. package/dist/index.d.ts +25 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +210 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/metrics.d.ts +56 -0
  31. package/dist/metrics.d.ts.map +1 -0
  32. package/dist/metrics.js +98 -0
  33. package/dist/metrics.js.map +1 -0
  34. package/dist/path-index.d.ts +20 -0
  35. package/dist/path-index.d.ts.map +1 -0
  36. package/dist/path-index.js +148 -0
  37. package/dist/path-index.js.map +1 -0
  38. package/dist/path-matcher.d.ts +32 -0
  39. package/dist/path-matcher.d.ts.map +1 -0
  40. package/dist/path-matcher.js +85 -0
  41. package/dist/path-matcher.js.map +1 -0
  42. package/dist/providers/cloud-provider.d.ts +10 -0
  43. package/dist/providers/cloud-provider.d.ts.map +1 -0
  44. package/dist/providers/cloud-provider.js +52 -0
  45. package/dist/providers/cloud-provider.js.map +1 -0
  46. package/dist/providers/local-provider.d.ts +11 -0
  47. package/dist/providers/local-provider.d.ts.map +1 -0
  48. package/dist/providers/local-provider.js +36 -0
  49. package/dist/providers/local-provider.js.map +1 -0
  50. package/dist/providers/openai-compat-provider.d.ts +29 -0
  51. package/dist/providers/openai-compat-provider.d.ts.map +1 -0
  52. package/dist/providers/openai-compat-provider.js +128 -0
  53. package/dist/providers/openai-compat-provider.js.map +1 -0
  54. package/dist/providers/openai-completions-runner.d.ts +28 -0
  55. package/dist/providers/openai-completions-runner.d.ts.map +1 -0
  56. package/dist/providers/openai-completions-runner.js +46 -0
  57. package/dist/providers/openai-completions-runner.js.map +1 -0
  58. package/dist/providers/race.d.ts +40 -0
  59. package/dist/providers/race.d.ts.map +1 -0
  60. package/dist/providers/race.js +128 -0
  61. package/dist/providers/race.js.map +1 -0
  62. package/dist/providers/truncate.d.ts +7 -0
  63. package/dist/providers/truncate.d.ts.map +1 -0
  64. package/dist/providers/truncate.js +39 -0
  65. package/dist/providers/truncate.js.map +1 -0
  66. package/dist/providers/types.d.ts +18 -0
  67. package/dist/providers/types.d.ts.map +1 -0
  68. package/dist/providers/types.js +2 -0
  69. package/dist/providers/types.js.map +1 -0
  70. package/dist/recent-paths.d.ts +25 -0
  71. package/dist/recent-paths.d.ts.map +1 -0
  72. package/dist/recent-paths.js +114 -0
  73. package/dist/recent-paths.js.map +1 -0
  74. package/package.json +79 -0
package/dist/index.js ADDED
@@ -0,0 +1,210 @@
1
+ import { readConfigFromEnv } from "./config.js";
2
+ import { GhostEditor } from "./ghost-editor.js";
3
+ import { MetricsLogger, readMetricsConfigFromEnv } from "./metrics.js";
4
+ import { buildPathIndex } from "./path-index.js";
5
+ import { CloudProvider } from "./providers/cloud-provider.js";
6
+ import { LocalProvider } from "./providers/local-provider.js";
7
+ import { OpenAICompatProvider } from "./providers/openai-compat-provider.js";
8
+ import { RaceProvider } from "./providers/race.js";
9
+ const ghost = (pi) => {
10
+ const config = readConfigFromEnv();
11
+ pi.registerCommand("ghost", {
12
+ description: "Ghost autocomplete: status / toggle. Args: 'on' | 'off' | empty (status)",
13
+ handler: async (args, ctx) => {
14
+ const arg = args.trim().toLowerCase();
15
+ if (arg === "on") {
16
+ config.enabled = true;
17
+ ctx.ui.notify("Pi Ghost: enabled", "info");
18
+ }
19
+ else if (arg === "off") {
20
+ config.enabled = false;
21
+ ctx.ui.notify("Pi Ghost: disabled", "info");
22
+ }
23
+ else {
24
+ ctx.ui.notify(`Pi Ghost: ${config.enabled ? "enabled" : "disabled"}\n ${describeProvider(config)}`, "info");
25
+ }
26
+ },
27
+ });
28
+ let activeEditor = null;
29
+ pi.on("session_start", async (_event, ctx) => {
30
+ if (!ctx.hasUI)
31
+ return;
32
+ const provider = await buildProvider(config, ctx);
33
+ const metricsCfg = readMetricsConfigFromEnv();
34
+ const metrics = metricsCfg.metricsEnabled || metricsCfg.debugEnabled
35
+ ? new MetricsLogger(ctx.cwd, metricsCfg)
36
+ : null;
37
+ const onError = makeOneShotNotifier(ctx);
38
+ let resolvedIndex = null;
39
+ ctx.ui.setEditorComponent((tui, theme, keybindings) => {
40
+ const editor = new GhostEditor(tui, theme, keybindings, ctx, config, provider, metrics, resolvedIndex, onError);
41
+ activeEditor = editor;
42
+ return editor;
43
+ });
44
+ ctx.ui.setStatus("ghost", provider ? `ghost:${provider.name.replace(/^ghost-/, "")}` : "ghost:off");
45
+ void buildPathIndex(ctx.cwd).then((idx) => {
46
+ resolvedIndex = idx;
47
+ activeEditor?.setPathIndex(idx);
48
+ });
49
+ });
50
+ pi.on("session_shutdown", async () => {
51
+ activeEditor?.dispose();
52
+ activeEditor = null;
53
+ });
54
+ };
55
+ async function buildProvider(config, ctx) {
56
+ if (!config.enabled)
57
+ return null;
58
+ if (config.provider.kind === "race") {
59
+ const members = [];
60
+ for (const m of config.provider.members) {
61
+ if (m.kind === "cloud") {
62
+ members.push(new CloudProvider(m));
63
+ continue;
64
+ }
65
+ if (m.kind === "openai-compat") {
66
+ members.push(new OpenAICompatProvider(m));
67
+ continue;
68
+ }
69
+ const local = new LocalProvider(m);
70
+ if (await local.preflight())
71
+ members.push(local);
72
+ }
73
+ if (members.length === 0) {
74
+ if (ctx.hasUI)
75
+ ctx.ui.notify("Pi Ghost: no race members available", "warning");
76
+ return null;
77
+ }
78
+ return new RaceProvider(members, {
79
+ pauseMs: config.provider.pauseMs,
80
+ cooldownMs: config.provider.cooldownMs,
81
+ cutoffMs: config.provider.cutoffMs,
82
+ });
83
+ }
84
+ if (config.provider.kind === "cloud") {
85
+ const p = new CloudProvider(config.provider);
86
+ return p;
87
+ }
88
+ if (config.provider.kind === "openai-compat") {
89
+ const p = new OpenAICompatProvider(config.provider);
90
+ void preflightAndWarn(p, ctx);
91
+ return p;
92
+ }
93
+ const p = new LocalProvider(config.provider);
94
+ const ok = await p.preflight();
95
+ if (!ok) {
96
+ if (ctx.hasUI)
97
+ ctx.ui.notify("Pi Ghost: local server unreachable, ghost disabled", "warning");
98
+ return null;
99
+ }
100
+ return p;
101
+ }
102
+ /**
103
+ * Fire-and-forget preflight for OpenAI-compat providers. The provider is
104
+ * returned to the caller immediately (so the session isn't blocked), and
105
+ * this runs in the background. On failure it surfaces a warning so the
106
+ * user knows their key/endpoint is misconfigured instead of wondering why
107
+ * nothing happens.
108
+ */
109
+ async function preflightAndWarn(provider, ctx) {
110
+ try {
111
+ const result = await provider.preflight();
112
+ if (result.ok)
113
+ return;
114
+ if (ctx.hasUI) {
115
+ ctx.ui.notify(`Pi Ghost (${provider.name}): ${result.message}\n` +
116
+ " File-path completions still work. LLM predictions disabled until resolved.\n" +
117
+ " Run /ghost for current status.", "warning");
118
+ }
119
+ }
120
+ catch {
121
+ // Preflight itself must never break the session.
122
+ }
123
+ }
124
+ /**
125
+ * Returns a callback that notifies the user about the first API error
126
+ * encountered at runtime, then goes silent. Respects the design principle
127
+ * of not spamming the chat with repeated warnings.
128
+ */
129
+ function makeOneShotNotifier(ctx) {
130
+ let fired = false;
131
+ return (err) => {
132
+ if (fired || !ctx.hasUI)
133
+ return;
134
+ fired = true;
135
+ ctx.ui.notify(`Pi Ghost: LLM request failed — ${err.message}\n` +
136
+ " Further errors will be silent. Run /ghost for status.\n" +
137
+ " Tip: enable PI_GHOST_DEBUG_LOG=1 for detailed diagnostics.", "warning");
138
+ };
139
+ }
140
+ function describeProvider(config) {
141
+ const mask = (s) => (s ? `${s.substring(0, 4)}***` : "unset");
142
+ const parts = [];
143
+ parts.push(`minChars=${config.minChars}`);
144
+ parts.push(`maxLineLength=${config.maxLineLength}`);
145
+ if (config.provider.kind === "race") {
146
+ parts.push(`mode=race`);
147
+ parts.push(`pauseMs=${config.provider.pauseMs}`);
148
+ parts.push(`cooldownMs=${config.provider.cooldownMs}`);
149
+ parts.push(`cutoffMs=${config.provider.cutoffMs}`);
150
+ parts.push(`maxRecentMessages=${config.provider.maxRecentMessages}`);
151
+ const members = config.provider.members.map((m, i) => {
152
+ const mp = [];
153
+ if (m.kind === "cloud") {
154
+ mp.push(`provider=${m.provider}`);
155
+ mp.push(`model=${m.modelId}`);
156
+ mp.push(`apiKey=${mask(m.apiKey)}`);
157
+ }
158
+ else if (m.kind === "openai-compat") {
159
+ mp.push(`provider=${m.providerName}`);
160
+ mp.push(`baseUrl=${m.baseUrl}`);
161
+ mp.push(`model=${m.modelId}`);
162
+ mp.push(`apiKey=${mask(m.apiKey)}`);
163
+ }
164
+ else if (m.kind === "local") {
165
+ mp.push(`baseUrl=${m.baseUrl}`);
166
+ mp.push(`model=${m.modelId}`);
167
+ }
168
+ mp.push(`debounceMs=${m.debounceMs}`);
169
+ mp.push(`maxTokens=${m.maxTokens}`);
170
+ return `[${m.kind}] ${mp.join(", ")}`;
171
+ });
172
+ parts.push(`members:\n ${members.join("\n ")}`);
173
+ }
174
+ else {
175
+ const p = config.provider;
176
+ parts.push(`mode=${p.kind}`);
177
+ if (p.kind === "cloud") {
178
+ parts.push(`provider=${p.provider}`);
179
+ parts.push(`model=${p.modelId}`);
180
+ parts.push(`apiKey=${mask(p.apiKey)}`);
181
+ }
182
+ else if (p.kind === "openai-compat") {
183
+ parts.push(`provider=${p.providerName}`);
184
+ parts.push(`baseUrl=${p.baseUrl}`);
185
+ parts.push(`model=${p.modelId}`);
186
+ parts.push(`apiKey=${mask(p.apiKey)}`);
187
+ }
188
+ else if (p.kind === "local") {
189
+ parts.push(`baseUrl=${p.baseUrl}`);
190
+ parts.push(`model=${p.modelId}`);
191
+ }
192
+ parts.push(`debounceMs=${p.debounceMs}`);
193
+ parts.push(`maxTokens=${p.maxTokens}`);
194
+ parts.push(`maxRecentMessages=${p.maxRecentMessages}`);
195
+ }
196
+ return parts.join("\n ");
197
+ }
198
+ export default ghost;
199
+ export { CloudProvider } from "./providers/cloud-provider.js";
200
+ export { LocalProvider } from "./providers/local-provider.js";
201
+ export { OpenAICompatProvider } from "./providers/openai-compat-provider.js";
202
+ export { RaceProvider, firstNonNull } from "./providers/race.js";
203
+ export { GhostEditor, computeReplacement } from "./ghost-editor.js";
204
+ export { MetricsLogger, readMetricsConfigFromEnv } from "./metrics.js";
205
+ export { buildPathIndex } from "./path-index.js";
206
+ export { extractPathPrefixToken, findPathPrefix } from "./path-matcher.js";
207
+ export { extractFuzzyToken, findFuzzyFilename, hasActionTrigger } from "./fuzzy-matcher.js";
208
+ export { extractRecentPaths, getRoleWeightedRecent, recentBoost } from "./recent-paths.js";
209
+ export { formatReport, loadMetrics, parseMetricsText, quantile, runBenchmark, summarize, THRESHOLDS, } from "./bench.js";
210
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ export type CompletionMode = "deterministic" | "llm";
2
+ export type RequestResult = "produced" | "empty" | "rejected" | "aborted" | "error";
3
+ export type AcceptanceOutcome = "accepted" | "dismissed" | "stale" | "expired";
4
+ export type RejectionReason = "stale-text" | "newline" | "too-long" | "assistant-phrasing" | "duplicate-suffix" | "markdown" | "empty" | "echo-token";
5
+ export interface ProviderLatency {
6
+ provider: string;
7
+ latencyMs: number;
8
+ result: RequestResult;
9
+ rejectionReason?: RejectionReason;
10
+ }
11
+ export interface MetricsRecord {
12
+ ts: number;
13
+ requestId: string;
14
+ mode: CompletionMode;
15
+ providers?: ProviderLatency[];
16
+ winner?: string;
17
+ result: RequestResult;
18
+ rejectionReason?: RejectionReason;
19
+ completionLength?: number;
20
+ shown?: boolean;
21
+ acceptance?: AcceptanceOutcome;
22
+ /**
23
+ * For deterministic-mode records, the matched file path from the index.
24
+ * Allows benchmark/analysis to correlate accepted ghosts with files.
25
+ * Never set for LLM mode (would risk leaking output text).
26
+ */
27
+ matchedPath?: string;
28
+ }
29
+ export interface DebugRecord {
30
+ ts: number;
31
+ requestId: string;
32
+ kind: string;
33
+ [k: string]: unknown;
34
+ }
35
+ export interface MetricsConfig {
36
+ metricsEnabled: boolean;
37
+ debugEnabled: boolean;
38
+ }
39
+ export declare function readMetricsConfigFromEnv(): MetricsConfig;
40
+ export declare class MetricsLogger {
41
+ private readonly cwd;
42
+ private readonly cfg;
43
+ private readonly metricsPath;
44
+ private readonly debugPath;
45
+ private writeQueue;
46
+ private nextId;
47
+ constructor(cwd: string, cfg: MetricsConfig);
48
+ get metricsEnabled(): boolean;
49
+ get debugEnabled(): boolean;
50
+ newRequestId(): string;
51
+ logMetric(record: MetricsRecord): void;
52
+ logDebug(record: DebugRecord): void;
53
+ flush(): Promise<void>;
54
+ private enqueue;
55
+ }
56
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,KAAK,CAAC;AAErD,MAAM,MAAM,aAAa,GACtB,UAAU,GACV,OAAO,GACP,UAAU,GACV,SAAS,GACT,OAAO,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/E,MAAM,MAAM,eAAe,GACxB,YAAY,GACZ,SAAS,GACT,UAAU,GACV,oBAAoB,GACpB,kBAAkB,GAClB,UAAU,GACV,OAAO,GACP,YAAY,CAAC;AAEhB,MAAM,WAAW,eAAe;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC7B,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,wBAAwB,IAAI,aAAa,CAKxD;AAED,qBAAa,aAAa;IAOxB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPrB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,MAAM,CAAK;gBAGD,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,aAAa;IAMpC,IAAI,cAAc,IAAI,OAAO,CAE5B;IAED,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,YAAY,IAAI,MAAM;IAItB,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAKtC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAK7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,OAAO;CAGf"}
@@ -0,0 +1,98 @@
1
+ import { appendFile, mkdir, rename, stat } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ const MAX_BYTES = 10 * 1024 * 1024;
4
+ const METRICS_REL = ".pi/ghost-autocomplete/metrics.jsonl";
5
+ const DEBUG_REL = ".pi/ghost-autocomplete/debug.jsonl";
6
+ export function readMetricsConfigFromEnv() {
7
+ return {
8
+ metricsEnabled: process.env.PI_GHOST_METRICS === "1",
9
+ debugEnabled: process.env.PI_GHOST_DEBUG_LOG === "1",
10
+ };
11
+ }
12
+ export class MetricsLogger {
13
+ cwd;
14
+ cfg;
15
+ metricsPath;
16
+ debugPath;
17
+ writeQueue = Promise.resolve();
18
+ nextId = 1;
19
+ constructor(cwd, cfg) {
20
+ this.cwd = cwd;
21
+ this.cfg = cfg;
22
+ this.metricsPath = join(cwd, METRICS_REL);
23
+ this.debugPath = join(cwd, DEBUG_REL);
24
+ }
25
+ get metricsEnabled() {
26
+ return this.cfg.metricsEnabled;
27
+ }
28
+ get debugEnabled() {
29
+ return this.cfg.debugEnabled;
30
+ }
31
+ newRequestId() {
32
+ return `r${Date.now().toString(36)}-${this.nextId++}`;
33
+ }
34
+ logMetric(record) {
35
+ if (!this.cfg.metricsEnabled)
36
+ return;
37
+ this.enqueue(() => writeLine(this.metricsPath, redactMetric(record)));
38
+ }
39
+ logDebug(record) {
40
+ if (!this.cfg.debugEnabled)
41
+ return;
42
+ this.enqueue(() => writeLine(this.debugPath, record));
43
+ }
44
+ async flush() {
45
+ await this.writeQueue;
46
+ }
47
+ enqueue(fn) {
48
+ this.writeQueue = this.writeQueue.then(fn, fn).catch(() => { });
49
+ }
50
+ }
51
+ function redactMetric(rec) {
52
+ const { ts, requestId, mode, providers, winner, result, rejectionReason, completionLength, shown, acceptance, matchedPath } = rec;
53
+ const out = { ts, requestId, mode, result };
54
+ if (providers && providers.length > 0)
55
+ out.providers = providers;
56
+ if (winner !== undefined)
57
+ out.winner = winner;
58
+ if (rejectionReason !== undefined)
59
+ out.rejectionReason = rejectionReason;
60
+ if (completionLength !== undefined)
61
+ out.completionLength = completionLength;
62
+ if (shown !== undefined)
63
+ out.shown = shown;
64
+ if (acceptance !== undefined)
65
+ out.acceptance = acceptance;
66
+ if (mode === "deterministic" && matchedPath !== undefined)
67
+ out.matchedPath = matchedPath;
68
+ return out;
69
+ }
70
+ async function writeLine(path, payload) {
71
+ try {
72
+ await mkdir(dirname(path), { recursive: true });
73
+ await rotateIfNeeded(path);
74
+ await appendFile(path, `${JSON.stringify(payload)}\n`, "utf8");
75
+ }
76
+ catch {
77
+ // Logging must never break ghost completion. Swallow.
78
+ }
79
+ }
80
+ async function rotateIfNeeded(path) {
81
+ let size;
82
+ try {
83
+ const s = await stat(path);
84
+ size = s.size;
85
+ }
86
+ catch {
87
+ return;
88
+ }
89
+ if (size < MAX_BYTES)
90
+ return;
91
+ try {
92
+ await rename(path, `${path}.1`);
93
+ }
94
+ catch {
95
+ // Best-effort rotation; if rename fails, keep appending to the file.
96
+ }
97
+ }
98
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export interface PathIndexOptions {
2
+ cap?: number;
3
+ signal?: AbortSignal;
4
+ }
5
+ export interface PathIndex {
6
+ readonly root: string;
7
+ readonly cwd: string;
8
+ readonly capExceeded: boolean;
9
+ readonly files: readonly string[];
10
+ prefixMatches(token: string, limit: number): string[];
11
+ }
12
+ /**
13
+ * Build a deterministic path index rooted at the Git toplevel of `cwd`.
14
+ *
15
+ * Returns null when Git is unavailable, `cwd` is outside any repo, or the
16
+ * underlying commands fail. Callers should treat null as "no deterministic
17
+ * path completion this session".
18
+ */
19
+ export declare function buildPathIndex(cwd: string, opts?: PathIndexOptions): Promise<PathIndex | null>;
20
+ //# sourceMappingURL=path-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-index.d.ts","sourceRoot":"","sources":["../src/path-index.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACtD;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,gBAAqB,GACzB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAqB3B"}
@@ -0,0 +1,148 @@
1
+ import { spawn } from "node:child_process";
2
+ const DEFAULT_CAP = 20_000;
3
+ /**
4
+ * Build a deterministic path index rooted at the Git toplevel of `cwd`.
5
+ *
6
+ * Returns null when Git is unavailable, `cwd` is outside any repo, or the
7
+ * underlying commands fail. Callers should treat null as "no deterministic
8
+ * path completion this session".
9
+ */
10
+ export async function buildPathIndex(cwd, opts = {}) {
11
+ const cap = opts.cap ?? DEFAULT_CAP;
12
+ const root = await gitToplevel(cwd, opts.signal);
13
+ if (!root)
14
+ return null;
15
+ const all = await gitLsFiles(root, opts.signal);
16
+ if (!all)
17
+ return null;
18
+ let files;
19
+ let capExceeded = false;
20
+ if (all.length > cap) {
21
+ capExceeded = true;
22
+ const rel = relPosix(root, cwd);
23
+ const subtreePrefix = rel === "" ? "" : `${rel}/`;
24
+ files = all.filter((f) => subtreePrefix === "" || f.startsWith(subtreePrefix));
25
+ if (files.length > cap)
26
+ files = files.slice(0, cap);
27
+ }
28
+ else {
29
+ files = all;
30
+ }
31
+ files.sort();
32
+ return new PathIndexImpl(root, cwd, files, capExceeded);
33
+ }
34
+ class PathIndexImpl {
35
+ root;
36
+ cwd;
37
+ files;
38
+ capExceeded;
39
+ constructor(root, cwd, files, capExceeded) {
40
+ this.root = root;
41
+ this.cwd = cwd;
42
+ this.files = files;
43
+ this.capExceeded = capExceeded;
44
+ }
45
+ prefixMatches(token, limit) {
46
+ if (!token || limit <= 0)
47
+ return [];
48
+ const start = lowerBound(this.files, token);
49
+ const out = [];
50
+ for (let i = start; i < this.files.length && out.length < limit; i++) {
51
+ const f = this.files[i];
52
+ if (!f || !f.startsWith(token))
53
+ break;
54
+ out.push(f);
55
+ }
56
+ return out;
57
+ }
58
+ }
59
+ function lowerBound(arr, target) {
60
+ let lo = 0;
61
+ let hi = arr.length;
62
+ while (lo < hi) {
63
+ const mid = (lo + hi) >>> 1;
64
+ const v = arr[mid];
65
+ if (v !== undefined && v < target)
66
+ lo = mid + 1;
67
+ else
68
+ hi = mid;
69
+ }
70
+ return lo;
71
+ }
72
+ function relPosix(root, cwd) {
73
+ const r = normSlashes(root);
74
+ const c = normSlashes(cwd);
75
+ if (c === r)
76
+ return "";
77
+ const prefix = r.endsWith("/") ? r : `${r}/`;
78
+ if (c.startsWith(prefix))
79
+ return c.slice(prefix.length);
80
+ return "";
81
+ }
82
+ function normSlashes(p) {
83
+ return p.replace(/\\/g, "/").replace(/\/+$/, "");
84
+ }
85
+ async function gitToplevel(cwd, signal) {
86
+ const res = await runGit(["rev-parse", "--show-toplevel"], cwd, signal);
87
+ if (!res)
88
+ return null;
89
+ const line = res.split(/\r?\n/)[0]?.trim();
90
+ if (!line)
91
+ return null;
92
+ return normSlashes(line);
93
+ }
94
+ async function gitLsFiles(root, signal) {
95
+ const res = await runGit(["ls-files", "-z"], root, signal);
96
+ if (res === null)
97
+ return null;
98
+ if (res === "")
99
+ return [];
100
+ return res.split("\0").filter((s) => s.length > 0);
101
+ }
102
+ function runGit(args, cwd, signal) {
103
+ return new Promise((resolve) => {
104
+ let proc;
105
+ try {
106
+ proc = spawn("git", args, { cwd, windowsHide: true });
107
+ }
108
+ catch {
109
+ resolve(null);
110
+ return;
111
+ }
112
+ let out = "";
113
+ let err = false;
114
+ const onAbort = () => {
115
+ try {
116
+ proc.kill();
117
+ }
118
+ catch {
119
+ // proc may already be dead
120
+ }
121
+ };
122
+ if (signal) {
123
+ if (signal.aborted)
124
+ onAbort();
125
+ else
126
+ signal.addEventListener("abort", onAbort, { once: true });
127
+ }
128
+ proc.stdout?.on("data", (chunk) => {
129
+ out += chunk.toString("utf8");
130
+ });
131
+ proc.on("error", () => {
132
+ err = true;
133
+ resolve(null);
134
+ });
135
+ proc.on("close", (code) => {
136
+ if (signal)
137
+ signal.removeEventListener("abort", onAbort);
138
+ if (err)
139
+ return;
140
+ if (code !== 0) {
141
+ resolve(null);
142
+ return;
143
+ }
144
+ resolve(out);
145
+ });
146
+ });
147
+ }
148
+ //# sourceMappingURL=path-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-index.js","sourceRoot":"","sources":["../src/path-index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { PathIndex } from "./path-index.js";
2
+ export interface PathToken {
3
+ token: string;
4
+ insideQuote: boolean;
5
+ }
6
+ export interface PathPrefixMatch {
7
+ match: string;
8
+ suffix: string;
9
+ score: number;
10
+ }
11
+ export interface FindPathPrefixOptions {
12
+ limit?: number;
13
+ recentPaths?: Set<string> | null;
14
+ }
15
+ /**
16
+ * Extract a path-like token from the end of `before` (text before the cursor).
17
+ * Returns null when no path-prefix mode is applicable: a token is only
18
+ * eligible when it contains a `/` (path-prefix mode is structural, not fuzzy).
19
+ *
20
+ * `insideQuote` is true when the character immediately preceding the token is
21
+ * a quote or backtick; callers can still trust the token in that case.
22
+ */
23
+ export declare function extractPathPrefixToken(before: string): PathToken | null;
24
+ /**
25
+ * Pick the best path-prefix match for `token`. Strategy: enumerate matches
26
+ * (capped), prefer files inside `cwdRel` subtree, then shorter paths.
27
+ *
28
+ * Returns null when the token has no matches or matches itself exactly
29
+ * (nothing to insert).
30
+ */
31
+ export declare function findPathPrefix(index: PathIndex, token: string, opts?: FindPathPrefixOptions | number): PathPrefixMatch | null;
32
+ //# sourceMappingURL=path-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-matcher.d.ts","sourceRoot":"","sources":["../src/path-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CACjC;AAKD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAevE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC7B,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,GAAE,qBAAqB,GAAG,MAAW,GACvC,eAAe,GAAG,IAAI,CA+BxB"}