@skein-code/cli 0.3.18 → 0.3.20
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 +21 -4
- package/dist/cli.js +827 -411
- package/dist/cli.js.map +1 -1
- package/docs/NEXT_STEPS.md +36 -9
- package/package.json +7 -3
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5
5
|
import { stdin as input, stdout as output } from "node:process";
|
|
6
6
|
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
7
|
-
import { basename as basename13, resolve as
|
|
7
|
+
import { basename as basename13, resolve as resolve24 } from "node:path";
|
|
8
8
|
import { Command, Option } from "commander";
|
|
9
9
|
import chalk4 from "chalk";
|
|
10
10
|
|
|
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@skein-code/cli",
|
|
223
|
-
version: "0.3.
|
|
223
|
+
version: "0.3.20",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -236,6 +236,9 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"Local index v3 records TypeScript AST definitions, calls, and relative import adjacency for graph-assisted retrieval",
|
|
240
|
+
"Context hits expose generation, content hash, matched terms, and bm25/path/symbol/phrase/graph score provenance",
|
|
241
|
+
"Context benchmark v2 locks multilingual recall, MRR, useful-token, stale-hit, incremental-index, and warm-latency gates",
|
|
239
242
|
"Large MCP catalogs now disclose at most eight request-relevant schemas per run while preserving network permissions",
|
|
240
243
|
"Token Ledger receipts report deferred progressive schemas and selected definitions remain stable across tool turns",
|
|
241
244
|
"A deterministic Token Economy replay benchmark guards evidence, output-firewall, and no-progress invariants",
|
|
@@ -253,7 +256,8 @@ var package_default = {
|
|
|
253
256
|
"Prompt and timeline diagnostics expose the repository-first reuse ladder without blocking legitimate writes",
|
|
254
257
|
"Known tool changes refresh only their affected local-index paths before the next model turn",
|
|
255
258
|
"File ctime closes same-size and restored-mtime zero-hit freshness gaps without full-content scans",
|
|
256
|
-
"Repeated empty or unchanged search calls stop through the existing recovery circuit"
|
|
259
|
+
"Repeated empty or unchanged search calls stop through the existing recovery circuit",
|
|
260
|
+
"Git recency is an isolated, bounded tie-break with exact HEAD-bound cache invalidation and non-Git degradation"
|
|
257
261
|
]
|
|
258
262
|
},
|
|
259
263
|
bin: {
|
|
@@ -301,6 +305,7 @@ var package_default = {
|
|
|
301
305
|
react: "^19.2.3",
|
|
302
306
|
"string-width": "^8.2.2",
|
|
303
307
|
"strip-ansi": "^7.2.0",
|
|
308
|
+
typescript: "^5.9.3",
|
|
304
309
|
yaml: "^2.8.2",
|
|
305
310
|
zod: "^4.4.3"
|
|
306
311
|
},
|
|
@@ -310,7 +315,6 @@ var package_default = {
|
|
|
310
315
|
"@types/react": "^19.2.7",
|
|
311
316
|
tsup: "^8.5.1",
|
|
312
317
|
tsx: "^4.21.0",
|
|
313
|
-
typescript: "^5.9.3",
|
|
314
318
|
vitest: "^4.0.18"
|
|
315
319
|
},
|
|
316
320
|
overrides: {
|
|
@@ -2608,10 +2612,11 @@ function countExplicitPaths(value) {
|
|
|
2608
2612
|
}
|
|
2609
2613
|
|
|
2610
2614
|
// src/context/local-index.ts
|
|
2611
|
-
import { createHash as
|
|
2612
|
-
import { lstat as
|
|
2613
|
-
import { basename as basename5, dirname as dirname7, extname as extname2, isAbsolute as
|
|
2615
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
2616
|
+
import { lstat as lstat9, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
2617
|
+
import { basename as basename5, dirname as dirname7, extname as extname2, isAbsolute as isAbsolute4, join as join8, posix, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
|
|
2614
2618
|
import fg from "fast-glob";
|
|
2619
|
+
import ts from "typescript";
|
|
2615
2620
|
import { z as z3 } from "zod";
|
|
2616
2621
|
|
|
2617
2622
|
// src/tools/workspace.ts
|
|
@@ -3042,6 +3047,371 @@ function hash(value) {
|
|
|
3042
3047
|
return createHash5("sha256").update(value).digest("hex");
|
|
3043
3048
|
}
|
|
3044
3049
|
|
|
3050
|
+
// src/context/git-recency.ts
|
|
3051
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
3052
|
+
import { resolve as resolve9 } from "node:path";
|
|
3053
|
+
|
|
3054
|
+
// src/utils/process.ts
|
|
3055
|
+
import { spawn } from "node:child_process";
|
|
3056
|
+
import { constants as constants3 } from "node:fs";
|
|
3057
|
+
import { access as access2, lstat as lstat8, realpath as realpath6 } from "node:fs/promises";
|
|
3058
|
+
import { delimiter, isAbsolute as isAbsolute3, join as join7, resolve as resolve8 } from "node:path";
|
|
3059
|
+
import { StringDecoder } from "node:string_decoder";
|
|
3060
|
+
async function resolveExecutableRuntime(command2, cwd, excludedRoots = []) {
|
|
3061
|
+
const realRoots = await Promise.all(excludedRoots.map(async (root) => {
|
|
3062
|
+
try {
|
|
3063
|
+
return await realpath6(root);
|
|
3064
|
+
} catch {
|
|
3065
|
+
return resolve8(root);
|
|
3066
|
+
}
|
|
3067
|
+
}));
|
|
3068
|
+
const pathEntries = (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean);
|
|
3069
|
+
const safeDirectories2 = [];
|
|
3070
|
+
let executable2;
|
|
3071
|
+
const explicit = isAbsolute3(command2) || command2.includes("/") || command2.includes("\\");
|
|
3072
|
+
const explicitPath = explicit ? resolve8(cwd, command2) : void 0;
|
|
3073
|
+
for (const entry of pathEntries) {
|
|
3074
|
+
let directory;
|
|
3075
|
+
try {
|
|
3076
|
+
directory = await realpath6(resolve8(cwd, entry));
|
|
3077
|
+
if (!(await lstat8(directory)).isDirectory()) continue;
|
|
3078
|
+
} catch {
|
|
3079
|
+
continue;
|
|
3080
|
+
}
|
|
3081
|
+
if (realRoots.some((root) => isInside(root, directory))) continue;
|
|
3082
|
+
let contaminated = false;
|
|
3083
|
+
if (!explicit) {
|
|
3084
|
+
for (const name of executableNames(command2)) {
|
|
3085
|
+
const candidate = join7(directory, name);
|
|
3086
|
+
const resolvedCandidate = await usableExecutable(candidate);
|
|
3087
|
+
if (!resolvedCandidate) continue;
|
|
3088
|
+
if (realRoots.some((root) => isInside(root, resolvedCandidate))) {
|
|
3089
|
+
contaminated = true;
|
|
3090
|
+
continue;
|
|
3091
|
+
}
|
|
3092
|
+
executable2 ??= resolvedCandidate;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
if (!contaminated && !safeDirectories2.includes(directory)) safeDirectories2.push(directory);
|
|
3096
|
+
}
|
|
3097
|
+
if (explicitPath) executable2 = await usableExecutable(explicitPath);
|
|
3098
|
+
if (!executable2) return void 0;
|
|
3099
|
+
return { executable: executable2, path: safeDirectories2.join(delimiter) };
|
|
3100
|
+
}
|
|
3101
|
+
async function usableExecutable(candidate) {
|
|
3102
|
+
try {
|
|
3103
|
+
await access2(candidate, process.platform === "win32" ? constants3.F_OK : constants3.X_OK);
|
|
3104
|
+
const resolvedCandidate = await realpath6(candidate);
|
|
3105
|
+
return (await lstat8(resolvedCandidate)).isFile() ? resolvedCandidate : void 0;
|
|
3106
|
+
} catch {
|
|
3107
|
+
return void 0;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
function executableNames(command2) {
|
|
3111
|
+
if (process.platform !== "win32") return [command2];
|
|
3112
|
+
const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
|
|
3113
|
+
return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
|
|
3114
|
+
}
|
|
3115
|
+
function runProcess(command2, args, options) {
|
|
3116
|
+
return new Promise((resolve25, reject) => {
|
|
3117
|
+
const started = Date.now();
|
|
3118
|
+
const environment = options.inheritEnv === false ? {} : { ...process.env };
|
|
3119
|
+
for (const name of options.unsetEnv ?? []) delete environment[name];
|
|
3120
|
+
for (const name of Object.keys(environment)) {
|
|
3121
|
+
if (options.unsetEnvPrefixes?.some((prefix) => name.startsWith(prefix))) {
|
|
3122
|
+
delete environment[name];
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
Object.assign(environment, options.env);
|
|
3126
|
+
const child = spawn(command2, args, {
|
|
3127
|
+
cwd: options.cwd,
|
|
3128
|
+
env: environment,
|
|
3129
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
3130
|
+
signal: options.signal
|
|
3131
|
+
});
|
|
3132
|
+
const maxBytes = options.maxOutputBytes ?? 1e6;
|
|
3133
|
+
let stdout = "";
|
|
3134
|
+
let stderr = "";
|
|
3135
|
+
let stdoutBytes = 0;
|
|
3136
|
+
let stderrBytes = 0;
|
|
3137
|
+
let stdoutObservedBytes = 0;
|
|
3138
|
+
let stderrObservedBytes = 0;
|
|
3139
|
+
let timedOut = false;
|
|
3140
|
+
let outputLimitReached = false;
|
|
3141
|
+
let callbackError;
|
|
3142
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
3143
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
3144
|
+
const stdoutCallbackDecoder = new StringDecoder("utf8");
|
|
3145
|
+
const stderrCallbackDecoder = new StringDecoder("utf8");
|
|
3146
|
+
const append = (decoder, chunk, usedBytes) => {
|
|
3147
|
+
if (usedBytes >= maxBytes) return { text: "", usedBytes };
|
|
3148
|
+
const selected = chunk.subarray(0, maxBytes - usedBytes);
|
|
3149
|
+
return { text: decoder.write(selected), usedBytes: usedBytes + selected.length };
|
|
3150
|
+
};
|
|
3151
|
+
const notify = (callback, decoder, chunk) => {
|
|
3152
|
+
if (!callback || callbackError) return;
|
|
3153
|
+
try {
|
|
3154
|
+
const decoded = decoder.write(chunk);
|
|
3155
|
+
if (decoded) callback(decoded);
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
callbackError = error;
|
|
3158
|
+
child.kill("SIGTERM");
|
|
3159
|
+
}
|
|
3160
|
+
};
|
|
3161
|
+
child.stdout.on("data", (chunk) => {
|
|
3162
|
+
stdoutObservedBytes += chunk.length;
|
|
3163
|
+
if (options.stopOnOutputLimit && stdoutObservedBytes > maxBytes && !outputLimitReached) {
|
|
3164
|
+
outputLimitReached = true;
|
|
3165
|
+
child.kill("SIGTERM");
|
|
3166
|
+
}
|
|
3167
|
+
const appended = append(stdoutDecoder, chunk, stdoutBytes);
|
|
3168
|
+
stdout += appended.text;
|
|
3169
|
+
stdoutBytes = appended.usedBytes;
|
|
3170
|
+
notify(options.onStdout, stdoutCallbackDecoder, chunk);
|
|
3171
|
+
});
|
|
3172
|
+
child.stderr.on("data", (chunk) => {
|
|
3173
|
+
stderrObservedBytes += chunk.length;
|
|
3174
|
+
if (options.stopOnOutputLimit && stderrObservedBytes > maxBytes && !outputLimitReached) {
|
|
3175
|
+
outputLimitReached = true;
|
|
3176
|
+
child.kill("SIGTERM");
|
|
3177
|
+
}
|
|
3178
|
+
const appended = append(stderrDecoder, chunk, stderrBytes);
|
|
3179
|
+
stderr += appended.text;
|
|
3180
|
+
stderrBytes = appended.usedBytes;
|
|
3181
|
+
notify(options.onStderr, stderrCallbackDecoder, chunk);
|
|
3182
|
+
});
|
|
3183
|
+
child.on("error", reject);
|
|
3184
|
+
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
3185
|
+
const timeout = timeoutMs > 0 ? setTimeout(() => {
|
|
3186
|
+
timedOut = true;
|
|
3187
|
+
child.kill("SIGTERM");
|
|
3188
|
+
setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
|
|
3189
|
+
}, timeoutMs) : void 0;
|
|
3190
|
+
child.on("close", (code) => {
|
|
3191
|
+
if (timeout) clearTimeout(timeout);
|
|
3192
|
+
const stdoutTail = stdoutDecoder.end();
|
|
3193
|
+
const stderrTail = stderrDecoder.end();
|
|
3194
|
+
if (Buffer.byteLength(stdout) + Buffer.byteLength(stdoutTail) <= maxBytes) stdout += stdoutTail;
|
|
3195
|
+
if (Buffer.byteLength(stderr) + Buffer.byteLength(stderrTail) <= maxBytes) stderr += stderrTail;
|
|
3196
|
+
try {
|
|
3197
|
+
const stdoutCallbackTail = stdoutCallbackDecoder.end();
|
|
3198
|
+
const stderrCallbackTail = stderrCallbackDecoder.end();
|
|
3199
|
+
if (stdoutCallbackTail && options.onStdout && !callbackError) options.onStdout(stdoutCallbackTail);
|
|
3200
|
+
if (stderrCallbackTail && options.onStderr && !callbackError) options.onStderr(stderrCallbackTail);
|
|
3201
|
+
} catch (error) {
|
|
3202
|
+
callbackError = error;
|
|
3203
|
+
}
|
|
3204
|
+
if (callbackError) {
|
|
3205
|
+
reject(callbackError);
|
|
3206
|
+
return;
|
|
3207
|
+
}
|
|
3208
|
+
resolve25({
|
|
3209
|
+
command: [command2, ...args].join(" "),
|
|
3210
|
+
exitCode: code ?? (timedOut ? 124 : 1),
|
|
3211
|
+
stdout,
|
|
3212
|
+
stderr,
|
|
3213
|
+
timedOut,
|
|
3214
|
+
durationMs: Date.now() - started,
|
|
3215
|
+
stdoutBytes: stdoutObservedBytes,
|
|
3216
|
+
stderrBytes: stderrObservedBytes,
|
|
3217
|
+
stdoutTruncated: stdoutObservedBytes > stdoutBytes,
|
|
3218
|
+
stderrTruncated: stderrObservedBytes > stderrBytes
|
|
3219
|
+
});
|
|
3220
|
+
});
|
|
3221
|
+
if (options.stdin) child.stdin.end(options.stdin);
|
|
3222
|
+
else child.stdin.end();
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
function runShell(command2, cwd, options = {}) {
|
|
3226
|
+
const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/sh";
|
|
3227
|
+
const args = process.platform === "win32" ? ["/d", "/s", "/c", command2] : ["-lc", command2];
|
|
3228
|
+
return runProcess(shell, args, { ...options, cwd });
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
// src/utils/git.ts
|
|
3232
|
+
var unsafeInheritedGitEnvironment = [
|
|
3233
|
+
"GIT_DIR",
|
|
3234
|
+
"GIT_WORK_TREE",
|
|
3235
|
+
"GIT_COMMON_DIR",
|
|
3236
|
+
"GIT_INDEX_FILE",
|
|
3237
|
+
"GIT_OBJECT_DIRECTORY",
|
|
3238
|
+
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
3239
|
+
"GIT_NAMESPACE",
|
|
3240
|
+
"GIT_CEILING_DIRECTORIES",
|
|
3241
|
+
"GIT_DISCOVERY_ACROSS_FILESYSTEM",
|
|
3242
|
+
"GIT_EXTERNAL_DIFF",
|
|
3243
|
+
"GIT_DIFF_OPTS",
|
|
3244
|
+
"GIT_SSH",
|
|
3245
|
+
"GIT_SSH_COMMAND",
|
|
3246
|
+
"GIT_PROXY_COMMAND",
|
|
3247
|
+
"GIT_ASKPASS",
|
|
3248
|
+
"SSH_ASKPASS",
|
|
3249
|
+
"GIT_EXEC_PATH",
|
|
3250
|
+
"GIT_TEMPLATE_DIR",
|
|
3251
|
+
"GIT_CONFIG",
|
|
3252
|
+
"GIT_CONFIG_PARAMETERS",
|
|
3253
|
+
"GIT_QUARANTINE_PATH",
|
|
3254
|
+
"Path"
|
|
3255
|
+
];
|
|
3256
|
+
var isolatedGitEnvironment = {
|
|
3257
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
3258
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
3259
|
+
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
3260
|
+
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
3261
|
+
GIT_NO_REPLACE_OBJECTS: "1",
|
|
3262
|
+
GIT_PAGER: "cat",
|
|
3263
|
+
GIT_EDITOR: "true",
|
|
3264
|
+
GIT_SEQUENCE_EDITOR: "true",
|
|
3265
|
+
GIT_CONFIG_COUNT: "4",
|
|
3266
|
+
GIT_CONFIG_KEY_0: "core.hooksPath",
|
|
3267
|
+
GIT_CONFIG_VALUE_0: "/dev/null",
|
|
3268
|
+
GIT_CONFIG_KEY_1: "core.fsmonitor",
|
|
3269
|
+
GIT_CONFIG_VALUE_1: "false",
|
|
3270
|
+
GIT_CONFIG_KEY_2: "credential.helper",
|
|
3271
|
+
GIT_CONFIG_VALUE_2: "",
|
|
3272
|
+
GIT_CONFIG_KEY_3: "protocol.ext.allow",
|
|
3273
|
+
GIT_CONFIG_VALUE_3: "never"
|
|
3274
|
+
};
|
|
3275
|
+
function runIsolatedGit(runtime, args, cwd, options = {}) {
|
|
3276
|
+
return runProcess(runtime.executable, args, {
|
|
3277
|
+
cwd,
|
|
3278
|
+
timeoutMs: options.timeoutMs ?? 15e3,
|
|
3279
|
+
maxOutputBytes: options.maxOutputBytes ?? 2e6,
|
|
3280
|
+
...options.stopOnOutputLimit !== void 0 ? { stopOnOutputLimit: options.stopOnOutputLimit } : {},
|
|
3281
|
+
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
3282
|
+
unsetEnv: unsafeInheritedGitEnvironment,
|
|
3283
|
+
unsetEnvPrefixes: ["GIT_"],
|
|
3284
|
+
...options.stdin !== void 0 ? { stdin: options.stdin } : {},
|
|
3285
|
+
...options.signal ? { signal: options.signal } : {}
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
// src/context/git-recency.ts
|
|
3290
|
+
var MAX_COMMITS = 128;
|
|
3291
|
+
var MAX_OUTPUT_BYTES = 1e6;
|
|
3292
|
+
var TIMEOUT_MS = 2e3;
|
|
3293
|
+
var MAX_RECENCY_SCORE = 1e-3;
|
|
3294
|
+
var commitHashPattern = /^(?:[a-f\d]{40}|[a-f\d]{64})$/u;
|
|
3295
|
+
var GitRecencyCollector = class {
|
|
3296
|
+
roots;
|
|
3297
|
+
states = /* @__PURE__ */ new Map();
|
|
3298
|
+
constructor(roots) {
|
|
3299
|
+
this.roots = roots.map((root) => resolve9(root));
|
|
3300
|
+
}
|
|
3301
|
+
async collect() {
|
|
3302
|
+
const snapshots = await Promise.all(this.roots.map((root) => this.collectRoot(root)));
|
|
3303
|
+
const scores = /* @__PURE__ */ new Map();
|
|
3304
|
+
for (const snapshot of snapshots) {
|
|
3305
|
+
for (const [path, score] of snapshot.scores) {
|
|
3306
|
+
scores.set(path, Math.max(score, scores.get(path) ?? 0));
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return {
|
|
3310
|
+
generation: digest(snapshots.map(({ root, generation }) => `${root}\0${generation}`).join("\n")),
|
|
3311
|
+
scores
|
|
3312
|
+
};
|
|
3313
|
+
}
|
|
3314
|
+
async collectRoot(root) {
|
|
3315
|
+
const state = this.states.get(root) ?? {
|
|
3316
|
+
runtime: void 0,
|
|
3317
|
+
runtimeResolved: false,
|
|
3318
|
+
head: void 0,
|
|
3319
|
+
snapshot: void 0
|
|
3320
|
+
};
|
|
3321
|
+
this.states.set(root, state);
|
|
3322
|
+
try {
|
|
3323
|
+
if (!state.runtimeResolved) {
|
|
3324
|
+
state.runtime = await resolveExecutableRuntime("git", root, this.roots);
|
|
3325
|
+
state.runtimeResolved = true;
|
|
3326
|
+
}
|
|
3327
|
+
if (!state.runtime) return unavailableSnapshot(root, "missing");
|
|
3328
|
+
const headResult = await runIsolatedGit(
|
|
3329
|
+
state.runtime,
|
|
3330
|
+
["rev-parse", "--verify", "HEAD"],
|
|
3331
|
+
root,
|
|
3332
|
+
{ timeoutMs: TIMEOUT_MS, maxOutputBytes: 256, stopOnOutputLimit: true }
|
|
3333
|
+
);
|
|
3334
|
+
const head = headResult.stdout.trim();
|
|
3335
|
+
if (headResult.exitCode !== 0 || headResult.timedOut || headResult.stdoutTruncated || headResult.stderrTruncated || !commitHashPattern.test(head)) {
|
|
3336
|
+
state.head = void 0;
|
|
3337
|
+
state.snapshot = unavailableSnapshot(root, "no-head");
|
|
3338
|
+
return state.snapshot;
|
|
3339
|
+
}
|
|
3340
|
+
if (state.head === head && state.snapshot) return state.snapshot;
|
|
3341
|
+
state.head = head;
|
|
3342
|
+
state.snapshot = await collectRootGitLog(root, state.runtime);
|
|
3343
|
+
return state.snapshot;
|
|
3344
|
+
} catch {
|
|
3345
|
+
state.head = void 0;
|
|
3346
|
+
state.snapshot = unavailableSnapshot(root, "error");
|
|
3347
|
+
return state.snapshot;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
};
|
|
3351
|
+
async function collectRootGitLog(root, runtime) {
|
|
3352
|
+
try {
|
|
3353
|
+
const result = await runIsolatedGit(runtime, [
|
|
3354
|
+
"--no-pager",
|
|
3355
|
+
"log",
|
|
3356
|
+
"--topo-order",
|
|
3357
|
+
`--max-count=${MAX_COMMITS}`,
|
|
3358
|
+
"--format=%x00%H",
|
|
3359
|
+
"--name-only",
|
|
3360
|
+
"-z",
|
|
3361
|
+
"--no-renames",
|
|
3362
|
+
"--no-ext-diff",
|
|
3363
|
+
"--no-textconv",
|
|
3364
|
+
"--relative",
|
|
3365
|
+
"--",
|
|
3366
|
+
"."
|
|
3367
|
+
], root, {
|
|
3368
|
+
timeoutMs: TIMEOUT_MS,
|
|
3369
|
+
maxOutputBytes: MAX_OUTPUT_BYTES,
|
|
3370
|
+
stopOnOutputLimit: true
|
|
3371
|
+
});
|
|
3372
|
+
if (result.exitCode !== 0 || result.timedOut || result.stdoutTruncated || result.stderrTruncated) {
|
|
3373
|
+
const reason = result.timedOut ? "timeout" : result.stdoutTruncated || result.stderrTruncated ? "output-limit" : `exit-${result.exitCode}`;
|
|
3374
|
+
return unavailableSnapshot(root, reason);
|
|
3375
|
+
}
|
|
3376
|
+
return {
|
|
3377
|
+
root,
|
|
3378
|
+
generation: digest(result.stdout),
|
|
3379
|
+
scores: parseGitLog(root, result.stdout)
|
|
3380
|
+
};
|
|
3381
|
+
} catch {
|
|
3382
|
+
return unavailableSnapshot(root, "error");
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
function parseGitLog(root, output2) {
|
|
3386
|
+
const scores = /* @__PURE__ */ new Map();
|
|
3387
|
+
const records = output2.split("\0");
|
|
3388
|
+
let commitRank = -1;
|
|
3389
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
3390
|
+
const record = records[index] ?? "";
|
|
3391
|
+
if (!record) {
|
|
3392
|
+
const commit = records[index + 1]?.trim() ?? "";
|
|
3393
|
+
if (commitHashPattern.test(commit)) {
|
|
3394
|
+
commitRank += 1;
|
|
3395
|
+
index += 1;
|
|
3396
|
+
}
|
|
3397
|
+
continue;
|
|
3398
|
+
}
|
|
3399
|
+
if (commitRank < 0) continue;
|
|
3400
|
+
const path = record.startsWith("\n") ? record.slice(1) : record;
|
|
3401
|
+
if (!path) continue;
|
|
3402
|
+
const absolutePath = resolve9(root, path);
|
|
3403
|
+
if (!isInside(root, absolutePath) || scores.has(absolutePath)) continue;
|
|
3404
|
+
scores.set(absolutePath, MAX_RECENCY_SCORE / (commitRank + 1));
|
|
3405
|
+
}
|
|
3406
|
+
return scores;
|
|
3407
|
+
}
|
|
3408
|
+
function unavailableSnapshot(root, reason) {
|
|
3409
|
+
return { root, generation: digest(`unavailable\0${reason}`), scores: /* @__PURE__ */ new Map() };
|
|
3410
|
+
}
|
|
3411
|
+
function digest(value) {
|
|
3412
|
+
return createHash6("sha256").update(value, "utf8").digest("hex").slice(0, 16);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3045
3415
|
// src/context/local-index.ts
|
|
3046
3416
|
var contentHashSchema = z3.string().regex(/^[a-f\d]{64}$/u);
|
|
3047
3417
|
var indexedChunkSchema = z3.object({
|
|
@@ -3063,10 +3433,13 @@ var indexedFileSchema = z3.object({
|
|
|
3063
3433
|
ctimeMs: z3.number().optional(),
|
|
3064
3434
|
size: z3.number().nonnegative(),
|
|
3065
3435
|
contentHash: contentHashSchema,
|
|
3066
|
-
chunks: z3.array(indexedChunkSchema)
|
|
3436
|
+
chunks: z3.array(indexedChunkSchema),
|
|
3437
|
+
definitions: z3.array(z3.string()),
|
|
3438
|
+
references: z3.array(z3.string()),
|
|
3439
|
+
imports: z3.array(z3.string())
|
|
3067
3440
|
}).strict();
|
|
3068
3441
|
var localIndexSchema = z3.object({
|
|
3069
|
-
version: z3.literal(
|
|
3442
|
+
version: z3.literal(3),
|
|
3070
3443
|
createdAt: z3.string(),
|
|
3071
3444
|
generation: z3.string().min(1),
|
|
3072
3445
|
roots: z3.array(z3.string()),
|
|
@@ -3104,14 +3477,18 @@ var MIN_STRUCTURAL_CHUNK_LINES = 12;
|
|
|
3104
3477
|
var LocalContextIndex = class {
|
|
3105
3478
|
constructor(roots) {
|
|
3106
3479
|
this.roots = roots;
|
|
3107
|
-
this.roots = roots.map((root) =>
|
|
3480
|
+
this.roots = roots.map((root) => resolve10(root));
|
|
3108
3481
|
this.workspace = new WorkspaceAccess(this.roots);
|
|
3109
|
-
this.
|
|
3482
|
+
this.gitRecency = new GitRecencyCollector(this.roots);
|
|
3483
|
+
this.indexPath = join8(resolveProjectNamespaceSync(this.roots[0] ?? process.cwd()).active, "index.json");
|
|
3110
3484
|
}
|
|
3111
3485
|
roots;
|
|
3112
3486
|
index;
|
|
3113
3487
|
workspace;
|
|
3114
3488
|
queryCache = /* @__PURE__ */ new Map();
|
|
3489
|
+
gitRecency;
|
|
3490
|
+
queryCacheHits = 0;
|
|
3491
|
+
queryCacheMisses = 0;
|
|
3115
3492
|
fingerprintCache;
|
|
3116
3493
|
dirtyPaths = /* @__PURE__ */ new Set();
|
|
3117
3494
|
refreshState = "current";
|
|
@@ -3123,13 +3500,13 @@ var LocalContextIndex = class {
|
|
|
3123
3500
|
this.roots[0] ?? process.cwd(),
|
|
3124
3501
|
dirname7(this.indexPath)
|
|
3125
3502
|
);
|
|
3126
|
-
const info = await
|
|
3503
|
+
const info = await lstat9(this.indexPath);
|
|
3127
3504
|
if (!info.isFile() || info.isSymbolicLink()) return false;
|
|
3128
3505
|
const parsed = localIndexSchema.parse(JSON.parse(await readFile3(this.indexPath, "utf8")));
|
|
3129
3506
|
const files = [];
|
|
3130
3507
|
for (const file of parsed.files) {
|
|
3131
3508
|
try {
|
|
3132
|
-
if (!this.roots.includes(file.root) ||
|
|
3509
|
+
if (!this.roots.includes(file.root) || resolve10(file.root, file.path) !== file.absolutePath) continue;
|
|
3133
3510
|
const safe = await this.workspace.resolvePath(file.absolutePath, { expect: "file" });
|
|
3134
3511
|
if (safe !== file.absolutePath) continue;
|
|
3135
3512
|
const { ctimeMs, ...persistedFile } = file;
|
|
@@ -3206,19 +3583,21 @@ var LocalContextIndex = class {
|
|
|
3206
3583
|
await this.flushDirty();
|
|
3207
3584
|
await this.ensureCurrentIndex();
|
|
3208
3585
|
const limit = Math.max(1, Math.floor(topK));
|
|
3209
|
-
|
|
3210
|
-
|
|
3586
|
+
let recency = await this.gitRecency.collect();
|
|
3587
|
+
const cached = this.getCachedHits(query, limit, recency.generation);
|
|
3588
|
+
let hits = cached ?? this.rank(query, limit, recency.scores);
|
|
3211
3589
|
if (!await this.hitsAreCurrent(hits)) {
|
|
3212
3590
|
await this.buildWithOptions(void 0, true);
|
|
3213
|
-
|
|
3591
|
+
recency = await this.gitRecency.collect();
|
|
3592
|
+
hits = this.rank(query, limit, recency.scores);
|
|
3214
3593
|
if (!await this.hitsAreCurrent(hits)) return [];
|
|
3215
3594
|
}
|
|
3216
|
-
this.cacheHits(query, limit, hits);
|
|
3595
|
+
this.cacheHits(query, limit, recency.generation, hits);
|
|
3217
3596
|
return cloneHits(hits);
|
|
3218
3597
|
}
|
|
3219
3598
|
invalidate(paths) {
|
|
3220
3599
|
for (const path of paths) {
|
|
3221
|
-
const absolutePath =
|
|
3600
|
+
const absolutePath = isAbsolute4(path) ? resolve10(path) : resolve10(this.roots[0] ?? process.cwd(), path);
|
|
3222
3601
|
if (this.roots.some((root) => isWithinRoot(root, absolutePath))) {
|
|
3223
3602
|
this.dirtyPaths.add(absolutePath);
|
|
3224
3603
|
}
|
|
@@ -3257,7 +3636,7 @@ var LocalContextIndex = class {
|
|
|
3257
3636
|
const nextFiles = [...files.values()].sort((left, right) => left.absolutePath.localeCompare(right.absolutePath));
|
|
3258
3637
|
const generation = createGeneration(nextFiles);
|
|
3259
3638
|
this.index = {
|
|
3260
|
-
version:
|
|
3639
|
+
version: 3,
|
|
3261
3640
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3262
3641
|
generation,
|
|
3263
3642
|
roots: this.roots,
|
|
@@ -3309,6 +3688,8 @@ var LocalContextIndex = class {
|
|
|
3309
3688
|
files: this.index?.files.length ?? 0,
|
|
3310
3689
|
chunks: this.index?.files.reduce((total, file) => total + file.chunks.length, 0) ?? 0,
|
|
3311
3690
|
queryCacheEntries: this.queryCache.size,
|
|
3691
|
+
queryCacheHits: this.queryCacheHits,
|
|
3692
|
+
queryCacheMisses: this.queryCacheMisses,
|
|
3312
3693
|
refreshState: this.refreshState,
|
|
3313
3694
|
dirtyPaths: this.dirtyPaths.size,
|
|
3314
3695
|
...this.refreshError ? { refreshError: this.refreshError } : {},
|
|
@@ -3391,12 +3772,13 @@ var LocalContextIndex = class {
|
|
|
3391
3772
|
ctimeMs: info.ctimeMs,
|
|
3392
3773
|
size: info.size,
|
|
3393
3774
|
contentHash,
|
|
3394
|
-
chunks: chunkFile(safeItem, content)
|
|
3775
|
+
chunks: chunkFile(safeItem, content),
|
|
3776
|
+
...extractSourceFacts(safeItem.path, content)
|
|
3395
3777
|
});
|
|
3396
3778
|
}
|
|
3397
3779
|
const generation = createGeneration(files);
|
|
3398
3780
|
this.index = {
|
|
3399
|
-
version:
|
|
3781
|
+
version: 3,
|
|
3400
3782
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3401
3783
|
generation,
|
|
3402
3784
|
roots: this.roots,
|
|
@@ -3412,6 +3794,7 @@ var LocalContextIndex = class {
|
|
|
3412
3794
|
);
|
|
3413
3795
|
await atomicWrite(this.indexPath, `${JSON.stringify(this.index)}
|
|
3414
3796
|
`, 384);
|
|
3797
|
+
await this.gitRecency.collect();
|
|
3415
3798
|
onProgress?.({ phase: "done", completed: files.length, total: files.length });
|
|
3416
3799
|
return {
|
|
3417
3800
|
files: files.length,
|
|
@@ -3461,7 +3844,8 @@ var LocalContextIndex = class {
|
|
|
3461
3844
|
ctimeMs: info.ctimeMs,
|
|
3462
3845
|
size: info.size,
|
|
3463
3846
|
contentHash: hashContent(content),
|
|
3464
|
-
chunks: chunkFile(safeItem, content)
|
|
3847
|
+
chunks: chunkFile(safeItem, content),
|
|
3848
|
+
...extractSourceFacts(safeItem.path, content)
|
|
3465
3849
|
};
|
|
3466
3850
|
} catch {
|
|
3467
3851
|
return void 0;
|
|
@@ -3484,7 +3868,8 @@ var LocalContextIndex = class {
|
|
|
3484
3868
|
path: file.path,
|
|
3485
3869
|
absolutePath: file.absolutePath
|
|
3486
3870
|
}, content);
|
|
3487
|
-
|
|
3871
|
+
const expectedFacts = extractSourceFacts(file.path, content);
|
|
3872
|
+
if (!chunksMatch(expectedChunks, file.chunks) || !sameStrings(expectedFacts.definitions, file.definitions) || !sameStrings(expectedFacts.references, file.references) || !sameStrings(expectedFacts.imports, file.imports)) return false;
|
|
3488
3873
|
} catch {
|
|
3489
3874
|
return false;
|
|
3490
3875
|
}
|
|
@@ -3503,7 +3888,7 @@ var LocalContextIndex = class {
|
|
|
3503
3888
|
followSymbolicLinks: false,
|
|
3504
3889
|
ignore: ignorePatterns
|
|
3505
3890
|
});
|
|
3506
|
-
for (const path of paths) discovered.push({ root, path, absolutePath:
|
|
3891
|
+
for (const path of paths) discovered.push({ root, path, absolutePath: resolve10(root, path) });
|
|
3507
3892
|
}
|
|
3508
3893
|
discovered.sort((left, right) => left.absolutePath.localeCompare(right.absolutePath));
|
|
3509
3894
|
return discovered;
|
|
@@ -3523,11 +3908,15 @@ var LocalContextIndex = class {
|
|
|
3523
3908
|
});
|
|
3524
3909
|
return matches.length === 1 ? { root, path, absolutePath } : void 0;
|
|
3525
3910
|
}
|
|
3526
|
-
rank(query, topK) {
|
|
3527
|
-
const
|
|
3911
|
+
rank(query, topK, recencyScores) {
|
|
3912
|
+
const index = this.index;
|
|
3913
|
+
const files = index?.files ?? [];
|
|
3914
|
+
const chunks = files.flatMap((file) => file.chunks);
|
|
3528
3915
|
if (!chunks.length) return [];
|
|
3529
|
-
const
|
|
3530
|
-
if (!
|
|
3916
|
+
const directTerms = [...new Set(tokenize(query))];
|
|
3917
|
+
if (!directTerms.length || !index) return [];
|
|
3918
|
+
const expandedTerms = expandQueryTerms(files, directTerms);
|
|
3919
|
+
const terms = [.../* @__PURE__ */ new Set([...directTerms, ...expandedTerms])];
|
|
3531
3920
|
const queryTerms = new Set(terms);
|
|
3532
3921
|
const documentFrequency = new Map([...queryTerms].map((term) => [term, 0]));
|
|
3533
3922
|
for (const chunk of chunks) {
|
|
@@ -3536,22 +3925,53 @@ var LocalContextIndex = class {
|
|
|
3536
3925
|
}
|
|
3537
3926
|
}
|
|
3538
3927
|
const averageLength = chunks.reduce((sum, chunk) => sum + chunk.tokens.length, 0) / Math.max(chunks.length, 1);
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
score
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3928
|
+
const filesByPath = new Map(files.map((file) => [file.absolutePath, file]));
|
|
3929
|
+
const graphBoosts = buildGraphBoosts(files, directTerms);
|
|
3930
|
+
const coverageTerms = directTerms.filter((term) => !retrievalStopWords.has(term));
|
|
3931
|
+
const scored = chunks.map((chunk) => {
|
|
3932
|
+
const lexical = scoreChunk(
|
|
3933
|
+
chunk,
|
|
3934
|
+
terms,
|
|
3935
|
+
query,
|
|
3936
|
+
documentFrequency,
|
|
3937
|
+
chunks.length,
|
|
3938
|
+
averageLength
|
|
3939
|
+
);
|
|
3940
|
+
const graph = graphBoosts.get(chunk.absolutePath)?.score ?? 0;
|
|
3941
|
+
const recency = recencyScores.get(chunk.absolutePath) ?? 0;
|
|
3942
|
+
const score = lexical.bm25 + lexical.path + lexical.symbol + lexical.phrase + graph + recency;
|
|
3943
|
+
return { chunk, lexical, graph, recency, score };
|
|
3944
|
+
}).filter(({ lexical, graph }) => lexical.bm25 + lexical.path + lexical.symbol + lexical.phrase + graph > 0).sort((left, right) => right.score - left.score || left.chunk.absolutePath.localeCompare(right.chunk.absolutePath));
|
|
3945
|
+
const covered = scored.filter(({ lexical, graph }) => graph > 0 || hasSufficientQueryCoverage(lexical.matchedTerms, coverageTerms));
|
|
3946
|
+
return (covered.length ? covered : scored).slice(0, topK).map(({ chunk, lexical, graph, recency, score }) => {
|
|
3947
|
+
const file = filesByPath.get(chunk.absolutePath);
|
|
3948
|
+
const breakdown = {
|
|
3949
|
+
bm25: roundScore(lexical.bm25),
|
|
3950
|
+
path: roundScore(lexical.path),
|
|
3951
|
+
symbol: roundScore(lexical.symbol),
|
|
3952
|
+
phrase: roundScore(lexical.phrase),
|
|
3953
|
+
graph: roundScore(graph),
|
|
3954
|
+
recency: roundScore(recency),
|
|
3955
|
+
total: roundScore(score)
|
|
3956
|
+
};
|
|
3957
|
+
const provenance = {
|
|
3958
|
+
generation: index.generation,
|
|
3959
|
+
contentHash: file?.contentHash ?? hashContent(chunk.content),
|
|
3960
|
+
matchedTerms: lexical.matchedTerms.slice(0, 16),
|
|
3961
|
+
expandedTerms: expandedTerms.slice(0, 16),
|
|
3962
|
+
score: breakdown
|
|
3963
|
+
};
|
|
3964
|
+
return {
|
|
3965
|
+
path: chunk.absolutePath,
|
|
3966
|
+
startLine: chunk.startLine,
|
|
3967
|
+
endLine: chunk.endLine,
|
|
3968
|
+
content: chunk.content,
|
|
3969
|
+
score,
|
|
3970
|
+
source: "local-bm25+path+symbol+graph+recency",
|
|
3971
|
+
...chunk.symbol ? { symbol: chunk.symbol } : {},
|
|
3972
|
+
provenance
|
|
3973
|
+
};
|
|
3974
|
+
});
|
|
3555
3975
|
}
|
|
3556
3976
|
async hitsAreCurrent(hits) {
|
|
3557
3977
|
const files = new Map((this.index?.files ?? []).map((file) => [file.absolutePath, file]));
|
|
@@ -3573,21 +3993,28 @@ var LocalContextIndex = class {
|
|
|
3573
3993
|
}
|
|
3574
3994
|
return true;
|
|
3575
3995
|
}
|
|
3576
|
-
getCachedHits(query, topK) {
|
|
3996
|
+
getCachedHits(query, topK, rankingGeneration) {
|
|
3577
3997
|
const generation = this.index?.generation;
|
|
3578
|
-
if (!generation)
|
|
3579
|
-
|
|
3998
|
+
if (!generation) {
|
|
3999
|
+
this.queryCacheMisses += 1;
|
|
4000
|
+
return void 0;
|
|
4001
|
+
}
|
|
4002
|
+
const key = `${generation}\0${rankingGeneration}\0${topK}\0${query}`;
|
|
3580
4003
|
const cached = this.queryCache.get(key);
|
|
3581
|
-
if (!cached)
|
|
4004
|
+
if (!cached) {
|
|
4005
|
+
this.queryCacheMisses += 1;
|
|
4006
|
+
return void 0;
|
|
4007
|
+
}
|
|
4008
|
+
this.queryCacheHits += 1;
|
|
3582
4009
|
this.queryCache.delete(key);
|
|
3583
4010
|
this.queryCache.set(key, cached);
|
|
3584
4011
|
return cloneHits(cached);
|
|
3585
4012
|
}
|
|
3586
|
-
cacheHits(query, topK, hits) {
|
|
4013
|
+
cacheHits(query, topK, rankingGeneration, hits) {
|
|
3587
4014
|
if (!hits.length) return;
|
|
3588
4015
|
const generation = this.index?.generation;
|
|
3589
4016
|
if (!generation) return;
|
|
3590
|
-
const key = `${generation}\0${topK}\0${query}`;
|
|
4017
|
+
const key = `${generation}\0${rankingGeneration}\0${topK}\0${query}`;
|
|
3591
4018
|
this.queryCache.delete(key);
|
|
3592
4019
|
this.queryCache.set(key, cloneHits(hits));
|
|
3593
4020
|
while (this.queryCache.size > MAX_QUERY_CACHE_ENTRIES) {
|
|
@@ -3599,7 +4026,7 @@ var LocalContextIndex = class {
|
|
|
3599
4026
|
};
|
|
3600
4027
|
function isWithinRoot(root, path) {
|
|
3601
4028
|
const offset = relative5(root, path);
|
|
3602
|
-
return offset === "" || !offset.startsWith("..") && !
|
|
4029
|
+
return offset === "" || !offset.startsWith("..") && !isAbsolute4(offset);
|
|
3603
4030
|
}
|
|
3604
4031
|
function isIncludedSourcePath(path) {
|
|
3605
4032
|
const name = basename5(path);
|
|
@@ -3674,6 +4101,9 @@ function chunksMatch(expected, actual) {
|
|
|
3674
4101
|
return Boolean(candidate) && chunk.id === candidate?.id && chunk.root === candidate.root && chunk.path === candidate.path && chunk.absolutePath === candidate.absolutePath && chunk.startLine === candidate.startLine && chunk.endLine === candidate.endLine && chunk.content === candidate.content && chunk.symbol === candidate.symbol && chunk.tokens.length === candidate.tokens.length && chunk.tokens.every((token, tokenIndex) => token === candidate.tokens[tokenIndex]);
|
|
3675
4102
|
});
|
|
3676
4103
|
}
|
|
4104
|
+
function sameStrings(expected, actual) {
|
|
4105
|
+
return expected.length === actual.length && expected.every((value, index) => value === actual[index]);
|
|
4106
|
+
}
|
|
3677
4107
|
function existingBuildResult(status, started) {
|
|
3678
4108
|
if (!status.available || !status.generation) {
|
|
3679
4109
|
throw new Error("The existing local context index is unavailable.");
|
|
@@ -3728,7 +4158,8 @@ function packContextHits(hits, roots, maxTokens, engine) {
|
|
|
3728
4158
|
const text = selected.map((hit) => {
|
|
3729
4159
|
const shownPath = workspaceAliasPath(hit.path, roots);
|
|
3730
4160
|
const symbol = hit.symbol ? ` symbol="${escapeAttribute(hit.symbol)}"` : "";
|
|
3731
|
-
|
|
4161
|
+
const provenance = hit.provenance ? ` source="${escapeAttribute(hit.source)}" hash="${hit.provenance.contentHash.slice(0, 12)}" graph-score="${hit.provenance.score.graph.toFixed(3)}" recency-score="${hit.provenance.score.recency.toFixed(6)}"` : "";
|
|
4162
|
+
return `<code path="${escapeAttribute(shownPath)}" lines="${hit.startLine}-${hit.endLine}" score="${hit.score.toFixed(3)}"${symbol}${provenance}>
|
|
3732
4163
|
${hit.content}
|
|
3733
4164
|
</code>`;
|
|
3734
4165
|
}).join("\n\n");
|
|
@@ -3744,7 +4175,17 @@ ${hit.content}
|
|
|
3744
4175
|
};
|
|
3745
4176
|
}
|
|
3746
4177
|
function cloneHits(hits) {
|
|
3747
|
-
return hits.map((hit) => ({
|
|
4178
|
+
return hits.map((hit) => ({
|
|
4179
|
+
...hit,
|
|
4180
|
+
...hit.provenance ? {
|
|
4181
|
+
provenance: {
|
|
4182
|
+
...hit.provenance,
|
|
4183
|
+
matchedTerms: [...hit.provenance.matchedTerms],
|
|
4184
|
+
expandedTerms: [...hit.provenance.expandedTerms],
|
|
4185
|
+
score: { ...hit.provenance.score }
|
|
4186
|
+
}
|
|
4187
|
+
} : {}
|
|
4188
|
+
}));
|
|
3748
4189
|
}
|
|
3749
4190
|
function cloneDuplicationBaseline(baseline) {
|
|
3750
4191
|
return {
|
|
@@ -3765,10 +4206,10 @@ function reconstructIndexedContent(chunks) {
|
|
|
3765
4206
|
return lines.map((line) => line ?? "").join("\n");
|
|
3766
4207
|
}
|
|
3767
4208
|
function hashContent(content) {
|
|
3768
|
-
return
|
|
4209
|
+
return createHash7("sha256").update(content, "utf8").digest("hex");
|
|
3769
4210
|
}
|
|
3770
4211
|
function createGeneration(files) {
|
|
3771
|
-
return
|
|
4212
|
+
return createHash7("sha256").update(files.slice().sort((left, right) => left.absolutePath.localeCompare(right.absolutePath)).map((file) => `${file.absolutePath}\0${file.contentHash}`).join("\n"), "utf8").digest("hex").slice(0, 16);
|
|
3772
4213
|
}
|
|
3773
4214
|
function hasSubstantialOverlap(left, right) {
|
|
3774
4215
|
if (left.path !== right.path) return false;
|
|
@@ -3844,7 +4285,110 @@ function detectSymbol(lines) {
|
|
|
3844
4285
|
if (match?.[1]) return match[1];
|
|
3845
4286
|
}
|
|
3846
4287
|
}
|
|
3847
|
-
return void 0;
|
|
4288
|
+
return void 0;
|
|
4289
|
+
}
|
|
4290
|
+
function extractSourceFacts(path, content) {
|
|
4291
|
+
const extension = extname2(path).toLocaleLowerCase();
|
|
4292
|
+
if ([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(extension)) {
|
|
4293
|
+
return extractTypeScriptFacts(path, content);
|
|
4294
|
+
}
|
|
4295
|
+
if (extension === ".py") return extractPythonFacts(content);
|
|
4296
|
+
if (extension === ".sql") return extractSqlFacts(content);
|
|
4297
|
+
return { definitions: [], references: [], imports: [] };
|
|
4298
|
+
}
|
|
4299
|
+
function extractTypeScriptFacts(path, content) {
|
|
4300
|
+
const definitions = /* @__PURE__ */ new Set();
|
|
4301
|
+
const references = /* @__PURE__ */ new Set();
|
|
4302
|
+
const imports = /* @__PURE__ */ new Set();
|
|
4303
|
+
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, false, scriptKind(path));
|
|
4304
|
+
const addName = (name) => {
|
|
4305
|
+
if (!name) return;
|
|
4306
|
+
if (ts.isIdentifier(name)) definitions.add(name.text);
|
|
4307
|
+
else if (ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) definitions.add(name.text);
|
|
4308
|
+
else if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
|
|
4309
|
+
for (const element of name.elements) if (ts.isBindingElement(element)) addName(element.name);
|
|
4310
|
+
}
|
|
4311
|
+
};
|
|
4312
|
+
const addCallReference = (expression) => {
|
|
4313
|
+
if (ts.isIdentifier(expression)) references.add(expression.text);
|
|
4314
|
+
else if (ts.isPropertyAccessExpression(expression)) references.add(expression.name.text);
|
|
4315
|
+
else if (ts.isElementAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
|
|
4316
|
+
references.add(expression.expression.text);
|
|
4317
|
+
}
|
|
4318
|
+
};
|
|
4319
|
+
const visit = (node) => {
|
|
4320
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
4321
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) imports.add(node.moduleSpecifier.text);
|
|
4322
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && node.moduleReference.expression && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
4323
|
+
imports.add(node.moduleReference.expression.text);
|
|
4324
|
+
} else if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
4325
|
+
addName(node.name);
|
|
4326
|
+
} else if (ts.isVariableDeclaration(node) || ts.isBindingElement(node) || ts.isMethodDeclaration(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) {
|
|
4327
|
+
addName(node.name);
|
|
4328
|
+
} else if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
4329
|
+
addCallReference(node.expression);
|
|
4330
|
+
}
|
|
4331
|
+
ts.forEachChild(node, visit);
|
|
4332
|
+
};
|
|
4333
|
+
visit(source);
|
|
4334
|
+
return {
|
|
4335
|
+
definitions: sortedValues(definitions),
|
|
4336
|
+
references: sortedValues(references),
|
|
4337
|
+
imports: sortedValues(imports)
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
function scriptKind(path) {
|
|
4341
|
+
switch (extname2(path).toLocaleLowerCase()) {
|
|
4342
|
+
case ".tsx":
|
|
4343
|
+
return ts.ScriptKind.TSX;
|
|
4344
|
+
case ".jsx":
|
|
4345
|
+
return ts.ScriptKind.JSX;
|
|
4346
|
+
case ".js":
|
|
4347
|
+
case ".mjs":
|
|
4348
|
+
case ".cjs":
|
|
4349
|
+
return ts.ScriptKind.JS;
|
|
4350
|
+
default:
|
|
4351
|
+
return ts.ScriptKind.TS;
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
function extractPythonFacts(content) {
|
|
4355
|
+
const definitions = /* @__PURE__ */ new Set();
|
|
4356
|
+
const references = /* @__PURE__ */ new Set();
|
|
4357
|
+
const imports = /* @__PURE__ */ new Set();
|
|
4358
|
+
for (const line of content.split("\n")) {
|
|
4359
|
+
const definition = line.match(/^\s*(?:async\s+)?(?:def|class)\s+([\p{L}_][\p{L}\p{N}_]*)/u);
|
|
4360
|
+
if (definition?.[1]) definitions.add(definition[1]);
|
|
4361
|
+
const imported = line.match(/^\s*(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))/u);
|
|
4362
|
+
if (imported?.[1] || imported?.[2]) imports.add(imported[1] ?? imported[2] ?? "");
|
|
4363
|
+
for (const call of line.matchAll(/([\p{L}_][\p{L}\p{N}_]*)\s*\(/gu)) {
|
|
4364
|
+
if (call[1] && !["def", "class", "if", "for", "while"].includes(call[1])) references.add(call[1]);
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
for (const definition of definitions) references.delete(definition);
|
|
4368
|
+
return {
|
|
4369
|
+
definitions: sortedValues(definitions),
|
|
4370
|
+
references: sortedValues(references),
|
|
4371
|
+
imports: sortedValues(imports)
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
function extractSqlFacts(content) {
|
|
4375
|
+
const definitions = /* @__PURE__ */ new Set();
|
|
4376
|
+
const references = /* @__PURE__ */ new Set();
|
|
4377
|
+
const imports = /* @__PURE__ */ new Set();
|
|
4378
|
+
for (const definition of content.matchAll(/\bcreate\s+(?:table|view|function|procedure)\s+(?:if\s+not\s+exists\s+)?([\w.]+)/giu)) {
|
|
4379
|
+
if (definition[1]) definitions.add(definition[1]);
|
|
4380
|
+
}
|
|
4381
|
+
for (const reference2 of content.matchAll(/\b(?:from|join|update|into)\s+([\w.]+)/giu)) {
|
|
4382
|
+
if (reference2[1]) references.add(reference2[1]);
|
|
4383
|
+
}
|
|
4384
|
+
return {
|
|
4385
|
+
definitions: sortedValues(definitions),
|
|
4386
|
+
references: sortedValues(references),
|
|
4387
|
+
imports: sortedValues(imports)
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
function sortedValues(values) {
|
|
4391
|
+
return [...values].filter(Boolean).sort((left, right) => left.localeCompare(right));
|
|
3848
4392
|
}
|
|
3849
4393
|
function tokenize(input2) {
|
|
3850
4394
|
const normalized = input2.replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([\p{Ll}])([\p{Lu}])/gu, "$1 $2").toLocaleLowerCase();
|
|
@@ -3861,24 +4405,123 @@ function tokenize(input2) {
|
|
|
3861
4405
|
}
|
|
3862
4406
|
return output2;
|
|
3863
4407
|
}
|
|
4408
|
+
var retrievalStopWords = /* @__PURE__ */ new Set([
|
|
4409
|
+
"add",
|
|
4410
|
+
"audit",
|
|
4411
|
+
"change",
|
|
4412
|
+
"code",
|
|
4413
|
+
"create",
|
|
4414
|
+
"explain",
|
|
4415
|
+
"find",
|
|
4416
|
+
"fix",
|
|
4417
|
+
"implement",
|
|
4418
|
+
"inspect",
|
|
4419
|
+
"review",
|
|
4420
|
+
"show",
|
|
4421
|
+
"test",
|
|
4422
|
+
"the",
|
|
4423
|
+
"update",
|
|
4424
|
+
"where",
|
|
4425
|
+
"with",
|
|
4426
|
+
"\u4FEE\u6539",
|
|
4427
|
+
"\u4FEE\u590D",
|
|
4428
|
+
"\u5B9E\u73B0",
|
|
4429
|
+
"\u5BA1\u67E5",
|
|
4430
|
+
"\u67E5\u627E",
|
|
4431
|
+
"\u89E3\u91CA",
|
|
4432
|
+
"\u6D4B\u8BD5",
|
|
4433
|
+
"\u66F4\u65B0"
|
|
4434
|
+
]);
|
|
4435
|
+
function hasSufficientQueryCoverage(matchedTerms, coverageTerms) {
|
|
4436
|
+
if (!coverageTerms.length) return matchedTerms.length > 0;
|
|
4437
|
+
const matched = new Set(matchedTerms);
|
|
4438
|
+
const covered = [...new Set(coverageTerms)].filter((term) => matched.has(term)).length;
|
|
4439
|
+
return covered >= Math.min(2, new Set(coverageTerms).size);
|
|
4440
|
+
}
|
|
4441
|
+
function expandQueryTerms(files, directTerms) {
|
|
4442
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
4443
|
+
for (const file of files) {
|
|
4444
|
+
for (const definition of file.definitions) {
|
|
4445
|
+
const definitionTerms = [...new Set(tokenize(definition))];
|
|
4446
|
+
if (!definitionMatchesQuery(definitionTerms, directTerms)) continue;
|
|
4447
|
+
for (const term of definitionTerms) if (!directTerms.includes(term)) expanded.add(term);
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
return [...expanded].sort((left, right) => left.localeCompare(right)).slice(0, 24);
|
|
4451
|
+
}
|
|
4452
|
+
function buildGraphBoosts(files, directTerms) {
|
|
4453
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
4454
|
+
for (const file of files) {
|
|
4455
|
+
for (const definition of file.definitions) {
|
|
4456
|
+
if (!definitionMatchesQuery(tokenize(definition), directTerms)) continue;
|
|
4457
|
+
const values = anchors.get(file.absolutePath) ?? /* @__PURE__ */ new Set();
|
|
4458
|
+
values.add(definition.toLocaleLowerCase());
|
|
4459
|
+
anchors.set(file.absolutePath, values);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
if (!anchors.size) return /* @__PURE__ */ new Map();
|
|
4463
|
+
const boosts = /* @__PURE__ */ new Map();
|
|
4464
|
+
const addBoost = (path, score) => {
|
|
4465
|
+
boosts.set(path, { score: (boosts.get(path)?.score ?? 0) + score });
|
|
4466
|
+
};
|
|
4467
|
+
const anchorSymbols = new Set([...anchors.values()].flatMap((symbols) => [...symbols]));
|
|
4468
|
+
for (const file of files) {
|
|
4469
|
+
if (file.references.some((reference2) => anchorSymbols.has(reference2.toLocaleLowerCase()))) {
|
|
4470
|
+
addBoost(file.absolutePath, 1.5);
|
|
4471
|
+
}
|
|
4472
|
+
for (const specifier of file.imports) {
|
|
4473
|
+
for (const target of resolveImportTargets(file, specifier, files)) {
|
|
4474
|
+
if (anchors.has(target)) addBoost(file.absolutePath, 1.25);
|
|
4475
|
+
if (anchors.has(file.absolutePath)) addBoost(target, 0.75);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
return boosts;
|
|
4480
|
+
}
|
|
4481
|
+
function definitionMatchesQuery(definitionTerms, queryTerms) {
|
|
4482
|
+
const definitions = new Set(definitionTerms);
|
|
4483
|
+
const shared = [...new Set(queryTerms)].filter((term) => definitions.has(term));
|
|
4484
|
+
const required = Math.min(2, Math.max(1, Math.min(definitions.size, queryTerms.length)));
|
|
4485
|
+
return shared.length >= required;
|
|
4486
|
+
}
|
|
4487
|
+
function resolveImportTargets(importer, specifier, files) {
|
|
4488
|
+
if (!specifier.startsWith(".")) return [];
|
|
4489
|
+
const base = posix.normalize(posix.join(posix.dirname(importer.path), specifier));
|
|
4490
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
4491
|
+
const importedExtension = posix.extname(base);
|
|
4492
|
+
const extensionless = extensions.includes(importedExtension) ? base.slice(0, -importedExtension.length) : base;
|
|
4493
|
+
const candidates = /* @__PURE__ */ new Set([
|
|
4494
|
+
base,
|
|
4495
|
+
...extensions.map((extension) => `${extensionless}${extension}`),
|
|
4496
|
+
...extensions.map((extension) => posix.join(extensionless, `index${extension}`))
|
|
4497
|
+
]);
|
|
4498
|
+
return files.filter((file) => file.root === importer.root && candidates.has(file.path)).map((file) => file.absolutePath);
|
|
4499
|
+
}
|
|
3864
4500
|
function scoreChunk(chunk, terms, rawQuery, documentFrequency, documentCount, averageLength) {
|
|
3865
4501
|
const frequencies = /* @__PURE__ */ new Map();
|
|
3866
4502
|
for (const token of chunk.tokens) frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
|
|
3867
4503
|
const k1 = 1.2;
|
|
3868
4504
|
const b = 0.75;
|
|
3869
|
-
let
|
|
4505
|
+
let bm25 = 0;
|
|
4506
|
+
let path = 0;
|
|
4507
|
+
let symbol = 0;
|
|
4508
|
+
const matchedTerms = [];
|
|
3870
4509
|
for (const term of terms) {
|
|
3871
4510
|
const frequency = frequencies.get(term) ?? 0;
|
|
3872
4511
|
if (!frequency) continue;
|
|
4512
|
+
matchedTerms.push(term);
|
|
3873
4513
|
const df = documentFrequency.get(term) ?? 0;
|
|
3874
4514
|
const idf = Math.log(1 + (documentCount - df + 0.5) / (df + 0.5));
|
|
3875
|
-
|
|
3876
|
-
if (chunk.path.toLocaleLowerCase().includes(term))
|
|
3877
|
-
if (chunk.symbol?.toLocaleLowerCase().includes(term))
|
|
4515
|
+
bm25 += idf * (frequency * (k1 + 1) / (frequency + k1 * (1 - b + b * chunk.tokens.length / averageLength)));
|
|
4516
|
+
if (chunk.path.toLocaleLowerCase().includes(term)) path += 1.5;
|
|
4517
|
+
if (chunk.symbol?.toLocaleLowerCase().includes(term)) symbol += 2.5;
|
|
3878
4518
|
}
|
|
3879
4519
|
const phrase = rawQuery.trim().toLocaleLowerCase();
|
|
3880
|
-
|
|
3881
|
-
return
|
|
4520
|
+
const phraseScore = phrase.length > 3 && chunk.content.toLocaleLowerCase().includes(phrase) ? 3 : 0;
|
|
4521
|
+
return { bm25, path, symbol, phrase: phraseScore, matchedTerms };
|
|
4522
|
+
}
|
|
4523
|
+
function roundScore(value) {
|
|
4524
|
+
return Number(value.toFixed(6));
|
|
3882
4525
|
}
|
|
3883
4526
|
|
|
3884
4527
|
// src/context/context-engine.ts
|
|
@@ -3989,7 +4632,10 @@ function formatContextHits(hits, roots) {
|
|
|
3989
4632
|
return hits.map((hit) => {
|
|
3990
4633
|
const path = workspaceAliasPath(hit.path, roots);
|
|
3991
4634
|
const symbol = hit.symbol ? ` ${hit.symbol}` : "";
|
|
3992
|
-
|
|
4635
|
+
const score = hit.provenance?.score;
|
|
4636
|
+
const breakdown = score ? ` bm25=${score.bm25.toFixed(2)} path=${score.path.toFixed(2)} symbol=${score.symbol.toFixed(2)} graph=${score.graph.toFixed(2)} recency=${score.recency.toFixed(6)}` : "";
|
|
4637
|
+
const hash4 = hit.provenance?.contentHash.slice(0, 12);
|
|
4638
|
+
return `[${hit.source} ${hit.score.toFixed(3)}${breakdown}${hash4 ? ` hash=${hash4}` : ""}]${symbol} ${path}:${hit.startLine}-${hit.endLine}`;
|
|
3993
4639
|
}).join("\n");
|
|
3994
4640
|
}
|
|
3995
4641
|
|
|
@@ -4228,7 +4874,7 @@ function toolTokens(messages) {
|
|
|
4228
4874
|
|
|
4229
4875
|
// src/context/mentions.ts
|
|
4230
4876
|
import { readFile as readFile4, stat as stat4 } from "node:fs/promises";
|
|
4231
|
-
import { isAbsolute as
|
|
4877
|
+
import { isAbsolute as isAbsolute5, resolve as resolve11 } from "node:path";
|
|
4232
4878
|
import fg2 from "fast-glob";
|
|
4233
4879
|
var defaultMentionIgnores = [
|
|
4234
4880
|
"**/.git/**",
|
|
@@ -4299,7 +4945,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4299
4945
|
ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
|
|
4300
4946
|
});
|
|
4301
4947
|
matched.push(...await safeMentionPaths(
|
|
4302
|
-
children.slice(0, 25).map((path) =>
|
|
4948
|
+
children.slice(0, 25).map((path) => resolve11(aliased, path)),
|
|
4303
4949
|
workspace
|
|
4304
4950
|
));
|
|
4305
4951
|
}
|
|
@@ -4307,7 +4953,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4307
4953
|
}
|
|
4308
4954
|
}
|
|
4309
4955
|
for (const root of matched.length ? [] : roots) {
|
|
4310
|
-
const direct =
|
|
4956
|
+
const direct = resolve11(root, clean2);
|
|
4311
4957
|
try {
|
|
4312
4958
|
const safeDirect = await workspace.resolvePath(direct);
|
|
4313
4959
|
const info = await stat4(safeDirect);
|
|
@@ -4321,7 +4967,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4321
4967
|
ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
|
|
4322
4968
|
});
|
|
4323
4969
|
matched.push(...await safeMentionPaths(
|
|
4324
|
-
children.slice(0, 25).map((path) =>
|
|
4970
|
+
children.slice(0, 25).map((path) => resolve11(safeDirect, path)),
|
|
4325
4971
|
workspace
|
|
4326
4972
|
));
|
|
4327
4973
|
}
|
|
@@ -4337,7 +4983,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
|
|
|
4337
4983
|
ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
|
|
4338
4984
|
});
|
|
4339
4985
|
matched.push(...await safeMentionPaths(
|
|
4340
|
-
globbed.slice(0, 25).map((path) =>
|
|
4986
|
+
globbed.slice(0, 25).map((path) => resolve11(root, path)),
|
|
4341
4987
|
workspace
|
|
4342
4988
|
));
|
|
4343
4989
|
}
|
|
@@ -4403,7 +5049,7 @@ async function buildMentionPathIndex(roots, options = {}) {
|
|
|
4403
5049
|
})
|
|
4404
5050
|
})));
|
|
4405
5051
|
const paths = filesByRoot.flatMap(({ root, files }) => files.map(
|
|
4406
|
-
(path) => workspaceAliasPath(
|
|
5052
|
+
(path) => workspaceAliasPath(resolve11(root, path), normalizedRoots)
|
|
4407
5053
|
));
|
|
4408
5054
|
return new MentionPathIndex(normalizedRoots, paths);
|
|
4409
5055
|
}
|
|
@@ -4437,22 +5083,22 @@ function mapMentionCandidatePath(path, roots) {
|
|
|
4437
5083
|
const primary = normalizedRoots[0];
|
|
4438
5084
|
if (!primary || !path || path.includes("\0")) return void 0;
|
|
4439
5085
|
let candidate;
|
|
4440
|
-
if (
|
|
4441
|
-
candidate =
|
|
5086
|
+
if (isAbsolute5(path)) {
|
|
5087
|
+
candidate = resolve11(path);
|
|
4442
5088
|
} else {
|
|
4443
5089
|
const normalized = path.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
4444
5090
|
const [first = "", ...rest] = normalized.split("/");
|
|
4445
5091
|
const alias = first.toLocaleLowerCase();
|
|
4446
5092
|
const workspace = alias.match(/^workspace(\d+)$/);
|
|
4447
5093
|
if (alias === "main") {
|
|
4448
|
-
candidate =
|
|
5094
|
+
candidate = resolve11(primary, ...rest);
|
|
4449
5095
|
} else if (workspace) {
|
|
4450
5096
|
const index = Number(workspace[1]) - 1;
|
|
4451
5097
|
const root = Number.isSafeInteger(index) && index >= 1 ? normalizedRoots[index] : void 0;
|
|
4452
5098
|
if (!root) return void 0;
|
|
4453
|
-
candidate =
|
|
5099
|
+
candidate = resolve11(root, ...rest);
|
|
4454
5100
|
} else {
|
|
4455
|
-
candidate =
|
|
5101
|
+
candidate = resolve11(primary, normalized);
|
|
4456
5102
|
}
|
|
4457
5103
|
}
|
|
4458
5104
|
if (!normalizedRoots.some((root) => isInside(root, candidate))) return void 0;
|
|
@@ -4482,7 +5128,7 @@ function rankMention(path, needle) {
|
|
|
4482
5128
|
}
|
|
4483
5129
|
function normalizeRoots(roots) {
|
|
4484
5130
|
const values = typeof roots === "string" ? [roots] : roots;
|
|
4485
|
-
return [...new Set(values.map((root) =>
|
|
5131
|
+
return [...new Set(values.map((root) => resolve11(root)))];
|
|
4486
5132
|
}
|
|
4487
5133
|
function normalizeMentionCandidate(path) {
|
|
4488
5134
|
const normalized = path.replaceAll("\\", "/").normalize("NFC");
|
|
@@ -5148,9 +5794,9 @@ function createProvider(config) {
|
|
|
5148
5794
|
}
|
|
5149
5795
|
|
|
5150
5796
|
// src/checkpoint/store.ts
|
|
5151
|
-
import { createHash as
|
|
5152
|
-
import { lstat as
|
|
5153
|
-
import { basename as basename6, dirname as dirname8, join as
|
|
5797
|
+
import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
|
|
5798
|
+
import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
|
|
5799
|
+
import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
|
|
5154
5800
|
import { z as z4 } from "zod";
|
|
5155
5801
|
var entrySchema = z4.object({
|
|
5156
5802
|
path: z4.string(),
|
|
@@ -5175,7 +5821,7 @@ var CheckpointStore = class {
|
|
|
5175
5821
|
constructor(workspace, directory) {
|
|
5176
5822
|
this.workspace = typeof workspace === "string" ? new WorkspaceAccess([workspace]) : workspace;
|
|
5177
5823
|
this.managedDirectory = directory === void 0;
|
|
5178
|
-
this.directory = directory ?
|
|
5824
|
+
this.directory = directory ? resolve12(directory) : join9(resolveProjectNamespaceSync(this.workspace.primaryRoot).active, "checkpoints");
|
|
5179
5825
|
}
|
|
5180
5826
|
async capture(sessionId, paths, options = {}) {
|
|
5181
5827
|
validateIdentifier(sessionId, "session");
|
|
@@ -5185,8 +5831,8 @@ var CheckpointStore = class {
|
|
|
5185
5831
|
}
|
|
5186
5832
|
async captureUnlocked(sessionId, unique3, options) {
|
|
5187
5833
|
const id = `${Date.now().toString(36)}-${randomUUID7()}`;
|
|
5188
|
-
const target =
|
|
5189
|
-
const blobDirectory =
|
|
5834
|
+
const target = join9(this.directory, sessionId, id);
|
|
5835
|
+
const blobDirectory = join9(target, "blobs");
|
|
5190
5836
|
await this.ensureDirectory();
|
|
5191
5837
|
await this.assertManagedPath(target);
|
|
5192
5838
|
await mkdir7(blobDirectory, { recursive: true });
|
|
@@ -5195,14 +5841,14 @@ var CheckpointStore = class {
|
|
|
5195
5841
|
for (const input2 of unique3) {
|
|
5196
5842
|
const path = await this.workspace.resolvePath(input2, { allowMissing: true });
|
|
5197
5843
|
try {
|
|
5198
|
-
const info = await
|
|
5844
|
+
const info = await lstat10(path);
|
|
5199
5845
|
if (info.isSymbolicLink()) throw new Error(`Cannot checkpoint a symbolic link: ${input2}`);
|
|
5200
5846
|
if (!info.isFile()) throw new Error(`Cannot checkpoint a non-file path: ${input2}`);
|
|
5201
5847
|
if (info.size > 25e6) {
|
|
5202
5848
|
throw new Error(`File is too large to checkpoint (${info.size} bytes): ${input2}`);
|
|
5203
5849
|
}
|
|
5204
|
-
const blob = `${
|
|
5205
|
-
await atomicWrite(
|
|
5850
|
+
const blob = `${createHash8("sha256").update(path).digest("hex")}.bin`;
|
|
5851
|
+
await atomicWrite(join9(blobDirectory, blob), await readFile5(path), 384);
|
|
5206
5852
|
entries.push({
|
|
5207
5853
|
path,
|
|
5208
5854
|
relativePath: relative6(this.workspace.primaryRoot, path),
|
|
@@ -5229,7 +5875,7 @@ var CheckpointStore = class {
|
|
|
5229
5875
|
entries
|
|
5230
5876
|
};
|
|
5231
5877
|
await atomicWrite(
|
|
5232
|
-
|
|
5878
|
+
join9(target, "manifest.json"),
|
|
5233
5879
|
`${JSON.stringify(manifest, null, 2)}
|
|
5234
5880
|
`,
|
|
5235
5881
|
384
|
|
@@ -5242,9 +5888,9 @@ var CheckpointStore = class {
|
|
|
5242
5888
|
if (!await this.directoryAvailable()) {
|
|
5243
5889
|
throw new Error(`Checkpoint not found: ${checkpointId}`);
|
|
5244
5890
|
}
|
|
5245
|
-
const checkpointDirectory =
|
|
5891
|
+
const checkpointDirectory = join9(this.directory, sessionId, checkpointId);
|
|
5246
5892
|
await this.assertManagedPath(checkpointDirectory);
|
|
5247
|
-
const manifestPath =
|
|
5893
|
+
const manifestPath = join9(checkpointDirectory, "manifest.json");
|
|
5248
5894
|
await this.assertManagedFile(manifestPath);
|
|
5249
5895
|
const raw = await readFile5(manifestPath, "utf8");
|
|
5250
5896
|
const manifest = manifestSchema.parse(JSON.parse(raw));
|
|
@@ -5255,7 +5901,7 @@ var CheckpointStore = class {
|
|
|
5255
5901
|
}
|
|
5256
5902
|
async restore(sessionId, checkpointId) {
|
|
5257
5903
|
const manifest = await this.load(sessionId, checkpointId);
|
|
5258
|
-
const blobDirectory =
|
|
5904
|
+
const blobDirectory = join9(this.directory, sessionId, checkpointId, "blobs");
|
|
5259
5905
|
await this.assertManagedPath(blobDirectory);
|
|
5260
5906
|
const actions = [];
|
|
5261
5907
|
for (const entry of manifest.entries) {
|
|
@@ -5270,8 +5916,8 @@ var CheckpointStore = class {
|
|
|
5270
5916
|
if (!/^[a-f0-9]{64}\.bin$/.test(entry.blob) || basename6(entry.blob) !== entry.blob) {
|
|
5271
5917
|
throw new Error(`Checkpoint blob name is invalid for ${entry.relativePath}.`);
|
|
5272
5918
|
}
|
|
5273
|
-
const blobPath =
|
|
5274
|
-
const blobInfo = await
|
|
5919
|
+
const blobPath = join9(blobDirectory, entry.blob);
|
|
5920
|
+
const blobInfo = await lstat10(blobPath);
|
|
5275
5921
|
if (!blobInfo.isFile() || blobInfo.isSymbolicLink()) {
|
|
5276
5922
|
throw new Error(`Checkpoint blob is not a regular file for ${entry.relativePath}.`);
|
|
5277
5923
|
}
|
|
@@ -5312,7 +5958,7 @@ var CheckpointStore = class {
|
|
|
5312
5958
|
async list(sessionId) {
|
|
5313
5959
|
validateIdentifier(sessionId, "session");
|
|
5314
5960
|
if (!await this.directoryAvailable()) return [];
|
|
5315
|
-
const directory =
|
|
5961
|
+
const directory = join9(this.directory, sessionId);
|
|
5316
5962
|
let names;
|
|
5317
5963
|
try {
|
|
5318
5964
|
names = await readdir2(directory);
|
|
@@ -5351,7 +5997,7 @@ var CheckpointStore = class {
|
|
|
5351
5997
|
await assertNoSymlinkPath(this.workspace.primaryRoot, this.directory);
|
|
5352
5998
|
}
|
|
5353
5999
|
try {
|
|
5354
|
-
const info = await
|
|
6000
|
+
const info = await lstat10(this.directory);
|
|
5355
6001
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
5356
6002
|
throw new Error(`Checkpoint storage is not a regular directory: ${this.directory}`);
|
|
5357
6003
|
}
|
|
@@ -5370,7 +6016,7 @@ var CheckpointStore = class {
|
|
|
5370
6016
|
await this.assertManagedPath(dirname8(path));
|
|
5371
6017
|
if (!this.managedDirectory) return;
|
|
5372
6018
|
try {
|
|
5373
|
-
if ((await
|
|
6019
|
+
if ((await lstat10(path)).isSymbolicLink()) {
|
|
5374
6020
|
throw new Error(`Checkpoint path cannot contain a symbolic link: ${path}`);
|
|
5375
6021
|
}
|
|
5376
6022
|
} catch (error) {
|
|
@@ -5380,7 +6026,7 @@ var CheckpointStore = class {
|
|
|
5380
6026
|
};
|
|
5381
6027
|
async function readSnapshot(path) {
|
|
5382
6028
|
try {
|
|
5383
|
-
const info = await
|
|
6029
|
+
const info = await lstat10(path);
|
|
5384
6030
|
if (info.isSymbolicLink()) throw new Error(`Cannot restore over a symbolic link: ${path}`);
|
|
5385
6031
|
if (!info.isFile()) throw new Error(`Cannot restore over a non-file path: ${path}`);
|
|
5386
6032
|
return { content: await readFile5(path), mode: info.mode };
|
|
@@ -5405,174 +6051,6 @@ function validateIdentifier(value, kind) {
|
|
|
5405
6051
|
}
|
|
5406
6052
|
}
|
|
5407
6053
|
|
|
5408
|
-
// src/utils/process.ts
|
|
5409
|
-
import { spawn } from "node:child_process";
|
|
5410
|
-
import { constants as constants3 } from "node:fs";
|
|
5411
|
-
import { access as access2, lstat as lstat10, realpath as realpath6 } from "node:fs/promises";
|
|
5412
|
-
import { delimiter, isAbsolute as isAbsolute5, join as join9, resolve as resolve11 } from "node:path";
|
|
5413
|
-
import { StringDecoder } from "node:string_decoder";
|
|
5414
|
-
async function resolveExecutableRuntime(command2, cwd, excludedRoots = []) {
|
|
5415
|
-
const realRoots = await Promise.all(excludedRoots.map(async (root) => {
|
|
5416
|
-
try {
|
|
5417
|
-
return await realpath6(root);
|
|
5418
|
-
} catch {
|
|
5419
|
-
return resolve11(root);
|
|
5420
|
-
}
|
|
5421
|
-
}));
|
|
5422
|
-
const pathEntries = (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean);
|
|
5423
|
-
const safeDirectories2 = [];
|
|
5424
|
-
let executable2;
|
|
5425
|
-
const explicit = isAbsolute5(command2) || command2.includes("/") || command2.includes("\\");
|
|
5426
|
-
const explicitPath = explicit ? resolve11(cwd, command2) : void 0;
|
|
5427
|
-
for (const entry of pathEntries) {
|
|
5428
|
-
let directory;
|
|
5429
|
-
try {
|
|
5430
|
-
directory = await realpath6(resolve11(cwd, entry));
|
|
5431
|
-
if (!(await lstat10(directory)).isDirectory()) continue;
|
|
5432
|
-
} catch {
|
|
5433
|
-
continue;
|
|
5434
|
-
}
|
|
5435
|
-
if (realRoots.some((root) => isInside(root, directory))) continue;
|
|
5436
|
-
let contaminated = false;
|
|
5437
|
-
if (!explicit) {
|
|
5438
|
-
for (const name of executableNames(command2)) {
|
|
5439
|
-
const candidate = join9(directory, name);
|
|
5440
|
-
const resolvedCandidate = await usableExecutable(candidate);
|
|
5441
|
-
if (!resolvedCandidate) continue;
|
|
5442
|
-
if (realRoots.some((root) => isInside(root, resolvedCandidate))) {
|
|
5443
|
-
contaminated = true;
|
|
5444
|
-
continue;
|
|
5445
|
-
}
|
|
5446
|
-
executable2 ??= resolvedCandidate;
|
|
5447
|
-
}
|
|
5448
|
-
}
|
|
5449
|
-
if (!contaminated && !safeDirectories2.includes(directory)) safeDirectories2.push(directory);
|
|
5450
|
-
}
|
|
5451
|
-
if (explicitPath) executable2 = await usableExecutable(explicitPath);
|
|
5452
|
-
if (!executable2) return void 0;
|
|
5453
|
-
return { executable: executable2, path: safeDirectories2.join(delimiter) };
|
|
5454
|
-
}
|
|
5455
|
-
async function usableExecutable(candidate) {
|
|
5456
|
-
try {
|
|
5457
|
-
await access2(candidate, process.platform === "win32" ? constants3.F_OK : constants3.X_OK);
|
|
5458
|
-
const resolvedCandidate = await realpath6(candidate);
|
|
5459
|
-
return (await lstat10(resolvedCandidate)).isFile() ? resolvedCandidate : void 0;
|
|
5460
|
-
} catch {
|
|
5461
|
-
return void 0;
|
|
5462
|
-
}
|
|
5463
|
-
}
|
|
5464
|
-
function executableNames(command2) {
|
|
5465
|
-
if (process.platform !== "win32") return [command2];
|
|
5466
|
-
const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
|
|
5467
|
-
return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
|
|
5468
|
-
}
|
|
5469
|
-
function runProcess(command2, args, options) {
|
|
5470
|
-
return new Promise((resolve24, reject) => {
|
|
5471
|
-
const started = Date.now();
|
|
5472
|
-
const environment = options.inheritEnv === false ? {} : { ...process.env };
|
|
5473
|
-
for (const name of options.unsetEnv ?? []) delete environment[name];
|
|
5474
|
-
for (const name of Object.keys(environment)) {
|
|
5475
|
-
if (options.unsetEnvPrefixes?.some((prefix) => name.startsWith(prefix))) {
|
|
5476
|
-
delete environment[name];
|
|
5477
|
-
}
|
|
5478
|
-
}
|
|
5479
|
-
Object.assign(environment, options.env);
|
|
5480
|
-
const child = spawn(command2, args, {
|
|
5481
|
-
cwd: options.cwd,
|
|
5482
|
-
env: environment,
|
|
5483
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
5484
|
-
signal: options.signal
|
|
5485
|
-
});
|
|
5486
|
-
const maxBytes = options.maxOutputBytes ?? 1e6;
|
|
5487
|
-
let stdout = "";
|
|
5488
|
-
let stderr = "";
|
|
5489
|
-
let stdoutBytes = 0;
|
|
5490
|
-
let stderrBytes = 0;
|
|
5491
|
-
let stdoutObservedBytes = 0;
|
|
5492
|
-
let stderrObservedBytes = 0;
|
|
5493
|
-
let timedOut = false;
|
|
5494
|
-
let callbackError;
|
|
5495
|
-
const stdoutDecoder = new StringDecoder("utf8");
|
|
5496
|
-
const stderrDecoder = new StringDecoder("utf8");
|
|
5497
|
-
const stdoutCallbackDecoder = new StringDecoder("utf8");
|
|
5498
|
-
const stderrCallbackDecoder = new StringDecoder("utf8");
|
|
5499
|
-
const append = (decoder, chunk, usedBytes) => {
|
|
5500
|
-
if (usedBytes >= maxBytes) return { text: "", usedBytes };
|
|
5501
|
-
const selected = chunk.subarray(0, maxBytes - usedBytes);
|
|
5502
|
-
return { text: decoder.write(selected), usedBytes: usedBytes + selected.length };
|
|
5503
|
-
};
|
|
5504
|
-
const notify = (callback, decoder, chunk) => {
|
|
5505
|
-
if (!callback || callbackError) return;
|
|
5506
|
-
try {
|
|
5507
|
-
const decoded = decoder.write(chunk);
|
|
5508
|
-
if (decoded) callback(decoded);
|
|
5509
|
-
} catch (error) {
|
|
5510
|
-
callbackError = error;
|
|
5511
|
-
child.kill("SIGTERM");
|
|
5512
|
-
}
|
|
5513
|
-
};
|
|
5514
|
-
child.stdout.on("data", (chunk) => {
|
|
5515
|
-
stdoutObservedBytes += chunk.length;
|
|
5516
|
-
const appended = append(stdoutDecoder, chunk, stdoutBytes);
|
|
5517
|
-
stdout += appended.text;
|
|
5518
|
-
stdoutBytes = appended.usedBytes;
|
|
5519
|
-
notify(options.onStdout, stdoutCallbackDecoder, chunk);
|
|
5520
|
-
});
|
|
5521
|
-
child.stderr.on("data", (chunk) => {
|
|
5522
|
-
stderrObservedBytes += chunk.length;
|
|
5523
|
-
const appended = append(stderrDecoder, chunk, stderrBytes);
|
|
5524
|
-
stderr += appended.text;
|
|
5525
|
-
stderrBytes = appended.usedBytes;
|
|
5526
|
-
notify(options.onStderr, stderrCallbackDecoder, chunk);
|
|
5527
|
-
});
|
|
5528
|
-
child.on("error", reject);
|
|
5529
|
-
const timeoutMs = options.timeoutMs ?? 12e4;
|
|
5530
|
-
const timeout = timeoutMs > 0 ? setTimeout(() => {
|
|
5531
|
-
timedOut = true;
|
|
5532
|
-
child.kill("SIGTERM");
|
|
5533
|
-
setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
|
|
5534
|
-
}, timeoutMs) : void 0;
|
|
5535
|
-
child.on("close", (code) => {
|
|
5536
|
-
if (timeout) clearTimeout(timeout);
|
|
5537
|
-
const stdoutTail = stdoutDecoder.end();
|
|
5538
|
-
const stderrTail = stderrDecoder.end();
|
|
5539
|
-
if (Buffer.byteLength(stdout) + Buffer.byteLength(stdoutTail) <= maxBytes) stdout += stdoutTail;
|
|
5540
|
-
if (Buffer.byteLength(stderr) + Buffer.byteLength(stderrTail) <= maxBytes) stderr += stderrTail;
|
|
5541
|
-
try {
|
|
5542
|
-
const stdoutCallbackTail = stdoutCallbackDecoder.end();
|
|
5543
|
-
const stderrCallbackTail = stderrCallbackDecoder.end();
|
|
5544
|
-
if (stdoutCallbackTail && options.onStdout && !callbackError) options.onStdout(stdoutCallbackTail);
|
|
5545
|
-
if (stderrCallbackTail && options.onStderr && !callbackError) options.onStderr(stderrCallbackTail);
|
|
5546
|
-
} catch (error) {
|
|
5547
|
-
callbackError = error;
|
|
5548
|
-
}
|
|
5549
|
-
if (callbackError) {
|
|
5550
|
-
reject(callbackError);
|
|
5551
|
-
return;
|
|
5552
|
-
}
|
|
5553
|
-
resolve24({
|
|
5554
|
-
command: [command2, ...args].join(" "),
|
|
5555
|
-
exitCode: code ?? (timedOut ? 124 : 1),
|
|
5556
|
-
stdout,
|
|
5557
|
-
stderr,
|
|
5558
|
-
timedOut,
|
|
5559
|
-
durationMs: Date.now() - started,
|
|
5560
|
-
stdoutBytes: stdoutObservedBytes,
|
|
5561
|
-
stderrBytes: stderrObservedBytes,
|
|
5562
|
-
stdoutTruncated: stdoutObservedBytes > stdoutBytes,
|
|
5563
|
-
stderrTruncated: stderrObservedBytes > stderrBytes
|
|
5564
|
-
});
|
|
5565
|
-
});
|
|
5566
|
-
if (options.stdin) child.stdin.end(options.stdin);
|
|
5567
|
-
else child.stdin.end();
|
|
5568
|
-
});
|
|
5569
|
-
}
|
|
5570
|
-
function runShell(command2, cwd, options = {}) {
|
|
5571
|
-
const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/sh";
|
|
5572
|
-
const args = process.platform === "win32" ? ["/d", "/s", "/c", command2] : ["-lc", command2];
|
|
5573
|
-
return runProcess(shell, args, { ...options, cwd });
|
|
5574
|
-
}
|
|
5575
|
-
|
|
5576
6054
|
// src/hooks/runner.ts
|
|
5577
6055
|
var HookError = class extends Error {
|
|
5578
6056
|
constructor(message2, result) {
|
|
@@ -5642,7 +6120,7 @@ import {
|
|
|
5642
6120
|
stat as stat5,
|
|
5643
6121
|
unlink as unlink3
|
|
5644
6122
|
} from "node:fs/promises";
|
|
5645
|
-
import { basename as basename7, dirname as dirname9, join as join10, resolve as
|
|
6123
|
+
import { basename as basename7, dirname as dirname9, join as join10, resolve as resolve13 } from "node:path";
|
|
5646
6124
|
import { z as z5 } from "zod";
|
|
5647
6125
|
var sessionIdSchema = z5.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/);
|
|
5648
6126
|
var toolCallSchema = z5.object({
|
|
@@ -5901,9 +6379,9 @@ var SessionStore = class {
|
|
|
5901
6379
|
managedDirectory;
|
|
5902
6380
|
writes = Promise.resolve();
|
|
5903
6381
|
constructor(workspace, directory) {
|
|
5904
|
-
this.workspace =
|
|
6382
|
+
this.workspace = resolve13(workspace);
|
|
5905
6383
|
this.managedDirectory = directory === void 0;
|
|
5906
|
-
this.directory = directory ?
|
|
6384
|
+
this.directory = directory ? resolve13(directory) : join10(resolveProjectNamespaceSync(this.workspace).active, "sessions");
|
|
5907
6385
|
}
|
|
5908
6386
|
async create(options) {
|
|
5909
6387
|
const session = createSession({ ...options, workspace: this.workspace });
|
|
@@ -5912,7 +6390,7 @@ var SessionStore = class {
|
|
|
5912
6390
|
}
|
|
5913
6391
|
async save(session) {
|
|
5914
6392
|
validateId(session.id);
|
|
5915
|
-
if (
|
|
6393
|
+
if (resolve13(session.workspace) !== this.workspace) {
|
|
5916
6394
|
throw new Error("Session workspace does not match this store.");
|
|
5917
6395
|
}
|
|
5918
6396
|
session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5951,7 +6429,7 @@ var SessionStore = class {
|
|
|
5951
6429
|
const summaries = await Promise.all(
|
|
5952
6430
|
entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => {
|
|
5953
6431
|
const session = await tryReadSession(join10(this.directory, entry.name));
|
|
5954
|
-
if (!session ||
|
|
6432
|
+
if (!session || resolve13(session.workspace) !== this.workspace) return;
|
|
5955
6433
|
return toSummary(session);
|
|
5956
6434
|
})
|
|
5957
6435
|
);
|
|
@@ -6022,7 +6500,7 @@ var SessionStore = class {
|
|
|
6022
6500
|
for (const candidate of candidates) {
|
|
6023
6501
|
await this.assertManagedFile(candidate.path);
|
|
6024
6502
|
const session = await tryReadSession(candidate.path);
|
|
6025
|
-
if (session?.id === id &&
|
|
6503
|
+
if (session?.id === id && resolve13(session.workspace) === this.workspace) return session;
|
|
6026
6504
|
}
|
|
6027
6505
|
return void 0;
|
|
6028
6506
|
}
|
|
@@ -6036,7 +6514,7 @@ var SessionStore = class {
|
|
|
6036
6514
|
}
|
|
6037
6515
|
}
|
|
6038
6516
|
assertWorkspace(session) {
|
|
6039
|
-
if (
|
|
6517
|
+
if (resolve13(session.workspace) !== this.workspace) {
|
|
6040
6518
|
throw new Error("Stored session belongs to a different workspace.");
|
|
6041
6519
|
}
|
|
6042
6520
|
return session;
|
|
@@ -6095,7 +6573,7 @@ function createSession(options) {
|
|
|
6095
6573
|
return {
|
|
6096
6574
|
id,
|
|
6097
6575
|
title: cleanTitle(options.title ?? "New session"),
|
|
6098
|
-
workspace:
|
|
6576
|
+
workspace: resolve13(options.workspace),
|
|
6099
6577
|
createdAt: now,
|
|
6100
6578
|
updatedAt: now,
|
|
6101
6579
|
model: options.model,
|
|
@@ -6150,9 +6628,9 @@ async function exists2(path) {
|
|
|
6150
6628
|
}
|
|
6151
6629
|
|
|
6152
6630
|
// src/session/tool-artifacts.ts
|
|
6153
|
-
import { createHash as
|
|
6631
|
+
import { createHash as createHash9 } from "node:crypto";
|
|
6154
6632
|
import { lstat as lstat12, readFile as readFile7, readdir as readdir4, rm as rm2 } from "node:fs/promises";
|
|
6155
|
-
import { join as join11, resolve as
|
|
6633
|
+
import { join as join11, resolve as resolve14 } from "node:path";
|
|
6156
6634
|
import { z as z6 } from "zod";
|
|
6157
6635
|
var MAX_ARTIFACT_BYTES = 5 * 1024 * 1024;
|
|
6158
6636
|
var MAX_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
@@ -6182,9 +6660,9 @@ var ToolArtifactStore = class {
|
|
|
6182
6660
|
retentionMs;
|
|
6183
6661
|
writes = Promise.resolve();
|
|
6184
6662
|
constructor(workspace, options = {}) {
|
|
6185
|
-
this.workspace =
|
|
6663
|
+
this.workspace = resolve14(workspace);
|
|
6186
6664
|
this.managedDirectory = options.directory === void 0;
|
|
6187
|
-
this.directory = options.directory ?
|
|
6665
|
+
this.directory = options.directory ? resolve14(options.directory) : join11(resolveProjectNamespaceSync(this.workspace).active, "tool-artifacts");
|
|
6188
6666
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
6189
6667
|
this.maxArtifactBytes = options.maxArtifactBytes ?? MAX_ARTIFACT_BYTES;
|
|
6190
6668
|
this.maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
|
|
@@ -6241,7 +6719,7 @@ var ToolArtifactStore = class {
|
|
|
6241
6719
|
}
|
|
6242
6720
|
if (retainedBytes + storageBytes > this.maxTotalBytes) return { stored: false, reason: "total_limit" };
|
|
6243
6721
|
const path = this.pathFor(sessionId, toolCallId);
|
|
6244
|
-
await ensureWorkspaceStorageDirectory(this.workspace,
|
|
6722
|
+
await ensureWorkspaceStorageDirectory(this.workspace, resolve14(path, ".."), {
|
|
6245
6723
|
requireActiveNamespace: this.managedDirectory
|
|
6246
6724
|
});
|
|
6247
6725
|
await atomicWrite(path, `${JSON.stringify(artifact)}
|
|
@@ -6392,7 +6870,7 @@ var ToolArtifactStore = class {
|
|
|
6392
6870
|
}
|
|
6393
6871
|
}
|
|
6394
6872
|
async assertRegularFile(path) {
|
|
6395
|
-
await assertNoSymlinkPath(this.workspace,
|
|
6873
|
+
await assertNoSymlinkPath(this.workspace, resolve14(path, ".."));
|
|
6396
6874
|
try {
|
|
6397
6875
|
const info = await lstat12(path);
|
|
6398
6876
|
if (info.isSymbolicLink() || !info.isFile()) {
|
|
@@ -6429,7 +6907,7 @@ function reference(artifact) {
|
|
|
6429
6907
|
};
|
|
6430
6908
|
}
|
|
6431
6909
|
function hash2(value) {
|
|
6432
|
-
return
|
|
6910
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
6433
6911
|
}
|
|
6434
6912
|
function storedBytes(artifact) {
|
|
6435
6913
|
return Buffer.byteLength(JSON.stringify(artifact)) + 1;
|
|
@@ -6976,48 +7454,6 @@ var knownCommands = /* @__PURE__ */ new Set([
|
|
|
6976
7454
|
...networkCommands,
|
|
6977
7455
|
...workspaceMutationCommands
|
|
6978
7456
|
]);
|
|
6979
|
-
var unsafeInheritedGitEnvironment = [
|
|
6980
|
-
"GIT_DIR",
|
|
6981
|
-
"GIT_WORK_TREE",
|
|
6982
|
-
"GIT_COMMON_DIR",
|
|
6983
|
-
"GIT_INDEX_FILE",
|
|
6984
|
-
"GIT_OBJECT_DIRECTORY",
|
|
6985
|
-
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
|
|
6986
|
-
"GIT_NAMESPACE",
|
|
6987
|
-
"GIT_CEILING_DIRECTORIES",
|
|
6988
|
-
"GIT_DISCOVERY_ACROSS_FILESYSTEM",
|
|
6989
|
-
"GIT_EXTERNAL_DIFF",
|
|
6990
|
-
"GIT_DIFF_OPTS",
|
|
6991
|
-
"GIT_SSH",
|
|
6992
|
-
"GIT_SSH_COMMAND",
|
|
6993
|
-
"GIT_PROXY_COMMAND",
|
|
6994
|
-
"GIT_ASKPASS",
|
|
6995
|
-
"SSH_ASKPASS",
|
|
6996
|
-
"GIT_EXEC_PATH",
|
|
6997
|
-
"GIT_TEMPLATE_DIR",
|
|
6998
|
-
"GIT_CONFIG",
|
|
6999
|
-
"GIT_CONFIG_PARAMETERS",
|
|
7000
|
-
"GIT_QUARANTINE_PATH",
|
|
7001
|
-
"Path"
|
|
7002
|
-
];
|
|
7003
|
-
var isolatedGitEnvironment = {
|
|
7004
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
7005
|
-
GIT_CONFIG_NOSYSTEM: "1",
|
|
7006
|
-
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
7007
|
-
GIT_CONFIG_SYSTEM: "/dev/null",
|
|
7008
|
-
GIT_PAGER: "cat",
|
|
7009
|
-
GIT_EDITOR: "true",
|
|
7010
|
-
GIT_SEQUENCE_EDITOR: "true",
|
|
7011
|
-
GIT_CONFIG_COUNT: "4",
|
|
7012
|
-
GIT_CONFIG_KEY_0: "core.hooksPath",
|
|
7013
|
-
GIT_CONFIG_VALUE_0: "/dev/null",
|
|
7014
|
-
GIT_CONFIG_KEY_1: "core.fsmonitor",
|
|
7015
|
-
GIT_CONFIG_VALUE_1: "false",
|
|
7016
|
-
GIT_CONFIG_KEY_2: "credential.helper",
|
|
7017
|
-
GIT_CONFIG_VALUE_2: "",
|
|
7018
|
-
GIT_CONFIG_KEY_3: "protocol.ext.allow",
|
|
7019
|
-
GIT_CONFIG_VALUE_3: "never"
|
|
7020
|
-
};
|
|
7021
7457
|
var diffCommands = /* @__PURE__ */ new Set(["diff", "log", "show", "whatchanged"]);
|
|
7022
7458
|
var gitTool = {
|
|
7023
7459
|
definition: {
|
|
@@ -7065,13 +7501,9 @@ var gitTool = {
|
|
|
7065
7501
|
}
|
|
7066
7502
|
const runtime = await resolveExecutableRuntime("git", cwd, context.workspace.roots);
|
|
7067
7503
|
if (!runtime) throw new Error("Git executable was not found outside the configured workspace roots.");
|
|
7068
|
-
const status = await
|
|
7069
|
-
cwd,
|
|
7504
|
+
const status = await runIsolatedGit(runtime, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], cwd, {
|
|
7070
7505
|
timeoutMs: 15e3,
|
|
7071
|
-
maxOutputBytes: 2e6
|
|
7072
|
-
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
7073
|
-
unsetEnv: unsafeInheritedGitEnvironment,
|
|
7074
|
-
unsetEnvPrefixes: ["GIT_"]
|
|
7506
|
+
maxOutputBytes: 2e6
|
|
7075
7507
|
});
|
|
7076
7508
|
if (status.exitCode === 0) {
|
|
7077
7509
|
for (const record of status.stdout.split("\0")) {
|
|
@@ -7098,15 +7530,11 @@ var gitTool = {
|
|
|
7098
7530
|
const runtime = await resolveExecutableRuntime("git", cwd, context.workspace.roots);
|
|
7099
7531
|
if (!runtime) throw new Error("Git executable was not found outside the configured workspace roots.");
|
|
7100
7532
|
const before = worktreeTrackingCommands.has(command2) ? await captureGitState(runtime, cwd) : void 0;
|
|
7101
|
-
const result = await
|
|
7102
|
-
cwd,
|
|
7533
|
+
const result = await runIsolatedGit(runtime, protectedGitArguments(input2.args, command2), cwd, {
|
|
7103
7534
|
timeoutMs: input2.timeout_ms ?? 12e4,
|
|
7104
7535
|
maxOutputBytes: 2e6,
|
|
7105
7536
|
...input2.stdin !== void 0 ? { stdin: input2.stdin } : {},
|
|
7106
|
-
...context.signal ? { signal: context.signal } : {}
|
|
7107
|
-
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
7108
|
-
unsetEnv: unsafeInheritedGitEnvironment,
|
|
7109
|
-
unsetEnvPrefixes: ["GIT_"]
|
|
7537
|
+
...context.signal ? { signal: context.signal } : {}
|
|
7110
7538
|
});
|
|
7111
7539
|
const changedFiles = before ? await collectGitChanges(before, await captureGitState(runtime, cwd), runtime, cwd, context) : [];
|
|
7112
7540
|
return {
|
|
@@ -7184,18 +7612,6 @@ function parsePorcelainStatus(output2) {
|
|
|
7184
7612
|
}
|
|
7185
7613
|
return status;
|
|
7186
7614
|
}
|
|
7187
|
-
function runIsolatedGit(runtime, args, cwd, options = {}) {
|
|
7188
|
-
return runProcess(runtime.executable, args, {
|
|
7189
|
-
cwd,
|
|
7190
|
-
timeoutMs: options.timeoutMs ?? 15e3,
|
|
7191
|
-
maxOutputBytes: options.maxOutputBytes ?? 2e6,
|
|
7192
|
-
env: { ...isolatedGitEnvironment, PATH: runtime.path },
|
|
7193
|
-
unsetEnv: unsafeInheritedGitEnvironment,
|
|
7194
|
-
unsetEnvPrefixes: ["GIT_"],
|
|
7195
|
-
...options.stdin !== void 0 ? { stdin: options.stdin } : {},
|
|
7196
|
-
...options.signal ? { signal: options.signal } : {}
|
|
7197
|
-
});
|
|
7198
|
-
}
|
|
7199
7615
|
function validateGitArguments(args) {
|
|
7200
7616
|
for (const argument of args) {
|
|
7201
7617
|
if (argument.includes("\0") || argument.includes("\n") || argument.includes("\r")) {
|
|
@@ -7331,7 +7747,7 @@ function positionalArguments(args, command2) {
|
|
|
7331
7747
|
|
|
7332
7748
|
// src/tools/list.ts
|
|
7333
7749
|
import { lstat as lstat14 } from "node:fs/promises";
|
|
7334
|
-
import { relative as relative7, resolve as
|
|
7750
|
+
import { relative as relative7, resolve as resolve15 } from "node:path";
|
|
7335
7751
|
import fg3 from "fast-glob";
|
|
7336
7752
|
import { z as z9 } from "zod";
|
|
7337
7753
|
var inputSchema4 = z9.object({
|
|
@@ -7389,7 +7805,7 @@ var listFilesTool = {
|
|
|
7389
7805
|
for (const path of selected) {
|
|
7390
7806
|
let safePath;
|
|
7391
7807
|
try {
|
|
7392
|
-
safePath = await context.workspace.resolvePath(
|
|
7808
|
+
safePath = await context.workspace.resolvePath(resolve15(directory, path), { expect: "any" });
|
|
7393
7809
|
} catch {
|
|
7394
7810
|
continue;
|
|
7395
7811
|
}
|
|
@@ -7599,7 +8015,7 @@ function assertToolName(name) {
|
|
|
7599
8015
|
|
|
7600
8016
|
// src/tools/search.ts
|
|
7601
8017
|
import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
7602
|
-
import { resolve as
|
|
8018
|
+
import { resolve as resolve16 } from "node:path";
|
|
7603
8019
|
import fg4 from "fast-glob";
|
|
7604
8020
|
import { z as z12 } from "zod";
|
|
7605
8021
|
var inputSchema7 = z12.object({
|
|
@@ -7669,7 +8085,7 @@ var searchCodeTool = {
|
|
|
7669
8085
|
if (results.length >= maxResults) break;
|
|
7670
8086
|
let path;
|
|
7671
8087
|
try {
|
|
7672
|
-
path = await context.workspace.resolvePath(
|
|
8088
|
+
path = await context.workspace.resolvePath(resolve16(directory, file), { expect: "file" });
|
|
7673
8089
|
} catch {
|
|
7674
8090
|
skipped += 1;
|
|
7675
8091
|
continue;
|
|
@@ -7751,7 +8167,7 @@ ${hit.content}`
|
|
|
7751
8167
|
}
|
|
7752
8168
|
|
|
7753
8169
|
// src/tools/shell.ts
|
|
7754
|
-
import { createHash as
|
|
8170
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
7755
8171
|
import { lstat as lstat15, readFile as readFile11, readdir as readdir5 } from "node:fs/promises";
|
|
7756
8172
|
import { join as join13 } from "node:path";
|
|
7757
8173
|
import { z as z13 } from "zod";
|
|
@@ -7965,7 +8381,7 @@ async function captureWorkspaceSnapshot(roots) {
|
|
|
7965
8381
|
files.set(path, {
|
|
7966
8382
|
size: info.size,
|
|
7967
8383
|
mtimeMs: info.mtimeMs,
|
|
7968
|
-
hash:
|
|
8384
|
+
hash: createHash10("sha256").update(content).digest("hex")
|
|
7969
8385
|
});
|
|
7970
8386
|
} catch (error) {
|
|
7971
8387
|
if (error.code !== "ENOENT") complete = false;
|
|
@@ -8168,15 +8584,15 @@ function compact(value, limit) {
|
|
|
8168
8584
|
}
|
|
8169
8585
|
|
|
8170
8586
|
// src/agent/completion-gate.ts
|
|
8171
|
-
import { createHash as
|
|
8587
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
8172
8588
|
|
|
8173
8589
|
// src/tools/permissions.ts
|
|
8174
|
-
import { createHash as
|
|
8590
|
+
import { createHash as createHash11, createHmac, randomBytes } from "node:crypto";
|
|
8175
8591
|
var permissionScopeSecret = randomBytes(32);
|
|
8176
8592
|
function permissionKey(call, category) {
|
|
8177
8593
|
const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
|
|
8178
|
-
const
|
|
8179
|
-
return `${category}:${call.name}:${
|
|
8594
|
+
const digest2 = createHash11("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
|
|
8595
|
+
return `${category}:${call.name}:${digest2}`;
|
|
8180
8596
|
}
|
|
8181
8597
|
function permissionTarget(call) {
|
|
8182
8598
|
const command2 = commandForCall(call);
|
|
@@ -8369,7 +8785,7 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
|
|
|
8369
8785
|
kind,
|
|
8370
8786
|
ok: result.ok,
|
|
8371
8787
|
changeSequence,
|
|
8372
|
-
commandKey:
|
|
8788
|
+
commandKey: createHash12("sha256").update(normalized).digest("hex")
|
|
8373
8789
|
};
|
|
8374
8790
|
}
|
|
8375
8791
|
function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = [], duplication) {
|
|
@@ -8553,7 +8969,7 @@ function verificationRequirementMet(requirement, checks) {
|
|
|
8553
8969
|
const normalized = normalizeCommand2(requirement);
|
|
8554
8970
|
const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
|
|
8555
8971
|
if (broad) return checks.length > 0;
|
|
8556
|
-
const commandKey =
|
|
8972
|
+
const commandKey = createHash12("sha256").update(normalized).digest("hex");
|
|
8557
8973
|
return checks.some((item) => item.commandKey === commandKey);
|
|
8558
8974
|
}
|
|
8559
8975
|
function acceptanceUnresolved(acceptance) {
|
|
@@ -9147,7 +9563,7 @@ function escapeAttribute3(value) {
|
|
|
9147
9563
|
}
|
|
9148
9564
|
|
|
9149
9565
|
// src/agent/tool-recovery.ts
|
|
9150
|
-
import { createHash as
|
|
9566
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
9151
9567
|
var RETRY_BUDGET = {
|
|
9152
9568
|
schema_input: 3,
|
|
9153
9569
|
unknown_tool: 2,
|
|
@@ -9219,7 +9635,7 @@ var ToolRecoveryController = class {
|
|
|
9219
9635
|
recordEvidence(call, result) {
|
|
9220
9636
|
if (call.name !== "search_code" || !result.ok) return void 0;
|
|
9221
9637
|
const callKey = callSignature(call);
|
|
9222
|
-
const fingerprint =
|
|
9638
|
+
const fingerprint = createHash13("sha256").update(`${result.content}\0${stableJson(result.metadata ?? {})}`).digest("hex");
|
|
9223
9639
|
const count = typeof result.metadata?.count === "number" ? result.metadata.count : void 0;
|
|
9224
9640
|
const current = this.evidence.get(callKey);
|
|
9225
9641
|
const repeated = current?.fingerprint === fingerprint;
|
|
@@ -9229,7 +9645,7 @@ var ToolRecoveryController = class {
|
|
|
9229
9645
|
status: count === 0 ? "empty" : repeated ? "repeated" : "new",
|
|
9230
9646
|
repeatCount: repeats,
|
|
9231
9647
|
stop: repeats >= 2,
|
|
9232
|
-
signature:
|
|
9648
|
+
signature: createHash13("sha256").update(`${callKey}\0${fingerprint}`).digest("hex")
|
|
9233
9649
|
};
|
|
9234
9650
|
}
|
|
9235
9651
|
receipt(call, failureClass, attempt, circuitOpen) {
|
|
@@ -9265,10 +9681,10 @@ function isRetryable(failureClass) {
|
|
|
9265
9681
|
return RETRY_BUDGET[failureClass] > 0;
|
|
9266
9682
|
}
|
|
9267
9683
|
function callSignature(call) {
|
|
9268
|
-
return
|
|
9684
|
+
return createHash13("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
|
|
9269
9685
|
}
|
|
9270
9686
|
function failureSignature(call, failureClass) {
|
|
9271
|
-
return
|
|
9687
|
+
return createHash13("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
|
|
9272
9688
|
}
|
|
9273
9689
|
function redact(value, key = "") {
|
|
9274
9690
|
if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
|
|
@@ -9616,7 +10032,7 @@ function escapeAttribute5(value) {
|
|
|
9616
10032
|
}
|
|
9617
10033
|
|
|
9618
10034
|
// src/agent/reuse-gate.ts
|
|
9619
|
-
import { createHash as
|
|
10035
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
9620
10036
|
import { readFile as readFile14, stat as stat10 } from "node:fs/promises";
|
|
9621
10037
|
import { basename as basename8, extname as extname3 } from "node:path";
|
|
9622
10038
|
var MAX_CANDIDATES = 5;
|
|
@@ -9719,7 +10135,7 @@ async function evaluateReuseGate(input2) {
|
|
|
9719
10135
|
candidates.push({
|
|
9720
10136
|
path: hit.path,
|
|
9721
10137
|
...hit.symbol ? { symbol: hit.symbol.slice(0, 160) } : {},
|
|
9722
|
-
score:
|
|
10138
|
+
score: roundScore2(hit.score),
|
|
9723
10139
|
read
|
|
9724
10140
|
});
|
|
9725
10141
|
}
|
|
@@ -9825,14 +10241,14 @@ function unresolvedReceipt(input2, queryHash, targetPaths, trigger, detail) {
|
|
|
9825
10241
|
};
|
|
9826
10242
|
}
|
|
9827
10243
|
function hash3(value) {
|
|
9828
|
-
return
|
|
10244
|
+
return createHash14("sha256").update(value).digest("hex");
|
|
9829
10245
|
}
|
|
9830
|
-
function
|
|
10246
|
+
function roundScore2(value) {
|
|
9831
10247
|
return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : 0;
|
|
9832
10248
|
}
|
|
9833
10249
|
|
|
9834
10250
|
// src/agent/duplication-audit.ts
|
|
9835
|
-
import { createHash as
|
|
10251
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
9836
10252
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
9837
10253
|
var DEFAULT_NEAR_CLONE_THRESHOLD = 0.55;
|
|
9838
10254
|
var MAX_MATCHES = 8;
|
|
@@ -10007,7 +10423,7 @@ function round(value) {
|
|
|
10007
10423
|
return Math.round(value * 1e3) / 1e3;
|
|
10008
10424
|
}
|
|
10009
10425
|
function matchId(generation, changeSequence, changed, candidate, similarity) {
|
|
10010
|
-
return
|
|
10426
|
+
return createHash15("sha256").update([
|
|
10011
10427
|
generation,
|
|
10012
10428
|
String(changeSequence),
|
|
10013
10429
|
changed.path,
|
|
@@ -11425,9 +11841,9 @@ function numeric(...values) {
|
|
|
11425
11841
|
}
|
|
11426
11842
|
|
|
11427
11843
|
// src/agent/team-store.ts
|
|
11428
|
-
import { createHash as
|
|
11844
|
+
import { createHash as createHash16, randomUUID as randomUUID13 } from "node:crypto";
|
|
11429
11845
|
import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
11430
|
-
import { join as join16, resolve as
|
|
11846
|
+
import { join as join16, resolve as resolve17 } from "node:path";
|
|
11431
11847
|
import { z as z18 } from "zod";
|
|
11432
11848
|
var runIdSchema = z18.string().uuid();
|
|
11433
11849
|
var hashSchema = z18.string().regex(/^[a-f0-9]{64}$/u);
|
|
@@ -11511,9 +11927,9 @@ var TeamRunStore = class {
|
|
|
11511
11927
|
managedDirectory;
|
|
11512
11928
|
writes = Promise.resolve();
|
|
11513
11929
|
constructor(workspace, directory) {
|
|
11514
|
-
this.workspace =
|
|
11930
|
+
this.workspace = resolve17(workspace);
|
|
11515
11931
|
this.managedDirectory = directory === void 0;
|
|
11516
|
-
this.directory = directory ?
|
|
11932
|
+
this.directory = directory ? resolve17(directory) : join16(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
|
|
11517
11933
|
}
|
|
11518
11934
|
async create(input2) {
|
|
11519
11935
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -11600,7 +12016,7 @@ var TeamRunStore = class {
|
|
|
11600
12016
|
const path = join16(this.runDirectory(runId), "manifest.json");
|
|
11601
12017
|
await this.assertRegularFile(path);
|
|
11602
12018
|
const manifest = manifestSchema2.parse(JSON.parse(await readFile17(path, "utf8")));
|
|
11603
|
-
if (manifest.id !== runId ||
|
|
12019
|
+
if (manifest.id !== runId || resolve17(manifest.workspace) !== this.workspace) {
|
|
11604
12020
|
throw new Error("Team run manifest identity does not match its location.");
|
|
11605
12021
|
}
|
|
11606
12022
|
if (verify) {
|
|
@@ -11683,7 +12099,7 @@ var TeamRunStore = class {
|
|
|
11683
12099
|
async writeArtifact(runId, content, truncate = true) {
|
|
11684
12100
|
const data = boundedArtifactText(content, 5e5, truncate);
|
|
11685
12101
|
const bytes = Buffer.byteLength(data);
|
|
11686
|
-
const sha256 =
|
|
12102
|
+
const sha256 = createHash16("sha256").update(data).digest("hex");
|
|
11687
12103
|
const directory = join16(this.runDirectory(runId), "blobs");
|
|
11688
12104
|
await ensureWorkspaceStorageDirectory(this.workspace, directory, {
|
|
11689
12105
|
requireActiveNamespace: this.managedDirectory
|
|
@@ -11692,7 +12108,7 @@ var TeamRunStore = class {
|
|
|
11692
12108
|
try {
|
|
11693
12109
|
await this.assertRegularFile(path);
|
|
11694
12110
|
const existing = await readFile17(path);
|
|
11695
|
-
if (
|
|
12111
|
+
if (createHash16("sha256").update(existing).digest("hex") !== sha256) {
|
|
11696
12112
|
throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
|
|
11697
12113
|
}
|
|
11698
12114
|
} catch (error) {
|
|
@@ -11713,7 +12129,7 @@ var TeamRunStore = class {
|
|
|
11713
12129
|
const path = this.artifactPath(runId, artifact.sha256);
|
|
11714
12130
|
await this.assertRegularFile(path);
|
|
11715
12131
|
const data = await readFile17(path);
|
|
11716
|
-
const hash4 =
|
|
12132
|
+
const hash4 = createHash16("sha256").update(data).digest("hex");
|
|
11717
12133
|
if (hash4 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
|
|
11718
12134
|
throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
|
|
11719
12135
|
}
|
|
@@ -11731,7 +12147,7 @@ var TeamRunStore = class {
|
|
|
11731
12147
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
|
|
11732
12148
|
}
|
|
11733
12149
|
async assertRegularFile(path) {
|
|
11734
|
-
await assertNoSymlinkPath(this.workspace,
|
|
12150
|
+
await assertNoSymlinkPath(this.workspace, resolve17(path, ".."));
|
|
11735
12151
|
const info = await lstat18(path);
|
|
11736
12152
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Team run file is not a regular file: ${path}`);
|
|
11737
12153
|
}
|
|
@@ -11776,10 +12192,10 @@ function resolveAgentModelRoute(team, parent, profile) {
|
|
|
11776
12192
|
}
|
|
11777
12193
|
|
|
11778
12194
|
// src/agent/writer-lane.ts
|
|
11779
|
-
import { createHash as
|
|
12195
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
11780
12196
|
import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
|
|
11781
12197
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
11782
|
-
import { isAbsolute as isAbsolute6, join as join17, resolve as
|
|
12198
|
+
import { isAbsolute as isAbsolute6, join as join17, resolve as resolve18 } from "node:path";
|
|
11783
12199
|
var WriterLaneApplyError = class extends Error {
|
|
11784
12200
|
constructor(message2, attempted, cause) {
|
|
11785
12201
|
super(message2, cause === void 0 ? void 0 : { cause });
|
|
@@ -11792,8 +12208,8 @@ var WriterLane = class {
|
|
|
11792
12208
|
workspace;
|
|
11793
12209
|
constructor(workspace, workspaceRoots) {
|
|
11794
12210
|
this.workspace = new WorkspaceAccess([
|
|
11795
|
-
|
|
11796
|
-
...workspaceRoots.map((root) =>
|
|
12211
|
+
resolve18(workspace),
|
|
12212
|
+
...workspaceRoots.map((root) => resolve18(root))
|
|
11797
12213
|
]);
|
|
11798
12214
|
}
|
|
11799
12215
|
async createDraft(maxPatchBytes, operation, signal) {
|
|
@@ -11874,7 +12290,7 @@ var WriterLane = class {
|
|
|
11874
12290
|
return {
|
|
11875
12291
|
baseCommit,
|
|
11876
12292
|
patch,
|
|
11877
|
-
patchSha256:
|
|
12293
|
+
patchSha256: createHash17("sha256").update(patch).digest("hex"),
|
|
11878
12294
|
files,
|
|
11879
12295
|
worktreeCleaned,
|
|
11880
12296
|
value
|
|
@@ -12016,7 +12432,7 @@ var WriterLane = class {
|
|
|
12016
12432
|
if (topLevel.exitCode !== 0 || !topLevel.stdout.trim()) {
|
|
12017
12433
|
throw new Error("The primary workspace must be a Git repository root for writer lanes.");
|
|
12018
12434
|
}
|
|
12019
|
-
const discoveredRoot =
|
|
12435
|
+
const discoveredRoot = resolve18(topLevel.stdout.trim());
|
|
12020
12436
|
if (await realpath7(discoveredRoot) !== await realpath7(root)) {
|
|
12021
12437
|
throw new Error("The primary workspace must be the Git repository root for writer lanes.");
|
|
12022
12438
|
}
|
|
@@ -12025,7 +12441,7 @@ var WriterLane = class {
|
|
|
12025
12441
|
if (common.exitCode !== 0 || !common.stdout.trim()) {
|
|
12026
12442
|
throw new Error(`Unable to resolve the Git common directory: ${processDetail(common)}`);
|
|
12027
12443
|
}
|
|
12028
|
-
const commonDirectory = await realpath7(isAbsolute6(common.stdout.trim()) ? common.stdout.trim() :
|
|
12444
|
+
const commonDirectory = await realpath7(isAbsolute6(common.stdout.trim()) ? common.stdout.trim() : resolve18(repositoryRoot, common.stdout.trim()));
|
|
12029
12445
|
return { root: repositoryRoot, commonDirectory, runtime };
|
|
12030
12446
|
}
|
|
12031
12447
|
async head(repository) {
|
|
@@ -12037,7 +12453,7 @@ var WriterLane = class {
|
|
|
12037
12453
|
return value;
|
|
12038
12454
|
}
|
|
12039
12455
|
async cleanupWorktree(repository, worktree, added) {
|
|
12040
|
-
const physicalWorktree = await realpath7(worktree).catch(() =>
|
|
12456
|
+
const physicalWorktree = await realpath7(worktree).catch(() => resolve18(worktree));
|
|
12041
12457
|
if (added) {
|
|
12042
12458
|
await runIsolatedGit(repository.runtime, [
|
|
12043
12459
|
"worktree",
|
|
@@ -13963,7 +14379,7 @@ function formatResultDetail(result, paint = chalk2, glyphs = resolveCliGlyphs())
|
|
|
13963
14379
|
}
|
|
13964
14380
|
|
|
13965
14381
|
// src/cli/namespace-leases.ts
|
|
13966
|
-
import { resolve as
|
|
14382
|
+
import { resolve as resolve19 } from "node:path";
|
|
13967
14383
|
function cliNamespaceLeaseScopes(actionCommand) {
|
|
13968
14384
|
const names = commandNames(actionCommand);
|
|
13969
14385
|
const topLevel = names[1];
|
|
@@ -13985,7 +14401,7 @@ async function acquireCliNamespaceLeases(actionCommand) {
|
|
|
13985
14401
|
const scopes = cliNamespaceLeaseScopes(actionCommand);
|
|
13986
14402
|
const localOptions = actionCommand.opts();
|
|
13987
14403
|
const globalOptions = actionCommand.optsWithGlobals();
|
|
13988
|
-
const workspace =
|
|
14404
|
+
const workspace = resolve19(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
|
|
13989
14405
|
const targets = scopes.map((scope) => scope === "project" ? projectNamespacePaths(workspace).canonical : homeNamespacePaths().canonical);
|
|
13990
14406
|
const leases = [];
|
|
13991
14407
|
try {
|
|
@@ -14237,7 +14653,7 @@ function graphemes(value) {
|
|
|
14237
14653
|
|
|
14238
14654
|
// src/ui/theme.ts
|
|
14239
14655
|
import { lstat as lstat20, readdir as readdir8, readFile as readFile18 } from "node:fs/promises";
|
|
14240
|
-
import { basename as basename10, join as join19, resolve as
|
|
14656
|
+
import { basename as basename10, join as join19, resolve as resolve20 } from "node:path";
|
|
14241
14657
|
import React, { createContext, useContext } from "react";
|
|
14242
14658
|
function defineTheme(seed) {
|
|
14243
14659
|
return {
|
|
@@ -14359,7 +14775,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
|
|
|
14359
14775
|
userThemeNames.clear();
|
|
14360
14776
|
const loaded = [];
|
|
14361
14777
|
const errors = [];
|
|
14362
|
-
const resolvedDirectory =
|
|
14778
|
+
const resolvedDirectory = resolve20(directory);
|
|
14363
14779
|
let entries;
|
|
14364
14780
|
try {
|
|
14365
14781
|
entries = await readdir8(resolvedDirectory, { encoding: "utf8" });
|
|
@@ -16823,7 +17239,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
16823
17239
|
return () => clearInterval(timer);
|
|
16824
17240
|
}, [busy]);
|
|
16825
17241
|
const requestPermission = useCallback((call, category) => {
|
|
16826
|
-
return new Promise((
|
|
17242
|
+
return new Promise((resolve25) => setPermission({ call, category, resolve: resolve25 }));
|
|
16827
17243
|
}, []);
|
|
16828
17244
|
const onEvent = useCallback((event) => {
|
|
16829
17245
|
switch (event.type) {
|
|
@@ -17659,8 +18075,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17659
18075
|
}, [append]);
|
|
17660
18076
|
function settlePermission(grant, stop = false) {
|
|
17661
18077
|
if (!permission) return;
|
|
17662
|
-
const { call, category, resolve:
|
|
17663
|
-
|
|
18078
|
+
const { call, category, resolve: resolve25 } = permission;
|
|
18079
|
+
resolve25(grant);
|
|
17664
18080
|
setPermission(void 0);
|
|
17665
18081
|
if (grant === "session") {
|
|
17666
18082
|
append({
|
|
@@ -18995,7 +19411,7 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
|
|
|
18995
19411
|
}
|
|
18996
19412
|
|
|
18997
19413
|
// src/runtime/extensions.ts
|
|
18998
|
-
import { resolve as
|
|
19414
|
+
import { resolve as resolve23 } from "node:path";
|
|
18999
19415
|
|
|
19000
19416
|
// src/mcp/manager.ts
|
|
19001
19417
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -19004,7 +19420,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
|
|
|
19004
19420
|
import stripAnsi5 from "strip-ansi";
|
|
19005
19421
|
|
|
19006
19422
|
// src/mcp/tool.ts
|
|
19007
|
-
import { createHash as
|
|
19423
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
19008
19424
|
import stripAnsi4 from "strip-ansi";
|
|
19009
19425
|
var MAX_ARGUMENT_BYTES = 256e3;
|
|
19010
19426
|
var MAX_DESCRIPTION_LENGTH = 4e3;
|
|
@@ -19175,7 +19591,7 @@ function fitToolName(name, identity2) {
|
|
|
19175
19591
|
return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
|
|
19176
19592
|
}
|
|
19177
19593
|
function shortHash(value) {
|
|
19178
|
-
return
|
|
19594
|
+
return createHash18("sha256").update(value).digest("hex").slice(0, 8);
|
|
19179
19595
|
}
|
|
19180
19596
|
function sanitizeOutputText(value) {
|
|
19181
19597
|
return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
|
|
@@ -19208,7 +19624,7 @@ function isRecord2(value) {
|
|
|
19208
19624
|
|
|
19209
19625
|
// src/mcp/validation.ts
|
|
19210
19626
|
import { stat as stat11, realpath as realpath8 } from "node:fs/promises";
|
|
19211
|
-
import { isAbsolute as isAbsolute7, resolve as
|
|
19627
|
+
import { isAbsolute as isAbsolute7, resolve as resolve21 } from "node:path";
|
|
19212
19628
|
var SERVER_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
19213
19629
|
var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
19214
19630
|
var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
@@ -19280,9 +19696,9 @@ async function validateCwd(configured, options) {
|
|
|
19280
19696
|
if (configured !== void 0 && (configured.length > 4e3 || CONTROL_CHARACTERS.test(configured))) {
|
|
19281
19697
|
throw new Error("MCP working directory is invalid or too long");
|
|
19282
19698
|
}
|
|
19283
|
-
const defaultCwd =
|
|
19284
|
-
const candidate = configured ? isAbsolute7(configured) ?
|
|
19285
|
-
const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) =>
|
|
19699
|
+
const defaultCwd = resolve21(options.cwd ?? process.cwd());
|
|
19700
|
+
const candidate = configured ? isAbsolute7(configured) ? resolve21(configured) : resolve21(defaultCwd, configured) : defaultCwd;
|
|
19701
|
+
const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) => resolve21(root)) : [defaultCwd];
|
|
19286
19702
|
const resolvedCandidate = await realpath8(candidate).catch(() => {
|
|
19287
19703
|
throw new Error(`MCP working directory does not exist: ${candidate}`);
|
|
19288
19704
|
});
|
|
@@ -19936,7 +20352,7 @@ function scopeKey(scope, context) {
|
|
|
19936
20352
|
// src/skills/catalog.ts
|
|
19937
20353
|
import { lstat as lstat21, readFile as readFile20, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
|
|
19938
20354
|
import { homedir as homedir4 } from "node:os";
|
|
19939
|
-
import { basename as basename12, join as join21, resolve as
|
|
20355
|
+
import { basename as basename12, join as join21, resolve as resolve22 } from "node:path";
|
|
19940
20356
|
import { parse as parseYaml3 } from "yaml";
|
|
19941
20357
|
var SkillCatalog = class {
|
|
19942
20358
|
constructor(workspace, config) {
|
|
@@ -20005,14 +20421,14 @@ ${skill.content}
|
|
|
20005
20421
|
}
|
|
20006
20422
|
function discoveryLocations(workspace, configured) {
|
|
20007
20423
|
const home = homedir4();
|
|
20008
|
-
const workspaceRoot =
|
|
20424
|
+
const workspaceRoot = resolve22(workspace);
|
|
20009
20425
|
return [
|
|
20010
20426
|
{ path: join21(home, ".agents", "skills"), scope: "user", trusted: true },
|
|
20011
20427
|
{ path: join21(home, ".claude", "skills"), scope: "user", trusted: true },
|
|
20012
20428
|
{ path: join21(home, ".augment", "skills"), scope: "user", trusted: true },
|
|
20013
20429
|
{ path: join21(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
|
|
20014
20430
|
...configured.map((path) => {
|
|
20015
|
-
const resolved =
|
|
20431
|
+
const resolved = resolve22(workspaceRoot, path);
|
|
20016
20432
|
return {
|
|
20017
20433
|
path: resolved,
|
|
20018
20434
|
scope: "configured",
|
|
@@ -20041,7 +20457,7 @@ async function readMetadata(path) {
|
|
|
20041
20457
|
const { frontmatter } = splitFrontmatter(raw);
|
|
20042
20458
|
if (!frontmatter) return void 0;
|
|
20043
20459
|
const parsed = parseYaml3(frontmatter);
|
|
20044
|
-
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename12(
|
|
20460
|
+
const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename12(resolve22(path, ".."));
|
|
20045
20461
|
const description = typeof parsed?.description === "string" ? parsed.description.trim() : "";
|
|
20046
20462
|
if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || !description || description.length > 1e3) {
|
|
20047
20463
|
return void 0;
|
|
@@ -20062,7 +20478,7 @@ async function safeRead(path, maxBytes) {
|
|
|
20062
20478
|
try {
|
|
20063
20479
|
const info = await lstat21(path);
|
|
20064
20480
|
if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes) return void 0;
|
|
20065
|
-
const parent =
|
|
20481
|
+
const parent = resolve22(path, "..");
|
|
20066
20482
|
const resolvedParent = await realpath9(parent);
|
|
20067
20483
|
const resolvedPath = await realpath9(path);
|
|
20068
20484
|
if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
|
|
@@ -20247,7 +20663,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
|
|
|
20247
20663
|
delegation;
|
|
20248
20664
|
initialized = false;
|
|
20249
20665
|
static async create(config, registry, options = {}) {
|
|
20250
|
-
const runtime = new _ExtensionRuntime(config,
|
|
20666
|
+
const runtime = new _ExtensionRuntime(config, resolve23(config.workspaceRoots[0] ?? process.cwd()), options);
|
|
20251
20667
|
try {
|
|
20252
20668
|
await runtime.initialize(registry, options.signal);
|
|
20253
20669
|
return runtime;
|
|
@@ -20452,15 +20868,15 @@ function upgradeCommandOverride(env = process.env) {
|
|
|
20452
20868
|
return trimmed ? trimmed : void 0;
|
|
20453
20869
|
}
|
|
20454
20870
|
function runUpgrade(plan) {
|
|
20455
|
-
return new Promise((
|
|
20871
|
+
return new Promise((resolve25) => {
|
|
20456
20872
|
const child = spawn2(plan.command, plan.args, {
|
|
20457
20873
|
stdio: "inherit",
|
|
20458
20874
|
shell: plan.shell ?? false
|
|
20459
20875
|
});
|
|
20460
|
-
child.on("error", () =>
|
|
20876
|
+
child.on("error", () => resolve25({ ok: false, exitCode: 127, display: plan.display }));
|
|
20461
20877
|
child.on("close", (code) => {
|
|
20462
20878
|
const exitCode = code ?? 1;
|
|
20463
|
-
|
|
20879
|
+
resolve25({ ok: exitCode === 0, exitCode, display: plan.display });
|
|
20464
20880
|
});
|
|
20465
20881
|
});
|
|
20466
20882
|
}
|
|
@@ -20722,7 +21138,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
|
|
|
20722
21138
|
const store = new SessionStore(workspaceOption(options.workspace));
|
|
20723
21139
|
const session = await requireSessionSelector(store, id);
|
|
20724
21140
|
const markdown = sessionMarkdown(session);
|
|
20725
|
-
if (options.output) await writeFile3(
|
|
21141
|
+
if (options.output) await writeFile3(resolve24(options.output), markdown, "utf8");
|
|
20726
21142
|
else process.stdout.write(markdown);
|
|
20727
21143
|
});
|
|
20728
21144
|
var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
|
|
@@ -21099,7 +21515,7 @@ async function runChat(prompts, options) {
|
|
|
21099
21515
|
const stdinPrompt = !process.stdin.isTTY ? await readStdin() : "";
|
|
21100
21516
|
const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
|
|
21101
21517
|
if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
|
|
21102
|
-
const workspace =
|
|
21518
|
+
const workspace = resolve24(options.workspace);
|
|
21103
21519
|
let config = await runtimeConfig(workspace, options);
|
|
21104
21520
|
let completedOnboarding = false;
|
|
21105
21521
|
if (!shouldPrint && needsFirstRunOnboarding(config)) {
|
|
@@ -21324,14 +21740,14 @@ async function runAgentSetup(options) {
|
|
|
21324
21740
|
`);
|
|
21325
21741
|
}
|
|
21326
21742
|
async function runtimeConfig(workspaceInput, options) {
|
|
21327
|
-
const workspace =
|
|
21743
|
+
const workspace = resolve24(workspaceInput);
|
|
21328
21744
|
const loaded = await loadConfig(workspace, options.config, {
|
|
21329
21745
|
trustProjectConfig: options.trustProjectConfig === true
|
|
21330
21746
|
});
|
|
21331
21747
|
const roots = [
|
|
21332
21748
|
workspace,
|
|
21333
21749
|
...loaded.workspaceRoots,
|
|
21334
|
-
...(options.addWorkspace ?? []).map((root) =>
|
|
21750
|
+
...(options.addWorkspace ?? []).map((root) => resolve24(workspace, root))
|
|
21335
21751
|
];
|
|
21336
21752
|
const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
|
|
21337
21753
|
return {
|
|
@@ -21487,7 +21903,7 @@ function collect(value, previous) {
|
|
|
21487
21903
|
}
|
|
21488
21904
|
function workspaceOption(value) {
|
|
21489
21905
|
const rootOptions = program.opts();
|
|
21490
|
-
return
|
|
21906
|
+
return resolve24(value ?? rootOptions.workspace ?? process.cwd());
|
|
21491
21907
|
}
|
|
21492
21908
|
function runtimeOptions(options) {
|
|
21493
21909
|
const root = program.opts();
|