@skein-code/cli 0.3.19 → 0.3.21

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/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 resolve23 } from "node:path";
7
+ import { basename as basename13, resolve as resolve25 } 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.19",
223
+ version: "0.3.21",
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,10 @@ var package_default = {
236
236
  },
237
237
  skein: {
238
238
  releaseNotes: [
239
+ "Failed configured verification output yields bounded current-run diagnostic ranking without persisting paths",
240
+ "Diagnostic hints cannot create zero-relevance hits and are cleared on success or the next agent run",
241
+ "Python absolute and relative module imports now contribute deterministic offline graph adjacency",
242
+ "Context provenance exposes the diagnostic score alongside graph and isolated Git recency signals",
239
243
  "Local index v3 records TypeScript AST definitions, calls, and relative import adjacency for graph-assisted retrieval",
240
244
  "Context hits expose generation, content hash, matched terms, and bm25/path/symbol/phrase/graph score provenance",
241
245
  "Context benchmark v2 locks multilingual recall, MRR, useful-token, stale-hit, incremental-index, and warm-latency gates",
@@ -256,7 +260,8 @@ var package_default = {
256
260
  "Prompt and timeline diagnostics expose the repository-first reuse ladder without blocking legitimate writes",
257
261
  "Known tool changes refresh only their affected local-index paths before the next model turn",
258
262
  "File ctime closes same-size and restored-mtime zero-hit freshness gaps without full-content scans",
259
- "Repeated empty or unchanged search calls stop through the existing recovery circuit"
263
+ "Repeated empty or unchanged search calls stop through the existing recovery circuit",
264
+ "Git recency is an isolated, bounded tie-break with exact HEAD-bound cache invalidation and non-Git degradation"
260
265
  ]
261
266
  },
262
267
  bin: {
@@ -2611,9 +2616,9 @@ function countExplicitPaths(value) {
2611
2616
  }
2612
2617
 
2613
2618
  // src/context/local-index.ts
2614
- import { createHash as createHash6 } from "node:crypto";
2615
- import { lstat as lstat8, readFile as readFile3, stat as stat3 } from "node:fs/promises";
2616
- import { basename as basename5, dirname as dirname7, extname as extname2, isAbsolute as isAbsolute3, join as join7, posix, relative as relative5, resolve as resolve8, sep as sep4 } from "node:path";
2619
+ import { createHash as createHash7 } from "node:crypto";
2620
+ import { lstat as lstat9, readFile as readFile3, stat as stat3 } from "node:fs/promises";
2621
+ 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";
2617
2622
  import fg from "fast-glob";
2618
2623
  import ts from "typescript";
2619
2624
  import { z as z3 } from "zod";
@@ -3046,6 +3051,371 @@ function hash(value) {
3046
3051
  return createHash5("sha256").update(value).digest("hex");
3047
3052
  }
3048
3053
 
3054
+ // src/context/git-recency.ts
3055
+ import { createHash as createHash6 } from "node:crypto";
3056
+ import { resolve as resolve9 } from "node:path";
3057
+
3058
+ // src/utils/process.ts
3059
+ import { spawn } from "node:child_process";
3060
+ import { constants as constants3 } from "node:fs";
3061
+ import { access as access2, lstat as lstat8, realpath as realpath6 } from "node:fs/promises";
3062
+ import { delimiter, isAbsolute as isAbsolute3, join as join7, resolve as resolve8 } from "node:path";
3063
+ import { StringDecoder } from "node:string_decoder";
3064
+ async function resolveExecutableRuntime(command2, cwd, excludedRoots = []) {
3065
+ const realRoots = await Promise.all(excludedRoots.map(async (root) => {
3066
+ try {
3067
+ return await realpath6(root);
3068
+ } catch {
3069
+ return resolve8(root);
3070
+ }
3071
+ }));
3072
+ const pathEntries = (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean);
3073
+ const safeDirectories2 = [];
3074
+ let executable2;
3075
+ const explicit = isAbsolute3(command2) || command2.includes("/") || command2.includes("\\");
3076
+ const explicitPath = explicit ? resolve8(cwd, command2) : void 0;
3077
+ for (const entry of pathEntries) {
3078
+ let directory;
3079
+ try {
3080
+ directory = await realpath6(resolve8(cwd, entry));
3081
+ if (!(await lstat8(directory)).isDirectory()) continue;
3082
+ } catch {
3083
+ continue;
3084
+ }
3085
+ if (realRoots.some((root) => isInside(root, directory))) continue;
3086
+ let contaminated = false;
3087
+ if (!explicit) {
3088
+ for (const name of executableNames(command2)) {
3089
+ const candidate = join7(directory, name);
3090
+ const resolvedCandidate = await usableExecutable(candidate);
3091
+ if (!resolvedCandidate) continue;
3092
+ if (realRoots.some((root) => isInside(root, resolvedCandidate))) {
3093
+ contaminated = true;
3094
+ continue;
3095
+ }
3096
+ executable2 ??= resolvedCandidate;
3097
+ }
3098
+ }
3099
+ if (!contaminated && !safeDirectories2.includes(directory)) safeDirectories2.push(directory);
3100
+ }
3101
+ if (explicitPath) executable2 = await usableExecutable(explicitPath);
3102
+ if (!executable2) return void 0;
3103
+ return { executable: executable2, path: safeDirectories2.join(delimiter) };
3104
+ }
3105
+ async function usableExecutable(candidate) {
3106
+ try {
3107
+ await access2(candidate, process.platform === "win32" ? constants3.F_OK : constants3.X_OK);
3108
+ const resolvedCandidate = await realpath6(candidate);
3109
+ return (await lstat8(resolvedCandidate)).isFile() ? resolvedCandidate : void 0;
3110
+ } catch {
3111
+ return void 0;
3112
+ }
3113
+ }
3114
+ function executableNames(command2) {
3115
+ if (process.platform !== "win32") return [command2];
3116
+ const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
3117
+ return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
3118
+ }
3119
+ function runProcess(command2, args, options) {
3120
+ return new Promise((resolve26, reject) => {
3121
+ const started = Date.now();
3122
+ const environment = options.inheritEnv === false ? {} : { ...process.env };
3123
+ for (const name of options.unsetEnv ?? []) delete environment[name];
3124
+ for (const name of Object.keys(environment)) {
3125
+ if (options.unsetEnvPrefixes?.some((prefix) => name.startsWith(prefix))) {
3126
+ delete environment[name];
3127
+ }
3128
+ }
3129
+ Object.assign(environment, options.env);
3130
+ const child = spawn(command2, args, {
3131
+ cwd: options.cwd,
3132
+ env: environment,
3133
+ stdio: ["pipe", "pipe", "pipe"],
3134
+ signal: options.signal
3135
+ });
3136
+ const maxBytes = options.maxOutputBytes ?? 1e6;
3137
+ let stdout = "";
3138
+ let stderr = "";
3139
+ let stdoutBytes = 0;
3140
+ let stderrBytes = 0;
3141
+ let stdoutObservedBytes = 0;
3142
+ let stderrObservedBytes = 0;
3143
+ let timedOut = false;
3144
+ let outputLimitReached = false;
3145
+ let callbackError;
3146
+ const stdoutDecoder = new StringDecoder("utf8");
3147
+ const stderrDecoder = new StringDecoder("utf8");
3148
+ const stdoutCallbackDecoder = new StringDecoder("utf8");
3149
+ const stderrCallbackDecoder = new StringDecoder("utf8");
3150
+ const append = (decoder, chunk, usedBytes) => {
3151
+ if (usedBytes >= maxBytes) return { text: "", usedBytes };
3152
+ const selected = chunk.subarray(0, maxBytes - usedBytes);
3153
+ return { text: decoder.write(selected), usedBytes: usedBytes + selected.length };
3154
+ };
3155
+ const notify = (callback, decoder, chunk) => {
3156
+ if (!callback || callbackError) return;
3157
+ try {
3158
+ const decoded = decoder.write(chunk);
3159
+ if (decoded) callback(decoded);
3160
+ } catch (error) {
3161
+ callbackError = error;
3162
+ child.kill("SIGTERM");
3163
+ }
3164
+ };
3165
+ child.stdout.on("data", (chunk) => {
3166
+ stdoutObservedBytes += chunk.length;
3167
+ if (options.stopOnOutputLimit && stdoutObservedBytes > maxBytes && !outputLimitReached) {
3168
+ outputLimitReached = true;
3169
+ child.kill("SIGTERM");
3170
+ }
3171
+ const appended = append(stdoutDecoder, chunk, stdoutBytes);
3172
+ stdout += appended.text;
3173
+ stdoutBytes = appended.usedBytes;
3174
+ notify(options.onStdout, stdoutCallbackDecoder, chunk);
3175
+ });
3176
+ child.stderr.on("data", (chunk) => {
3177
+ stderrObservedBytes += chunk.length;
3178
+ if (options.stopOnOutputLimit && stderrObservedBytes > maxBytes && !outputLimitReached) {
3179
+ outputLimitReached = true;
3180
+ child.kill("SIGTERM");
3181
+ }
3182
+ const appended = append(stderrDecoder, chunk, stderrBytes);
3183
+ stderr += appended.text;
3184
+ stderrBytes = appended.usedBytes;
3185
+ notify(options.onStderr, stderrCallbackDecoder, chunk);
3186
+ });
3187
+ child.on("error", reject);
3188
+ const timeoutMs = options.timeoutMs ?? 12e4;
3189
+ const timeout = timeoutMs > 0 ? setTimeout(() => {
3190
+ timedOut = true;
3191
+ child.kill("SIGTERM");
3192
+ setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
3193
+ }, timeoutMs) : void 0;
3194
+ child.on("close", (code) => {
3195
+ if (timeout) clearTimeout(timeout);
3196
+ const stdoutTail = stdoutDecoder.end();
3197
+ const stderrTail = stderrDecoder.end();
3198
+ if (Buffer.byteLength(stdout) + Buffer.byteLength(stdoutTail) <= maxBytes) stdout += stdoutTail;
3199
+ if (Buffer.byteLength(stderr) + Buffer.byteLength(stderrTail) <= maxBytes) stderr += stderrTail;
3200
+ try {
3201
+ const stdoutCallbackTail = stdoutCallbackDecoder.end();
3202
+ const stderrCallbackTail = stderrCallbackDecoder.end();
3203
+ if (stdoutCallbackTail && options.onStdout && !callbackError) options.onStdout(stdoutCallbackTail);
3204
+ if (stderrCallbackTail && options.onStderr && !callbackError) options.onStderr(stderrCallbackTail);
3205
+ } catch (error) {
3206
+ callbackError = error;
3207
+ }
3208
+ if (callbackError) {
3209
+ reject(callbackError);
3210
+ return;
3211
+ }
3212
+ resolve26({
3213
+ command: [command2, ...args].join(" "),
3214
+ exitCode: code ?? (timedOut ? 124 : 1),
3215
+ stdout,
3216
+ stderr,
3217
+ timedOut,
3218
+ durationMs: Date.now() - started,
3219
+ stdoutBytes: stdoutObservedBytes,
3220
+ stderrBytes: stderrObservedBytes,
3221
+ stdoutTruncated: stdoutObservedBytes > stdoutBytes,
3222
+ stderrTruncated: stderrObservedBytes > stderrBytes
3223
+ });
3224
+ });
3225
+ if (options.stdin) child.stdin.end(options.stdin);
3226
+ else child.stdin.end();
3227
+ });
3228
+ }
3229
+ function runShell(command2, cwd, options = {}) {
3230
+ const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/sh";
3231
+ const args = process.platform === "win32" ? ["/d", "/s", "/c", command2] : ["-lc", command2];
3232
+ return runProcess(shell, args, { ...options, cwd });
3233
+ }
3234
+
3235
+ // src/utils/git.ts
3236
+ var unsafeInheritedGitEnvironment = [
3237
+ "GIT_DIR",
3238
+ "GIT_WORK_TREE",
3239
+ "GIT_COMMON_DIR",
3240
+ "GIT_INDEX_FILE",
3241
+ "GIT_OBJECT_DIRECTORY",
3242
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
3243
+ "GIT_NAMESPACE",
3244
+ "GIT_CEILING_DIRECTORIES",
3245
+ "GIT_DISCOVERY_ACROSS_FILESYSTEM",
3246
+ "GIT_EXTERNAL_DIFF",
3247
+ "GIT_DIFF_OPTS",
3248
+ "GIT_SSH",
3249
+ "GIT_SSH_COMMAND",
3250
+ "GIT_PROXY_COMMAND",
3251
+ "GIT_ASKPASS",
3252
+ "SSH_ASKPASS",
3253
+ "GIT_EXEC_PATH",
3254
+ "GIT_TEMPLATE_DIR",
3255
+ "GIT_CONFIG",
3256
+ "GIT_CONFIG_PARAMETERS",
3257
+ "GIT_QUARANTINE_PATH",
3258
+ "Path"
3259
+ ];
3260
+ var isolatedGitEnvironment = {
3261
+ GIT_TERMINAL_PROMPT: "0",
3262
+ GIT_CONFIG_NOSYSTEM: "1",
3263
+ GIT_CONFIG_GLOBAL: "/dev/null",
3264
+ GIT_CONFIG_SYSTEM: "/dev/null",
3265
+ GIT_NO_REPLACE_OBJECTS: "1",
3266
+ GIT_PAGER: "cat",
3267
+ GIT_EDITOR: "true",
3268
+ GIT_SEQUENCE_EDITOR: "true",
3269
+ GIT_CONFIG_COUNT: "4",
3270
+ GIT_CONFIG_KEY_0: "core.hooksPath",
3271
+ GIT_CONFIG_VALUE_0: "/dev/null",
3272
+ GIT_CONFIG_KEY_1: "core.fsmonitor",
3273
+ GIT_CONFIG_VALUE_1: "false",
3274
+ GIT_CONFIG_KEY_2: "credential.helper",
3275
+ GIT_CONFIG_VALUE_2: "",
3276
+ GIT_CONFIG_KEY_3: "protocol.ext.allow",
3277
+ GIT_CONFIG_VALUE_3: "never"
3278
+ };
3279
+ function runIsolatedGit(runtime, args, cwd, options = {}) {
3280
+ return runProcess(runtime.executable, args, {
3281
+ cwd,
3282
+ timeoutMs: options.timeoutMs ?? 15e3,
3283
+ maxOutputBytes: options.maxOutputBytes ?? 2e6,
3284
+ ...options.stopOnOutputLimit !== void 0 ? { stopOnOutputLimit: options.stopOnOutputLimit } : {},
3285
+ env: { ...isolatedGitEnvironment, PATH: runtime.path },
3286
+ unsetEnv: unsafeInheritedGitEnvironment,
3287
+ unsetEnvPrefixes: ["GIT_"],
3288
+ ...options.stdin !== void 0 ? { stdin: options.stdin } : {},
3289
+ ...options.signal ? { signal: options.signal } : {}
3290
+ });
3291
+ }
3292
+
3293
+ // src/context/git-recency.ts
3294
+ var MAX_COMMITS = 128;
3295
+ var MAX_OUTPUT_BYTES = 1e6;
3296
+ var TIMEOUT_MS = 2e3;
3297
+ var MAX_RECENCY_SCORE = 1e-3;
3298
+ var commitHashPattern = /^(?:[a-f\d]{40}|[a-f\d]{64})$/u;
3299
+ var GitRecencyCollector = class {
3300
+ roots;
3301
+ states = /* @__PURE__ */ new Map();
3302
+ constructor(roots) {
3303
+ this.roots = roots.map((root) => resolve9(root));
3304
+ }
3305
+ async collect() {
3306
+ const snapshots = await Promise.all(this.roots.map((root) => this.collectRoot(root)));
3307
+ const scores = /* @__PURE__ */ new Map();
3308
+ for (const snapshot of snapshots) {
3309
+ for (const [path, score] of snapshot.scores) {
3310
+ scores.set(path, Math.max(score, scores.get(path) ?? 0));
3311
+ }
3312
+ }
3313
+ return {
3314
+ generation: digest(snapshots.map(({ root, generation }) => `${root}\0${generation}`).join("\n")),
3315
+ scores
3316
+ };
3317
+ }
3318
+ async collectRoot(root) {
3319
+ const state = this.states.get(root) ?? {
3320
+ runtime: void 0,
3321
+ runtimeResolved: false,
3322
+ head: void 0,
3323
+ snapshot: void 0
3324
+ };
3325
+ this.states.set(root, state);
3326
+ try {
3327
+ if (!state.runtimeResolved) {
3328
+ state.runtime = await resolveExecutableRuntime("git", root, this.roots);
3329
+ state.runtimeResolved = true;
3330
+ }
3331
+ if (!state.runtime) return unavailableSnapshot(root, "missing");
3332
+ const headResult = await runIsolatedGit(
3333
+ state.runtime,
3334
+ ["rev-parse", "--verify", "HEAD"],
3335
+ root,
3336
+ { timeoutMs: TIMEOUT_MS, maxOutputBytes: 256, stopOnOutputLimit: true }
3337
+ );
3338
+ const head = headResult.stdout.trim();
3339
+ if (headResult.exitCode !== 0 || headResult.timedOut || headResult.stdoutTruncated || headResult.stderrTruncated || !commitHashPattern.test(head)) {
3340
+ state.head = void 0;
3341
+ state.snapshot = unavailableSnapshot(root, "no-head");
3342
+ return state.snapshot;
3343
+ }
3344
+ if (state.head === head && state.snapshot) return state.snapshot;
3345
+ state.head = head;
3346
+ state.snapshot = await collectRootGitLog(root, state.runtime);
3347
+ return state.snapshot;
3348
+ } catch {
3349
+ state.head = void 0;
3350
+ state.snapshot = unavailableSnapshot(root, "error");
3351
+ return state.snapshot;
3352
+ }
3353
+ }
3354
+ };
3355
+ async function collectRootGitLog(root, runtime) {
3356
+ try {
3357
+ const result = await runIsolatedGit(runtime, [
3358
+ "--no-pager",
3359
+ "log",
3360
+ "--topo-order",
3361
+ `--max-count=${MAX_COMMITS}`,
3362
+ "--format=%x00%H",
3363
+ "--name-only",
3364
+ "-z",
3365
+ "--no-renames",
3366
+ "--no-ext-diff",
3367
+ "--no-textconv",
3368
+ "--relative",
3369
+ "--",
3370
+ "."
3371
+ ], root, {
3372
+ timeoutMs: TIMEOUT_MS,
3373
+ maxOutputBytes: MAX_OUTPUT_BYTES,
3374
+ stopOnOutputLimit: true
3375
+ });
3376
+ if (result.exitCode !== 0 || result.timedOut || result.stdoutTruncated || result.stderrTruncated) {
3377
+ const reason = result.timedOut ? "timeout" : result.stdoutTruncated || result.stderrTruncated ? "output-limit" : `exit-${result.exitCode}`;
3378
+ return unavailableSnapshot(root, reason);
3379
+ }
3380
+ return {
3381
+ root,
3382
+ generation: digest(result.stdout),
3383
+ scores: parseGitLog(root, result.stdout)
3384
+ };
3385
+ } catch {
3386
+ return unavailableSnapshot(root, "error");
3387
+ }
3388
+ }
3389
+ function parseGitLog(root, output2) {
3390
+ const scores = /* @__PURE__ */ new Map();
3391
+ const records = output2.split("\0");
3392
+ let commitRank = -1;
3393
+ for (let index = 0; index < records.length; index += 1) {
3394
+ const record = records[index] ?? "";
3395
+ if (!record) {
3396
+ const commit = records[index + 1]?.trim() ?? "";
3397
+ if (commitHashPattern.test(commit)) {
3398
+ commitRank += 1;
3399
+ index += 1;
3400
+ }
3401
+ continue;
3402
+ }
3403
+ if (commitRank < 0) continue;
3404
+ const path = record.startsWith("\n") ? record.slice(1) : record;
3405
+ if (!path) continue;
3406
+ const absolutePath = resolve9(root, path);
3407
+ if (!isInside(root, absolutePath) || scores.has(absolutePath)) continue;
3408
+ scores.set(absolutePath, MAX_RECENCY_SCORE / (commitRank + 1));
3409
+ }
3410
+ return scores;
3411
+ }
3412
+ function unavailableSnapshot(root, reason) {
3413
+ return { root, generation: digest(`unavailable\0${reason}`), scores: /* @__PURE__ */ new Map() };
3414
+ }
3415
+ function digest(value) {
3416
+ return createHash6("sha256").update(value, "utf8").digest("hex").slice(0, 16);
3417
+ }
3418
+
3049
3419
  // src/context/local-index.ts
3050
3420
  var contentHashSchema = z3.string().regex(/^[a-f\d]{64}$/u);
3051
3421
  var indexedChunkSchema = z3.object({
@@ -3108,17 +3478,23 @@ var MAX_QUERY_CACHE_ENTRIES = 64;
3108
3478
  var CHUNK_LINES = 100;
3109
3479
  var CHUNK_OVERLAP = 15;
3110
3480
  var MIN_STRUCTURAL_CHUNK_LINES = 12;
3481
+ var MAX_DIAGNOSTIC_SCORE = 0.05;
3482
+ var MAX_DIAGNOSTIC_HINTS_PER_COMMAND = 16;
3111
3483
  var LocalContextIndex = class {
3112
3484
  constructor(roots) {
3113
3485
  this.roots = roots;
3114
- this.roots = roots.map((root) => resolve8(root));
3486
+ this.roots = roots.map((root) => resolve10(root));
3115
3487
  this.workspace = new WorkspaceAccess(this.roots);
3116
- this.indexPath = join7(resolveProjectNamespaceSync(this.roots[0] ?? process.cwd()).active, "index.json");
3488
+ this.gitRecency = new GitRecencyCollector(this.roots);
3489
+ this.indexPath = join8(resolveProjectNamespaceSync(this.roots[0] ?? process.cwd()).active, "index.json");
3117
3490
  }
3118
3491
  roots;
3119
3492
  index;
3120
3493
  workspace;
3121
3494
  queryCache = /* @__PURE__ */ new Map();
3495
+ gitRecency;
3496
+ diagnosticsByCommand = /* @__PURE__ */ new Map();
3497
+ diagnosticScores = /* @__PURE__ */ new Map();
3122
3498
  queryCacheHits = 0;
3123
3499
  queryCacheMisses = 0;
3124
3500
  fingerprintCache;
@@ -3132,13 +3508,13 @@ var LocalContextIndex = class {
3132
3508
  this.roots[0] ?? process.cwd(),
3133
3509
  dirname7(this.indexPath)
3134
3510
  );
3135
- const info = await lstat8(this.indexPath);
3511
+ const info = await lstat9(this.indexPath);
3136
3512
  if (!info.isFile() || info.isSymbolicLink()) return false;
3137
3513
  const parsed = localIndexSchema.parse(JSON.parse(await readFile3(this.indexPath, "utf8")));
3138
3514
  const files = [];
3139
3515
  for (const file of parsed.files) {
3140
3516
  try {
3141
- if (!this.roots.includes(file.root) || resolve8(file.root, file.path) !== file.absolutePath) continue;
3517
+ if (!this.roots.includes(file.root) || resolve10(file.root, file.path) !== file.absolutePath) continue;
3142
3518
  const safe = await this.workspace.resolvePath(file.absolutePath, { expect: "file" });
3143
3519
  if (safe !== file.absolutePath) continue;
3144
3520
  const { ctimeMs, ...persistedFile } = file;
@@ -3215,19 +3591,21 @@ var LocalContextIndex = class {
3215
3591
  await this.flushDirty();
3216
3592
  await this.ensureCurrentIndex();
3217
3593
  const limit = Math.max(1, Math.floor(topK));
3218
- const cached = this.getCachedHits(query, limit);
3219
- let hits = cached ?? this.rank(query, limit);
3594
+ let recency = await this.gitRecency.collect();
3595
+ const cached = this.getCachedHits(query, limit, recency.generation);
3596
+ let hits = cached ?? this.rank(query, limit, recency.scores);
3220
3597
  if (!await this.hitsAreCurrent(hits)) {
3221
3598
  await this.buildWithOptions(void 0, true);
3222
- hits = this.rank(query, limit);
3599
+ recency = await this.gitRecency.collect();
3600
+ hits = this.rank(query, limit, recency.scores);
3223
3601
  if (!await this.hitsAreCurrent(hits)) return [];
3224
3602
  }
3225
- this.cacheHits(query, limit, hits);
3603
+ this.cacheHits(query, limit, recency.generation, hits);
3226
3604
  return cloneHits(hits);
3227
3605
  }
3228
3606
  invalidate(paths) {
3229
3607
  for (const path of paths) {
3230
- const absolutePath = isAbsolute3(path) ? resolve8(path) : resolve8(this.roots[0] ?? process.cwd(), path);
3608
+ const absolutePath = isAbsolute4(path) ? resolve10(path) : resolve10(this.roots[0] ?? process.cwd(), path);
3231
3609
  if (this.roots.some((root) => isWithinRoot(root, absolutePath))) {
3232
3610
  this.dirtyPaths.add(absolutePath);
3233
3611
  }
@@ -3235,6 +3613,23 @@ var LocalContextIndex = class {
3235
3613
  if (paths.length) this.queryCache.clear();
3236
3614
  if (this.dirtyPaths.size) this.refreshState = "dirty";
3237
3615
  }
3616
+ recordDiagnostics(update) {
3617
+ const paths = /* @__PURE__ */ new Set();
3618
+ for (const candidate of update.paths.slice(0, MAX_DIAGNOSTIC_HINTS_PER_COMMAND)) {
3619
+ const path = this.resolveDiagnosticPath(candidate);
3620
+ if (path) paths.add(path);
3621
+ }
3622
+ if (paths.size) this.diagnosticsByCommand.set(update.commandKey, paths);
3623
+ else this.diagnosticsByCommand.delete(update.commandKey);
3624
+ this.refreshDiagnosticScores();
3625
+ this.queryCache.clear();
3626
+ }
3627
+ resetDiagnostics() {
3628
+ if (!this.diagnosticsByCommand.size) return;
3629
+ this.diagnosticsByCommand.clear();
3630
+ this.diagnosticScores.clear();
3631
+ this.queryCache.clear();
3632
+ }
3238
3633
  async flushDirty() {
3239
3634
  if (!this.dirtyPaths.size) {
3240
3635
  return {
@@ -3320,6 +3715,7 @@ var LocalContextIndex = class {
3320
3715
  queryCacheEntries: this.queryCache.size,
3321
3716
  queryCacheHits: this.queryCacheHits,
3322
3717
  queryCacheMisses: this.queryCacheMisses,
3718
+ diagnosticHints: this.diagnosticScores.size,
3323
3719
  refreshState: this.refreshState,
3324
3720
  dirtyPaths: this.dirtyPaths.size,
3325
3721
  ...this.refreshError ? { refreshError: this.refreshError } : {},
@@ -3424,6 +3820,7 @@ var LocalContextIndex = class {
3424
3820
  );
3425
3821
  await atomicWrite(this.indexPath, `${JSON.stringify(this.index)}
3426
3822
  `, 384);
3823
+ await this.gitRecency.collect();
3427
3824
  onProgress?.({ phase: "done", completed: files.length, total: files.length });
3428
3825
  return {
3429
3826
  files: files.length,
@@ -3517,7 +3914,7 @@ var LocalContextIndex = class {
3517
3914
  followSymbolicLinks: false,
3518
3915
  ignore: ignorePatterns
3519
3916
  });
3520
- for (const path of paths) discovered.push({ root, path, absolutePath: resolve8(root, path) });
3917
+ for (const path of paths) discovered.push({ root, path, absolutePath: resolve10(root, path) });
3521
3918
  }
3522
3919
  discovered.sort((left, right) => left.absolutePath.localeCompare(right.absolutePath));
3523
3920
  return discovered;
@@ -3537,7 +3934,7 @@ var LocalContextIndex = class {
3537
3934
  });
3538
3935
  return matches.length === 1 ? { root, path, absolutePath } : void 0;
3539
3936
  }
3540
- rank(query, topK) {
3937
+ rank(query, topK, recencyScores) {
3541
3938
  const index = this.index;
3542
3939
  const files = index?.files ?? [];
3543
3940
  const chunks = files.flatMap((file) => file.chunks);
@@ -3567,11 +3964,13 @@ var LocalContextIndex = class {
3567
3964
  averageLength
3568
3965
  );
3569
3966
  const graph = graphBoosts.get(chunk.absolutePath)?.score ?? 0;
3570
- const score = lexical.bm25 + lexical.path + lexical.symbol + lexical.phrase + graph;
3571
- return { chunk, lexical, graph, score };
3572
- }).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || left.chunk.absolutePath.localeCompare(right.chunk.absolutePath));
3967
+ const recency = recencyScores.get(chunk.absolutePath) ?? 0;
3968
+ const diagnostic = this.diagnosticScores.get(chunk.absolutePath) ?? 0;
3969
+ const score = lexical.bm25 + lexical.path + lexical.symbol + lexical.phrase + graph + recency + diagnostic;
3970
+ return { chunk, lexical, graph, recency, diagnostic, score };
3971
+ }).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));
3573
3972
  const covered = scored.filter(({ lexical, graph }) => graph > 0 || hasSufficientQueryCoverage(lexical.matchedTerms, coverageTerms));
3574
- return (covered.length ? covered : scored).slice(0, topK).map(({ chunk, lexical, graph, score }) => {
3973
+ return (covered.length ? covered : scored).slice(0, topK).map(({ chunk, lexical, graph, recency, diagnostic, score }) => {
3575
3974
  const file = filesByPath.get(chunk.absolutePath);
3576
3975
  const breakdown = {
3577
3976
  bm25: roundScore(lexical.bm25),
@@ -3579,6 +3978,8 @@ var LocalContextIndex = class {
3579
3978
  symbol: roundScore(lexical.symbol),
3580
3979
  phrase: roundScore(lexical.phrase),
3581
3980
  graph: roundScore(graph),
3981
+ recency: roundScore(recency),
3982
+ diagnostic: roundScore(diagnostic),
3582
3983
  total: roundScore(score)
3583
3984
  };
3584
3985
  const provenance = {
@@ -3594,7 +3995,7 @@ var LocalContextIndex = class {
3594
3995
  endLine: chunk.endLine,
3595
3996
  content: chunk.content,
3596
3997
  score,
3597
- source: "local-bm25+path+symbol+graph",
3998
+ source: "local-bm25+path+symbol+graph+recency+diagnostic",
3598
3999
  ...chunk.symbol ? { symbol: chunk.symbol } : {},
3599
4000
  provenance
3600
4001
  };
@@ -3620,13 +4021,13 @@ var LocalContextIndex = class {
3620
4021
  }
3621
4022
  return true;
3622
4023
  }
3623
- getCachedHits(query, topK) {
4024
+ getCachedHits(query, topK, rankingGeneration) {
3624
4025
  const generation = this.index?.generation;
3625
4026
  if (!generation) {
3626
4027
  this.queryCacheMisses += 1;
3627
4028
  return void 0;
3628
4029
  }
3629
- const key = `${generation}\0${topK}\0${query}`;
4030
+ const key = `${generation}\0${rankingGeneration}\0${topK}\0${query}`;
3630
4031
  const cached = this.queryCache.get(key);
3631
4032
  if (!cached) {
3632
4033
  this.queryCacheMisses += 1;
@@ -3637,11 +4038,11 @@ var LocalContextIndex = class {
3637
4038
  this.queryCache.set(key, cached);
3638
4039
  return cloneHits(cached);
3639
4040
  }
3640
- cacheHits(query, topK, hits) {
4041
+ cacheHits(query, topK, rankingGeneration, hits) {
3641
4042
  if (!hits.length) return;
3642
4043
  const generation = this.index?.generation;
3643
4044
  if (!generation) return;
3644
- const key = `${generation}\0${topK}\0${query}`;
4045
+ const key = `${generation}\0${rankingGeneration}\0${topK}\0${query}`;
3645
4046
  this.queryCache.delete(key);
3646
4047
  this.queryCache.set(key, cloneHits(hits));
3647
4048
  while (this.queryCache.size > MAX_QUERY_CACHE_ENTRIES) {
@@ -3650,10 +4051,26 @@ var LocalContextIndex = class {
3650
4051
  this.queryCache.delete(oldest);
3651
4052
  }
3652
4053
  }
4054
+ resolveDiagnosticPath(candidate) {
4055
+ if (!candidate || candidate.includes("\0")) return void 0;
4056
+ const absolute = isAbsolute4(candidate) ? resolve10(candidate) : void 0;
4057
+ if (absolute && this.roots.some((root) => isWithinRoot(root, absolute))) return absolute;
4058
+ for (const root of this.roots) {
4059
+ const path = resolve10(root, candidate);
4060
+ if (isWithinRoot(root, path)) return path;
4061
+ }
4062
+ return void 0;
4063
+ }
4064
+ refreshDiagnosticScores() {
4065
+ this.diagnosticScores.clear();
4066
+ for (const paths of this.diagnosticsByCommand.values()) {
4067
+ for (const path of paths) this.diagnosticScores.set(path, MAX_DIAGNOSTIC_SCORE);
4068
+ }
4069
+ }
3653
4070
  };
3654
4071
  function isWithinRoot(root, path) {
3655
4072
  const offset = relative5(root, path);
3656
- return offset === "" || !offset.startsWith("..") && !isAbsolute3(offset);
4073
+ return offset === "" || !offset.startsWith("..") && !isAbsolute4(offset);
3657
4074
  }
3658
4075
  function isIncludedSourcePath(path) {
3659
4076
  const name = basename5(path);
@@ -3785,7 +4202,7 @@ function packContextHits(hits, roots, maxTokens, engine) {
3785
4202
  const text = selected.map((hit) => {
3786
4203
  const shownPath = workspaceAliasPath(hit.path, roots);
3787
4204
  const symbol = hit.symbol ? ` symbol="${escapeAttribute(hit.symbol)}"` : "";
3788
- const provenance = hit.provenance ? ` source="${escapeAttribute(hit.source)}" hash="${hit.provenance.contentHash.slice(0, 12)}" graph-score="${hit.provenance.score.graph.toFixed(3)}"` : "";
4205
+ 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)}" diagnostic-score="${hit.provenance.score.diagnostic.toFixed(3)}"` : "";
3789
4206
  return `<code path="${escapeAttribute(shownPath)}" lines="${hit.startLine}-${hit.endLine}" score="${hit.score.toFixed(3)}"${symbol}${provenance}>
3790
4207
  ${hit.content}
3791
4208
  </code>`;
@@ -3833,10 +4250,10 @@ function reconstructIndexedContent(chunks) {
3833
4250
  return lines.map((line) => line ?? "").join("\n");
3834
4251
  }
3835
4252
  function hashContent(content) {
3836
- return createHash6("sha256").update(content, "utf8").digest("hex");
4253
+ return createHash7("sha256").update(content, "utf8").digest("hex");
3837
4254
  }
3838
4255
  function createGeneration(files) {
3839
- return createHash6("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);
4256
+ 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);
3840
4257
  }
3841
4258
  function hasSubstantialOverlap(left, right) {
3842
4259
  if (left.path !== right.path) return false;
@@ -4112,18 +4529,37 @@ function definitionMatchesQuery(definitionTerms, queryTerms) {
4112
4529
  return shared.length >= required;
4113
4530
  }
4114
4531
  function resolveImportTargets(importer, specifier, files) {
4115
- if (!specifier.startsWith(".")) return [];
4116
- const base = posix.normalize(posix.join(posix.dirname(importer.path), specifier));
4117
- const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
4118
- const importedExtension = posix.extname(base);
4119
- const extensionless = extensions.includes(importedExtension) ? base.slice(0, -importedExtension.length) : base;
4120
- const candidates = /* @__PURE__ */ new Set([
4121
- base,
4122
- ...extensions.map((extension) => `${extensionless}${extension}`),
4123
- ...extensions.map((extension) => posix.join(extensionless, `index${extension}`))
4124
- ]);
4532
+ const candidates = /* @__PURE__ */ new Set();
4533
+ const extension = extname2(importer.path).toLocaleLowerCase();
4534
+ if (specifier.startsWith(".")) {
4535
+ const base = posix.normalize(posix.join(posix.dirname(importer.path), specifier));
4536
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
4537
+ const importedExtension = posix.extname(base);
4538
+ const extensionless = extensions.includes(importedExtension) ? base.slice(0, -importedExtension.length) : base;
4539
+ candidates.add(base);
4540
+ for (const candidateExtension of extensions) {
4541
+ candidates.add(`${extensionless}${candidateExtension}`);
4542
+ candidates.add(posix.join(extensionless, `index${candidateExtension}`));
4543
+ }
4544
+ }
4545
+ if (extension === ".py") {
4546
+ const modulePath = resolvePythonModulePath(importer.path, specifier);
4547
+ if (modulePath) {
4548
+ candidates.add(`${modulePath}.py`);
4549
+ candidates.add(posix.join(modulePath, "__init__.py"));
4550
+ }
4551
+ }
4125
4552
  return files.filter((file) => file.root === importer.root && candidates.has(file.path)).map((file) => file.absolutePath);
4126
4553
  }
4554
+ function resolvePythonModulePath(importerPath, specifier) {
4555
+ const match = specifier.match(/^(\.+)(.*)$/u);
4556
+ if (!match) return specifier.split(".").filter(Boolean).join("/");
4557
+ const [, dots, remainder] = match;
4558
+ if (!dots || !remainder) return void 0;
4559
+ let base = posix.dirname(importerPath);
4560
+ for (let level = 1; level < dots.length; level += 1) base = posix.dirname(base);
4561
+ return posix.normalize(posix.join(base, remainder.split(".").filter(Boolean).join("/")));
4562
+ }
4127
4563
  function scoreChunk(chunk, terms, rawQuery, documentFrequency, documentCount, averageLength) {
4128
4564
  const frequencies = /* @__PURE__ */ new Map();
4129
4565
  for (const token of chunk.tokens) frequencies.set(token, (frequencies.get(token) ?? 0) + 1);
@@ -4214,6 +4650,12 @@ var ContextEngine = class {
4214
4650
  invalidate(paths) {
4215
4651
  this.local.invalidate(paths);
4216
4652
  }
4653
+ recordDiagnostics(update) {
4654
+ this.local.recordDiagnostics(update);
4655
+ }
4656
+ resetDiagnostics() {
4657
+ this.local.resetDiagnostics();
4658
+ }
4217
4659
  async flushDirty() {
4218
4660
  try {
4219
4661
  const result = await this.local.flushDirty();
@@ -4260,7 +4702,7 @@ function formatContextHits(hits, roots) {
4260
4702
  const path = workspaceAliasPath(hit.path, roots);
4261
4703
  const symbol = hit.symbol ? ` ${hit.symbol}` : "";
4262
4704
  const score = hit.provenance?.score;
4263
- const breakdown = score ? ` bm25=${score.bm25.toFixed(2)} path=${score.path.toFixed(2)} symbol=${score.symbol.toFixed(2)} graph=${score.graph.toFixed(2)}` : "";
4705
+ 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)} diagnostic=${score.diagnostic.toFixed(3)}` : "";
4264
4706
  const hash4 = hit.provenance?.contentHash.slice(0, 12);
4265
4707
  return `[${hit.source} ${hit.score.toFixed(3)}${breakdown}${hash4 ? ` hash=${hash4}` : ""}]${symbol} ${path}:${hit.startLine}-${hit.endLine}`;
4266
4708
  }).join("\n");
@@ -4501,7 +4943,7 @@ function toolTokens(messages) {
4501
4943
 
4502
4944
  // src/context/mentions.ts
4503
4945
  import { readFile as readFile4, stat as stat4 } from "node:fs/promises";
4504
- import { isAbsolute as isAbsolute4, resolve as resolve9 } from "node:path";
4946
+ import { isAbsolute as isAbsolute5, resolve as resolve11 } from "node:path";
4505
4947
  import fg2 from "fast-glob";
4506
4948
  var defaultMentionIgnores = [
4507
4949
  "**/.git/**",
@@ -4572,7 +5014,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
4572
5014
  ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
4573
5015
  });
4574
5016
  matched.push(...await safeMentionPaths(
4575
- children.slice(0, 25).map((path) => resolve9(aliased, path)),
5017
+ children.slice(0, 25).map((path) => resolve11(aliased, path)),
4576
5018
  workspace
4577
5019
  ));
4578
5020
  }
@@ -4580,7 +5022,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
4580
5022
  }
4581
5023
  }
4582
5024
  for (const root of matched.length ? [] : roots) {
4583
- const direct = resolve9(root, clean2);
5025
+ const direct = resolve11(root, clean2);
4584
5026
  try {
4585
5027
  const safeDirect = await workspace.resolvePath(direct);
4586
5028
  const info = await stat4(safeDirect);
@@ -4594,7 +5036,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
4594
5036
  ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
4595
5037
  });
4596
5038
  matched.push(...await safeMentionPaths(
4597
- children.slice(0, 25).map((path) => resolve9(safeDirect, path)),
5039
+ children.slice(0, 25).map((path) => resolve11(safeDirect, path)),
4598
5040
  workspace
4599
5041
  ));
4600
5042
  }
@@ -4610,7 +5052,7 @@ async function resolveMentions(input2, roots, maxChars = 8e4) {
4610
5052
  ignore: ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/.mosaic/**", "**/.skein/**", "**/.skein.migrating-*/**", "**/.skein.rollback-*/**"]
4611
5053
  });
4612
5054
  matched.push(...await safeMentionPaths(
4613
- globbed.slice(0, 25).map((path) => resolve9(root, path)),
5055
+ globbed.slice(0, 25).map((path) => resolve11(root, path)),
4614
5056
  workspace
4615
5057
  ));
4616
5058
  }
@@ -4676,7 +5118,7 @@ async function buildMentionPathIndex(roots, options = {}) {
4676
5118
  })
4677
5119
  })));
4678
5120
  const paths = filesByRoot.flatMap(({ root, files }) => files.map(
4679
- (path) => workspaceAliasPath(resolve9(root, path), normalizedRoots)
5121
+ (path) => workspaceAliasPath(resolve11(root, path), normalizedRoots)
4680
5122
  ));
4681
5123
  return new MentionPathIndex(normalizedRoots, paths);
4682
5124
  }
@@ -4710,22 +5152,22 @@ function mapMentionCandidatePath(path, roots) {
4710
5152
  const primary = normalizedRoots[0];
4711
5153
  if (!primary || !path || path.includes("\0")) return void 0;
4712
5154
  let candidate;
4713
- if (isAbsolute4(path)) {
4714
- candidate = resolve9(path);
5155
+ if (isAbsolute5(path)) {
5156
+ candidate = resolve11(path);
4715
5157
  } else {
4716
5158
  const normalized = path.replaceAll("\\", "/").replace(/^\.\//, "");
4717
5159
  const [first = "", ...rest] = normalized.split("/");
4718
5160
  const alias = first.toLocaleLowerCase();
4719
5161
  const workspace = alias.match(/^workspace(\d+)$/);
4720
5162
  if (alias === "main") {
4721
- candidate = resolve9(primary, ...rest);
5163
+ candidate = resolve11(primary, ...rest);
4722
5164
  } else if (workspace) {
4723
5165
  const index = Number(workspace[1]) - 1;
4724
5166
  const root = Number.isSafeInteger(index) && index >= 1 ? normalizedRoots[index] : void 0;
4725
5167
  if (!root) return void 0;
4726
- candidate = resolve9(root, ...rest);
5168
+ candidate = resolve11(root, ...rest);
4727
5169
  } else {
4728
- candidate = resolve9(primary, normalized);
5170
+ candidate = resolve11(primary, normalized);
4729
5171
  }
4730
5172
  }
4731
5173
  if (!normalizedRoots.some((root) => isInside(root, candidate))) return void 0;
@@ -4755,7 +5197,7 @@ function rankMention(path, needle) {
4755
5197
  }
4756
5198
  function normalizeRoots(roots) {
4757
5199
  const values = typeof roots === "string" ? [roots] : roots;
4758
- return [...new Set(values.map((root) => resolve9(root)))];
5200
+ return [...new Set(values.map((root) => resolve11(root)))];
4759
5201
  }
4760
5202
  function normalizeMentionCandidate(path) {
4761
5203
  const normalized = path.replaceAll("\\", "/").normalize("NFC");
@@ -5421,9 +5863,9 @@ function createProvider(config) {
5421
5863
  }
5422
5864
 
5423
5865
  // src/checkpoint/store.ts
5424
- import { createHash as createHash7, randomUUID as randomUUID7 } from "node:crypto";
5425
- import { lstat as lstat9, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
5426
- import { basename as basename6, dirname as dirname8, join as join8, relative as relative6, resolve as resolve10 } from "node:path";
5866
+ import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
5867
+ import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
5868
+ import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
5427
5869
  import { z as z4 } from "zod";
5428
5870
  var entrySchema = z4.object({
5429
5871
  path: z4.string(),
@@ -5448,7 +5890,7 @@ var CheckpointStore = class {
5448
5890
  constructor(workspace, directory) {
5449
5891
  this.workspace = typeof workspace === "string" ? new WorkspaceAccess([workspace]) : workspace;
5450
5892
  this.managedDirectory = directory === void 0;
5451
- this.directory = directory ? resolve10(directory) : join8(resolveProjectNamespaceSync(this.workspace.primaryRoot).active, "checkpoints");
5893
+ this.directory = directory ? resolve12(directory) : join9(resolveProjectNamespaceSync(this.workspace.primaryRoot).active, "checkpoints");
5452
5894
  }
5453
5895
  async capture(sessionId, paths, options = {}) {
5454
5896
  validateIdentifier(sessionId, "session");
@@ -5458,8 +5900,8 @@ var CheckpointStore = class {
5458
5900
  }
5459
5901
  async captureUnlocked(sessionId, unique3, options) {
5460
5902
  const id = `${Date.now().toString(36)}-${randomUUID7()}`;
5461
- const target = join8(this.directory, sessionId, id);
5462
- const blobDirectory = join8(target, "blobs");
5903
+ const target = join9(this.directory, sessionId, id);
5904
+ const blobDirectory = join9(target, "blobs");
5463
5905
  await this.ensureDirectory();
5464
5906
  await this.assertManagedPath(target);
5465
5907
  await mkdir7(blobDirectory, { recursive: true });
@@ -5468,14 +5910,14 @@ var CheckpointStore = class {
5468
5910
  for (const input2 of unique3) {
5469
5911
  const path = await this.workspace.resolvePath(input2, { allowMissing: true });
5470
5912
  try {
5471
- const info = await lstat9(path);
5913
+ const info = await lstat10(path);
5472
5914
  if (info.isSymbolicLink()) throw new Error(`Cannot checkpoint a symbolic link: ${input2}`);
5473
5915
  if (!info.isFile()) throw new Error(`Cannot checkpoint a non-file path: ${input2}`);
5474
5916
  if (info.size > 25e6) {
5475
5917
  throw new Error(`File is too large to checkpoint (${info.size} bytes): ${input2}`);
5476
5918
  }
5477
- const blob = `${createHash7("sha256").update(path).digest("hex")}.bin`;
5478
- await atomicWrite(join8(blobDirectory, blob), await readFile5(path), 384);
5919
+ const blob = `${createHash8("sha256").update(path).digest("hex")}.bin`;
5920
+ await atomicWrite(join9(blobDirectory, blob), await readFile5(path), 384);
5479
5921
  entries.push({
5480
5922
  path,
5481
5923
  relativePath: relative6(this.workspace.primaryRoot, path),
@@ -5502,7 +5944,7 @@ var CheckpointStore = class {
5502
5944
  entries
5503
5945
  };
5504
5946
  await atomicWrite(
5505
- join8(target, "manifest.json"),
5947
+ join9(target, "manifest.json"),
5506
5948
  `${JSON.stringify(manifest, null, 2)}
5507
5949
  `,
5508
5950
  384
@@ -5515,9 +5957,9 @@ var CheckpointStore = class {
5515
5957
  if (!await this.directoryAvailable()) {
5516
5958
  throw new Error(`Checkpoint not found: ${checkpointId}`);
5517
5959
  }
5518
- const checkpointDirectory = join8(this.directory, sessionId, checkpointId);
5960
+ const checkpointDirectory = join9(this.directory, sessionId, checkpointId);
5519
5961
  await this.assertManagedPath(checkpointDirectory);
5520
- const manifestPath = join8(checkpointDirectory, "manifest.json");
5962
+ const manifestPath = join9(checkpointDirectory, "manifest.json");
5521
5963
  await this.assertManagedFile(manifestPath);
5522
5964
  const raw = await readFile5(manifestPath, "utf8");
5523
5965
  const manifest = manifestSchema.parse(JSON.parse(raw));
@@ -5528,7 +5970,7 @@ var CheckpointStore = class {
5528
5970
  }
5529
5971
  async restore(sessionId, checkpointId) {
5530
5972
  const manifest = await this.load(sessionId, checkpointId);
5531
- const blobDirectory = join8(this.directory, sessionId, checkpointId, "blobs");
5973
+ const blobDirectory = join9(this.directory, sessionId, checkpointId, "blobs");
5532
5974
  await this.assertManagedPath(blobDirectory);
5533
5975
  const actions = [];
5534
5976
  for (const entry of manifest.entries) {
@@ -5543,8 +5985,8 @@ var CheckpointStore = class {
5543
5985
  if (!/^[a-f0-9]{64}\.bin$/.test(entry.blob) || basename6(entry.blob) !== entry.blob) {
5544
5986
  throw new Error(`Checkpoint blob name is invalid for ${entry.relativePath}.`);
5545
5987
  }
5546
- const blobPath = join8(blobDirectory, entry.blob);
5547
- const blobInfo = await lstat9(blobPath);
5988
+ const blobPath = join9(blobDirectory, entry.blob);
5989
+ const blobInfo = await lstat10(blobPath);
5548
5990
  if (!blobInfo.isFile() || blobInfo.isSymbolicLink()) {
5549
5991
  throw new Error(`Checkpoint blob is not a regular file for ${entry.relativePath}.`);
5550
5992
  }
@@ -5585,7 +6027,7 @@ var CheckpointStore = class {
5585
6027
  async list(sessionId) {
5586
6028
  validateIdentifier(sessionId, "session");
5587
6029
  if (!await this.directoryAvailable()) return [];
5588
- const directory = join8(this.directory, sessionId);
6030
+ const directory = join9(this.directory, sessionId);
5589
6031
  let names;
5590
6032
  try {
5591
6033
  names = await readdir2(directory);
@@ -5624,7 +6066,7 @@ var CheckpointStore = class {
5624
6066
  await assertNoSymlinkPath(this.workspace.primaryRoot, this.directory);
5625
6067
  }
5626
6068
  try {
5627
- const info = await lstat9(this.directory);
6069
+ const info = await lstat10(this.directory);
5628
6070
  if (info.isSymbolicLink() || !info.isDirectory()) {
5629
6071
  throw new Error(`Checkpoint storage is not a regular directory: ${this.directory}`);
5630
6072
  }
@@ -5643,7 +6085,7 @@ var CheckpointStore = class {
5643
6085
  await this.assertManagedPath(dirname8(path));
5644
6086
  if (!this.managedDirectory) return;
5645
6087
  try {
5646
- if ((await lstat9(path)).isSymbolicLink()) {
6088
+ if ((await lstat10(path)).isSymbolicLink()) {
5647
6089
  throw new Error(`Checkpoint path cannot contain a symbolic link: ${path}`);
5648
6090
  }
5649
6091
  } catch (error) {
@@ -5653,7 +6095,7 @@ var CheckpointStore = class {
5653
6095
  };
5654
6096
  async function readSnapshot(path) {
5655
6097
  try {
5656
- const info = await lstat9(path);
6098
+ const info = await lstat10(path);
5657
6099
  if (info.isSymbolicLink()) throw new Error(`Cannot restore over a symbolic link: ${path}`);
5658
6100
  if (!info.isFile()) throw new Error(`Cannot restore over a non-file path: ${path}`);
5659
6101
  return { content: await readFile5(path), mode: info.mode };
@@ -5678,174 +6120,6 @@ function validateIdentifier(value, kind) {
5678
6120
  }
5679
6121
  }
5680
6122
 
5681
- // src/utils/process.ts
5682
- import { spawn } from "node:child_process";
5683
- import { constants as constants3 } from "node:fs";
5684
- import { access as access2, lstat as lstat10, realpath as realpath6 } from "node:fs/promises";
5685
- import { delimiter, isAbsolute as isAbsolute5, join as join9, resolve as resolve11 } from "node:path";
5686
- import { StringDecoder } from "node:string_decoder";
5687
- async function resolveExecutableRuntime(command2, cwd, excludedRoots = []) {
5688
- const realRoots = await Promise.all(excludedRoots.map(async (root) => {
5689
- try {
5690
- return await realpath6(root);
5691
- } catch {
5692
- return resolve11(root);
5693
- }
5694
- }));
5695
- const pathEntries = (process.env.PATH ?? process.env.Path ?? "").split(delimiter).filter(Boolean);
5696
- const safeDirectories2 = [];
5697
- let executable2;
5698
- const explicit = isAbsolute5(command2) || command2.includes("/") || command2.includes("\\");
5699
- const explicitPath = explicit ? resolve11(cwd, command2) : void 0;
5700
- for (const entry of pathEntries) {
5701
- let directory;
5702
- try {
5703
- directory = await realpath6(resolve11(cwd, entry));
5704
- if (!(await lstat10(directory)).isDirectory()) continue;
5705
- } catch {
5706
- continue;
5707
- }
5708
- if (realRoots.some((root) => isInside(root, directory))) continue;
5709
- let contaminated = false;
5710
- if (!explicit) {
5711
- for (const name of executableNames(command2)) {
5712
- const candidate = join9(directory, name);
5713
- const resolvedCandidate = await usableExecutable(candidate);
5714
- if (!resolvedCandidate) continue;
5715
- if (realRoots.some((root) => isInside(root, resolvedCandidate))) {
5716
- contaminated = true;
5717
- continue;
5718
- }
5719
- executable2 ??= resolvedCandidate;
5720
- }
5721
- }
5722
- if (!contaminated && !safeDirectories2.includes(directory)) safeDirectories2.push(directory);
5723
- }
5724
- if (explicitPath) executable2 = await usableExecutable(explicitPath);
5725
- if (!executable2) return void 0;
5726
- return { executable: executable2, path: safeDirectories2.join(delimiter) };
5727
- }
5728
- async function usableExecutable(candidate) {
5729
- try {
5730
- await access2(candidate, process.platform === "win32" ? constants3.F_OK : constants3.X_OK);
5731
- const resolvedCandidate = await realpath6(candidate);
5732
- return (await lstat10(resolvedCandidate)).isFile() ? resolvedCandidate : void 0;
5733
- } catch {
5734
- return void 0;
5735
- }
5736
- }
5737
- function executableNames(command2) {
5738
- if (process.platform !== "win32") return [command2];
5739
- const extensions = (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean);
5740
- return [command2, ...extensions.map((extension) => `${command2}${extension}`)];
5741
- }
5742
- function runProcess(command2, args, options) {
5743
- return new Promise((resolve24, reject) => {
5744
- const started = Date.now();
5745
- const environment = options.inheritEnv === false ? {} : { ...process.env };
5746
- for (const name of options.unsetEnv ?? []) delete environment[name];
5747
- for (const name of Object.keys(environment)) {
5748
- if (options.unsetEnvPrefixes?.some((prefix) => name.startsWith(prefix))) {
5749
- delete environment[name];
5750
- }
5751
- }
5752
- Object.assign(environment, options.env);
5753
- const child = spawn(command2, args, {
5754
- cwd: options.cwd,
5755
- env: environment,
5756
- stdio: ["pipe", "pipe", "pipe"],
5757
- signal: options.signal
5758
- });
5759
- const maxBytes = options.maxOutputBytes ?? 1e6;
5760
- let stdout = "";
5761
- let stderr = "";
5762
- let stdoutBytes = 0;
5763
- let stderrBytes = 0;
5764
- let stdoutObservedBytes = 0;
5765
- let stderrObservedBytes = 0;
5766
- let timedOut = false;
5767
- let callbackError;
5768
- const stdoutDecoder = new StringDecoder("utf8");
5769
- const stderrDecoder = new StringDecoder("utf8");
5770
- const stdoutCallbackDecoder = new StringDecoder("utf8");
5771
- const stderrCallbackDecoder = new StringDecoder("utf8");
5772
- const append = (decoder, chunk, usedBytes) => {
5773
- if (usedBytes >= maxBytes) return { text: "", usedBytes };
5774
- const selected = chunk.subarray(0, maxBytes - usedBytes);
5775
- return { text: decoder.write(selected), usedBytes: usedBytes + selected.length };
5776
- };
5777
- const notify = (callback, decoder, chunk) => {
5778
- if (!callback || callbackError) return;
5779
- try {
5780
- const decoded = decoder.write(chunk);
5781
- if (decoded) callback(decoded);
5782
- } catch (error) {
5783
- callbackError = error;
5784
- child.kill("SIGTERM");
5785
- }
5786
- };
5787
- child.stdout.on("data", (chunk) => {
5788
- stdoutObservedBytes += chunk.length;
5789
- const appended = append(stdoutDecoder, chunk, stdoutBytes);
5790
- stdout += appended.text;
5791
- stdoutBytes = appended.usedBytes;
5792
- notify(options.onStdout, stdoutCallbackDecoder, chunk);
5793
- });
5794
- child.stderr.on("data", (chunk) => {
5795
- stderrObservedBytes += chunk.length;
5796
- const appended = append(stderrDecoder, chunk, stderrBytes);
5797
- stderr += appended.text;
5798
- stderrBytes = appended.usedBytes;
5799
- notify(options.onStderr, stderrCallbackDecoder, chunk);
5800
- });
5801
- child.on("error", reject);
5802
- const timeoutMs = options.timeoutMs ?? 12e4;
5803
- const timeout = timeoutMs > 0 ? setTimeout(() => {
5804
- timedOut = true;
5805
- child.kill("SIGTERM");
5806
- setTimeout(() => child.kill("SIGKILL"), 1e3).unref();
5807
- }, timeoutMs) : void 0;
5808
- child.on("close", (code) => {
5809
- if (timeout) clearTimeout(timeout);
5810
- const stdoutTail = stdoutDecoder.end();
5811
- const stderrTail = stderrDecoder.end();
5812
- if (Buffer.byteLength(stdout) + Buffer.byteLength(stdoutTail) <= maxBytes) stdout += stdoutTail;
5813
- if (Buffer.byteLength(stderr) + Buffer.byteLength(stderrTail) <= maxBytes) stderr += stderrTail;
5814
- try {
5815
- const stdoutCallbackTail = stdoutCallbackDecoder.end();
5816
- const stderrCallbackTail = stderrCallbackDecoder.end();
5817
- if (stdoutCallbackTail && options.onStdout && !callbackError) options.onStdout(stdoutCallbackTail);
5818
- if (stderrCallbackTail && options.onStderr && !callbackError) options.onStderr(stderrCallbackTail);
5819
- } catch (error) {
5820
- callbackError = error;
5821
- }
5822
- if (callbackError) {
5823
- reject(callbackError);
5824
- return;
5825
- }
5826
- resolve24({
5827
- command: [command2, ...args].join(" "),
5828
- exitCode: code ?? (timedOut ? 124 : 1),
5829
- stdout,
5830
- stderr,
5831
- timedOut,
5832
- durationMs: Date.now() - started,
5833
- stdoutBytes: stdoutObservedBytes,
5834
- stderrBytes: stderrObservedBytes,
5835
- stdoutTruncated: stdoutObservedBytes > stdoutBytes,
5836
- stderrTruncated: stderrObservedBytes > stderrBytes
5837
- });
5838
- });
5839
- if (options.stdin) child.stdin.end(options.stdin);
5840
- else child.stdin.end();
5841
- });
5842
- }
5843
- function runShell(command2, cwd, options = {}) {
5844
- const shell = process.platform === "win32" ? process.env.COMSPEC ?? "cmd.exe" : process.env.SHELL ?? "/bin/sh";
5845
- const args = process.platform === "win32" ? ["/d", "/s", "/c", command2] : ["-lc", command2];
5846
- return runProcess(shell, args, { ...options, cwd });
5847
- }
5848
-
5849
6123
  // src/hooks/runner.ts
5850
6124
  var HookError = class extends Error {
5851
6125
  constructor(message2, result) {
@@ -5915,7 +6189,7 @@ import {
5915
6189
  stat as stat5,
5916
6190
  unlink as unlink3
5917
6191
  } from "node:fs/promises";
5918
- import { basename as basename7, dirname as dirname9, join as join10, resolve as resolve12 } from "node:path";
6192
+ import { basename as basename7, dirname as dirname9, join as join10, resolve as resolve13 } from "node:path";
5919
6193
  import { z as z5 } from "zod";
5920
6194
  var sessionIdSchema = z5.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/);
5921
6195
  var toolCallSchema = z5.object({
@@ -6174,9 +6448,9 @@ var SessionStore = class {
6174
6448
  managedDirectory;
6175
6449
  writes = Promise.resolve();
6176
6450
  constructor(workspace, directory) {
6177
- this.workspace = resolve12(workspace);
6451
+ this.workspace = resolve13(workspace);
6178
6452
  this.managedDirectory = directory === void 0;
6179
- this.directory = directory ? resolve12(directory) : join10(resolveProjectNamespaceSync(this.workspace).active, "sessions");
6453
+ this.directory = directory ? resolve13(directory) : join10(resolveProjectNamespaceSync(this.workspace).active, "sessions");
6180
6454
  }
6181
6455
  async create(options) {
6182
6456
  const session = createSession({ ...options, workspace: this.workspace });
@@ -6185,7 +6459,7 @@ var SessionStore = class {
6185
6459
  }
6186
6460
  async save(session) {
6187
6461
  validateId(session.id);
6188
- if (resolve12(session.workspace) !== this.workspace) {
6462
+ if (resolve13(session.workspace) !== this.workspace) {
6189
6463
  throw new Error("Session workspace does not match this store.");
6190
6464
  }
6191
6465
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -6224,7 +6498,7 @@ var SessionStore = class {
6224
6498
  const summaries = await Promise.all(
6225
6499
  entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => {
6226
6500
  const session = await tryReadSession(join10(this.directory, entry.name));
6227
- if (!session || resolve12(session.workspace) !== this.workspace) return;
6501
+ if (!session || resolve13(session.workspace) !== this.workspace) return;
6228
6502
  return toSummary(session);
6229
6503
  })
6230
6504
  );
@@ -6295,7 +6569,7 @@ var SessionStore = class {
6295
6569
  for (const candidate of candidates) {
6296
6570
  await this.assertManagedFile(candidate.path);
6297
6571
  const session = await tryReadSession(candidate.path);
6298
- if (session?.id === id && resolve12(session.workspace) === this.workspace) return session;
6572
+ if (session?.id === id && resolve13(session.workspace) === this.workspace) return session;
6299
6573
  }
6300
6574
  return void 0;
6301
6575
  }
@@ -6309,7 +6583,7 @@ var SessionStore = class {
6309
6583
  }
6310
6584
  }
6311
6585
  assertWorkspace(session) {
6312
- if (resolve12(session.workspace) !== this.workspace) {
6586
+ if (resolve13(session.workspace) !== this.workspace) {
6313
6587
  throw new Error("Stored session belongs to a different workspace.");
6314
6588
  }
6315
6589
  return session;
@@ -6368,7 +6642,7 @@ function createSession(options) {
6368
6642
  return {
6369
6643
  id,
6370
6644
  title: cleanTitle(options.title ?? "New session"),
6371
- workspace: resolve12(options.workspace),
6645
+ workspace: resolve13(options.workspace),
6372
6646
  createdAt: now,
6373
6647
  updatedAt: now,
6374
6648
  model: options.model,
@@ -6423,9 +6697,9 @@ async function exists2(path) {
6423
6697
  }
6424
6698
 
6425
6699
  // src/session/tool-artifacts.ts
6426
- import { createHash as createHash8 } from "node:crypto";
6700
+ import { createHash as createHash9 } from "node:crypto";
6427
6701
  import { lstat as lstat12, readFile as readFile7, readdir as readdir4, rm as rm2 } from "node:fs/promises";
6428
- import { join as join11, resolve as resolve13 } from "node:path";
6702
+ import { join as join11, resolve as resolve14 } from "node:path";
6429
6703
  import { z as z6 } from "zod";
6430
6704
  var MAX_ARTIFACT_BYTES = 5 * 1024 * 1024;
6431
6705
  var MAX_TOTAL_BYTES = 20 * 1024 * 1024;
@@ -6455,9 +6729,9 @@ var ToolArtifactStore = class {
6455
6729
  retentionMs;
6456
6730
  writes = Promise.resolve();
6457
6731
  constructor(workspace, options = {}) {
6458
- this.workspace = resolve13(workspace);
6732
+ this.workspace = resolve14(workspace);
6459
6733
  this.managedDirectory = options.directory === void 0;
6460
- this.directory = options.directory ? resolve13(options.directory) : join11(resolveProjectNamespaceSync(this.workspace).active, "tool-artifacts");
6734
+ this.directory = options.directory ? resolve14(options.directory) : join11(resolveProjectNamespaceSync(this.workspace).active, "tool-artifacts");
6461
6735
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
6462
6736
  this.maxArtifactBytes = options.maxArtifactBytes ?? MAX_ARTIFACT_BYTES;
6463
6737
  this.maxTotalBytes = options.maxTotalBytes ?? MAX_TOTAL_BYTES;
@@ -6514,7 +6788,7 @@ var ToolArtifactStore = class {
6514
6788
  }
6515
6789
  if (retainedBytes + storageBytes > this.maxTotalBytes) return { stored: false, reason: "total_limit" };
6516
6790
  const path = this.pathFor(sessionId, toolCallId);
6517
- await ensureWorkspaceStorageDirectory(this.workspace, resolve13(path, ".."), {
6791
+ await ensureWorkspaceStorageDirectory(this.workspace, resolve14(path, ".."), {
6518
6792
  requireActiveNamespace: this.managedDirectory
6519
6793
  });
6520
6794
  await atomicWrite(path, `${JSON.stringify(artifact)}
@@ -6665,7 +6939,7 @@ var ToolArtifactStore = class {
6665
6939
  }
6666
6940
  }
6667
6941
  async assertRegularFile(path) {
6668
- await assertNoSymlinkPath(this.workspace, resolve13(path, ".."));
6942
+ await assertNoSymlinkPath(this.workspace, resolve14(path, ".."));
6669
6943
  try {
6670
6944
  const info = await lstat12(path);
6671
6945
  if (info.isSymbolicLink() || !info.isFile()) {
@@ -6702,7 +6976,7 @@ function reference(artifact) {
6702
6976
  };
6703
6977
  }
6704
6978
  function hash2(value) {
6705
- return createHash8("sha256").update(value).digest("hex");
6979
+ return createHash9("sha256").update(value).digest("hex");
6706
6980
  }
6707
6981
  function storedBytes(artifact) {
6708
6982
  return Buffer.byteLength(JSON.stringify(artifact)) + 1;
@@ -7249,48 +7523,6 @@ var knownCommands = /* @__PURE__ */ new Set([
7249
7523
  ...networkCommands,
7250
7524
  ...workspaceMutationCommands
7251
7525
  ]);
7252
- var unsafeInheritedGitEnvironment = [
7253
- "GIT_DIR",
7254
- "GIT_WORK_TREE",
7255
- "GIT_COMMON_DIR",
7256
- "GIT_INDEX_FILE",
7257
- "GIT_OBJECT_DIRECTORY",
7258
- "GIT_ALTERNATE_OBJECT_DIRECTORIES",
7259
- "GIT_NAMESPACE",
7260
- "GIT_CEILING_DIRECTORIES",
7261
- "GIT_DISCOVERY_ACROSS_FILESYSTEM",
7262
- "GIT_EXTERNAL_DIFF",
7263
- "GIT_DIFF_OPTS",
7264
- "GIT_SSH",
7265
- "GIT_SSH_COMMAND",
7266
- "GIT_PROXY_COMMAND",
7267
- "GIT_ASKPASS",
7268
- "SSH_ASKPASS",
7269
- "GIT_EXEC_PATH",
7270
- "GIT_TEMPLATE_DIR",
7271
- "GIT_CONFIG",
7272
- "GIT_CONFIG_PARAMETERS",
7273
- "GIT_QUARANTINE_PATH",
7274
- "Path"
7275
- ];
7276
- var isolatedGitEnvironment = {
7277
- GIT_TERMINAL_PROMPT: "0",
7278
- GIT_CONFIG_NOSYSTEM: "1",
7279
- GIT_CONFIG_GLOBAL: "/dev/null",
7280
- GIT_CONFIG_SYSTEM: "/dev/null",
7281
- GIT_PAGER: "cat",
7282
- GIT_EDITOR: "true",
7283
- GIT_SEQUENCE_EDITOR: "true",
7284
- GIT_CONFIG_COUNT: "4",
7285
- GIT_CONFIG_KEY_0: "core.hooksPath",
7286
- GIT_CONFIG_VALUE_0: "/dev/null",
7287
- GIT_CONFIG_KEY_1: "core.fsmonitor",
7288
- GIT_CONFIG_VALUE_1: "false",
7289
- GIT_CONFIG_KEY_2: "credential.helper",
7290
- GIT_CONFIG_VALUE_2: "",
7291
- GIT_CONFIG_KEY_3: "protocol.ext.allow",
7292
- GIT_CONFIG_VALUE_3: "never"
7293
- };
7294
7526
  var diffCommands = /* @__PURE__ */ new Set(["diff", "log", "show", "whatchanged"]);
7295
7527
  var gitTool = {
7296
7528
  definition: {
@@ -7338,13 +7570,9 @@ var gitTool = {
7338
7570
  }
7339
7571
  const runtime = await resolveExecutableRuntime("git", cwd, context.workspace.roots);
7340
7572
  if (!runtime) throw new Error("Git executable was not found outside the configured workspace roots.");
7341
- const status = await runProcess(runtime.executable, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
7342
- cwd,
7573
+ const status = await runIsolatedGit(runtime, ["status", "--porcelain=v1", "-z", "--untracked-files=all"], cwd, {
7343
7574
  timeoutMs: 15e3,
7344
- maxOutputBytes: 2e6,
7345
- env: { ...isolatedGitEnvironment, PATH: runtime.path },
7346
- unsetEnv: unsafeInheritedGitEnvironment,
7347
- unsetEnvPrefixes: ["GIT_"]
7575
+ maxOutputBytes: 2e6
7348
7576
  });
7349
7577
  if (status.exitCode === 0) {
7350
7578
  for (const record of status.stdout.split("\0")) {
@@ -7371,15 +7599,11 @@ var gitTool = {
7371
7599
  const runtime = await resolveExecutableRuntime("git", cwd, context.workspace.roots);
7372
7600
  if (!runtime) throw new Error("Git executable was not found outside the configured workspace roots.");
7373
7601
  const before = worktreeTrackingCommands.has(command2) ? await captureGitState(runtime, cwd) : void 0;
7374
- const result = await runProcess(runtime.executable, protectedGitArguments(input2.args, command2), {
7375
- cwd,
7602
+ const result = await runIsolatedGit(runtime, protectedGitArguments(input2.args, command2), cwd, {
7376
7603
  timeoutMs: input2.timeout_ms ?? 12e4,
7377
7604
  maxOutputBytes: 2e6,
7378
7605
  ...input2.stdin !== void 0 ? { stdin: input2.stdin } : {},
7379
- ...context.signal ? { signal: context.signal } : {},
7380
- env: { ...isolatedGitEnvironment, PATH: runtime.path },
7381
- unsetEnv: unsafeInheritedGitEnvironment,
7382
- unsetEnvPrefixes: ["GIT_"]
7606
+ ...context.signal ? { signal: context.signal } : {}
7383
7607
  });
7384
7608
  const changedFiles = before ? await collectGitChanges(before, await captureGitState(runtime, cwd), runtime, cwd, context) : [];
7385
7609
  return {
@@ -7457,18 +7681,6 @@ function parsePorcelainStatus(output2) {
7457
7681
  }
7458
7682
  return status;
7459
7683
  }
7460
- function runIsolatedGit(runtime, args, cwd, options = {}) {
7461
- return runProcess(runtime.executable, args, {
7462
- cwd,
7463
- timeoutMs: options.timeoutMs ?? 15e3,
7464
- maxOutputBytes: options.maxOutputBytes ?? 2e6,
7465
- env: { ...isolatedGitEnvironment, PATH: runtime.path },
7466
- unsetEnv: unsafeInheritedGitEnvironment,
7467
- unsetEnvPrefixes: ["GIT_"],
7468
- ...options.stdin !== void 0 ? { stdin: options.stdin } : {},
7469
- ...options.signal ? { signal: options.signal } : {}
7470
- });
7471
- }
7472
7684
  function validateGitArguments(args) {
7473
7685
  for (const argument of args) {
7474
7686
  if (argument.includes("\0") || argument.includes("\n") || argument.includes("\r")) {
@@ -7604,7 +7816,7 @@ function positionalArguments(args, command2) {
7604
7816
 
7605
7817
  // src/tools/list.ts
7606
7818
  import { lstat as lstat14 } from "node:fs/promises";
7607
- import { relative as relative7, resolve as resolve14 } from "node:path";
7819
+ import { relative as relative7, resolve as resolve15 } from "node:path";
7608
7820
  import fg3 from "fast-glob";
7609
7821
  import { z as z9 } from "zod";
7610
7822
  var inputSchema4 = z9.object({
@@ -7662,7 +7874,7 @@ var listFilesTool = {
7662
7874
  for (const path of selected) {
7663
7875
  let safePath;
7664
7876
  try {
7665
- safePath = await context.workspace.resolvePath(resolve14(directory, path), { expect: "any" });
7877
+ safePath = await context.workspace.resolvePath(resolve15(directory, path), { expect: "any" });
7666
7878
  } catch {
7667
7879
  continue;
7668
7880
  }
@@ -7872,7 +8084,7 @@ function assertToolName(name) {
7872
8084
 
7873
8085
  // src/tools/search.ts
7874
8086
  import { readFile as readFile10, stat as stat8 } from "node:fs/promises";
7875
- import { resolve as resolve15 } from "node:path";
8087
+ import { resolve as resolve16 } from "node:path";
7876
8088
  import fg4 from "fast-glob";
7877
8089
  import { z as z12 } from "zod";
7878
8090
  var inputSchema7 = z12.object({
@@ -7942,7 +8154,7 @@ var searchCodeTool = {
7942
8154
  if (results.length >= maxResults) break;
7943
8155
  let path;
7944
8156
  try {
7945
- path = await context.workspace.resolvePath(resolve15(directory, file), { expect: "file" });
8157
+ path = await context.workspace.resolvePath(resolve16(directory, file), { expect: "file" });
7946
8158
  } catch {
7947
8159
  skipped += 1;
7948
8160
  continue;
@@ -8024,7 +8236,7 @@ ${hit.content}`
8024
8236
  }
8025
8237
 
8026
8238
  // src/tools/shell.ts
8027
- import { createHash as createHash9 } from "node:crypto";
8239
+ import { createHash as createHash10 } from "node:crypto";
8028
8240
  import { lstat as lstat15, readFile as readFile11, readdir as readdir5 } from "node:fs/promises";
8029
8241
  import { join as join13 } from "node:path";
8030
8242
  import { z as z13 } from "zod";
@@ -8238,7 +8450,7 @@ async function captureWorkspaceSnapshot(roots) {
8238
8450
  files.set(path, {
8239
8451
  size: info.size,
8240
8452
  mtimeMs: info.mtimeMs,
8241
- hash: createHash9("sha256").update(content).digest("hex")
8453
+ hash: createHash10("sha256").update(content).digest("hex")
8242
8454
  });
8243
8455
  } catch (error) {
8244
8456
  if (error.code !== "ENOENT") complete = false;
@@ -8441,15 +8653,16 @@ function compact(value, limit) {
8441
8653
  }
8442
8654
 
8443
8655
  // src/agent/completion-gate.ts
8444
- import { createHash as createHash11 } from "node:crypto";
8656
+ import { createHash as createHash12 } from "node:crypto";
8657
+ import { isAbsolute as isAbsolute6, resolve as resolve17 } from "node:path";
8445
8658
 
8446
8659
  // src/tools/permissions.ts
8447
- import { createHash as createHash10, createHmac, randomBytes } from "node:crypto";
8660
+ import { createHash as createHash11, createHmac, randomBytes } from "node:crypto";
8448
8661
  var permissionScopeSecret = randomBytes(32);
8449
8662
  function permissionKey(call, category) {
8450
8663
  const categoryScope = category === "network" ? `\0network-request:${scopeFingerprint(normalizeRequestState(call.arguments))}` : "";
8451
- const digest = createHash10("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
8452
- return `${category}:${call.name}:${digest}`;
8664
+ const digest2 = createHash11("sha256").update(`${category}\0${call.name}\0${permissionTarget(call)}${categoryScope}`).digest("hex").slice(0, 24);
8665
+ return `${category}:${call.name}:${digest2}`;
8453
8666
  }
8454
8667
  function permissionTarget(call) {
8455
8668
  const command2 = commandForCall(call);
@@ -8642,9 +8855,40 @@ function captureVerification(call, result, changeSequence, configuredCommands) {
8642
8855
  kind,
8643
8856
  ok: result.ok,
8644
8857
  changeSequence,
8645
- commandKey: createHash11("sha256").update(normalized).digest("hex")
8858
+ commandKey: createHash12("sha256").update(normalized).digest("hex")
8646
8859
  };
8647
8860
  }
8861
+ function verificationDiagnosticPaths(result, workspaceRoots) {
8862
+ if (result.ok || result.metadata?.sourceTruncated === true || typeof result.metadata?.exitCode !== "number" || result.metadata.exitCode === 0) return [];
8863
+ const roots = workspaceRoots.map((root) => resolve17(root));
8864
+ const cwd = typeof result.metadata.cwd === "string" ? resolve17(result.metadata.cwd) : void 0;
8865
+ const bases = cwd ? [cwd, ...roots] : roots;
8866
+ const candidates = /* @__PURE__ */ new Set();
8867
+ const pathPattern = String.raw`((?:[./][^\s():]+|[^\s():]+)\.[A-Za-z0-9]{1,10})`;
8868
+ const patterns = [
8869
+ new RegExp(`${pathPattern}\\(\\d+,\\d+\\):\\s*(?:error|warning|fatal)\\b`, "gimu"),
8870
+ new RegExp(`${pathPattern}:\\d+:\\d+:\\s*(?:error|warning|fatal)\\b`, "gimu"),
8871
+ new RegExp(`^\\s*(?:FAIL|ERROR)\\s+${pathPattern}`, "gimu")
8872
+ ];
8873
+ const output2 = result.content.slice(0, 2e5).split(/\r?\n/u).filter((line) => !/^\s*Command:/u.test(line)).join("\n");
8874
+ for (const pattern of patterns) {
8875
+ for (const match of output2.matchAll(pattern)) {
8876
+ const candidate = match[1];
8877
+ if (!candidate) continue;
8878
+ const path = resolveDiagnosticPath(candidate, bases, roots);
8879
+ if (path) candidates.add(path);
8880
+ if (candidates.size >= 16) return [...candidates];
8881
+ }
8882
+ }
8883
+ return [...candidates];
8884
+ }
8885
+ function resolveDiagnosticPath(candidate, bases, roots) {
8886
+ for (const base of bases) {
8887
+ const path = isAbsolute6(candidate) ? resolve17(candidate) : resolve17(base, candidate);
8888
+ if (roots.some((root) => isInside(root, path))) return path;
8889
+ }
8890
+ return void 0;
8891
+ }
8648
8892
  function buildRunCompletion(changedFiles, evidence, currentChangeSequence, mutationTracking = "complete", taskContract, audit = [], duplication) {
8649
8893
  const files = [...new Set(changedFiles)];
8650
8894
  const acceptance = taskContract ? buildAcceptance(taskContract, audit, evidence, currentChangeSequence) : void 0;
@@ -8826,7 +9070,7 @@ function verificationRequirementMet(requirement, checks) {
8826
9070
  const normalized = normalizeCommand2(requirement);
8827
9071
  const broad = normalized.match(/^Record at least one successful (.+) after the final mutation\.?$/iu);
8828
9072
  if (broad) return checks.length > 0;
8829
- const commandKey = createHash11("sha256").update(normalized).digest("hex");
9073
+ const commandKey = createHash12("sha256").update(normalized).digest("hex");
8830
9074
  return checks.some((item) => item.commandKey === commandKey);
8831
9075
  }
8832
9076
  function acceptanceUnresolved(acceptance) {
@@ -9420,7 +9664,7 @@ function escapeAttribute3(value) {
9420
9664
  }
9421
9665
 
9422
9666
  // src/agent/tool-recovery.ts
9423
- import { createHash as createHash12 } from "node:crypto";
9667
+ import { createHash as createHash13 } from "node:crypto";
9424
9668
  var RETRY_BUDGET = {
9425
9669
  schema_input: 3,
9426
9670
  unknown_tool: 2,
@@ -9492,7 +9736,7 @@ var ToolRecoveryController = class {
9492
9736
  recordEvidence(call, result) {
9493
9737
  if (call.name !== "search_code" || !result.ok) return void 0;
9494
9738
  const callKey = callSignature(call);
9495
- const fingerprint = createHash12("sha256").update(`${result.content}\0${stableJson(result.metadata ?? {})}`).digest("hex");
9739
+ const fingerprint = createHash13("sha256").update(`${result.content}\0${stableJson(result.metadata ?? {})}`).digest("hex");
9496
9740
  const count = typeof result.metadata?.count === "number" ? result.metadata.count : void 0;
9497
9741
  const current = this.evidence.get(callKey);
9498
9742
  const repeated = current?.fingerprint === fingerprint;
@@ -9502,7 +9746,7 @@ var ToolRecoveryController = class {
9502
9746
  status: count === 0 ? "empty" : repeated ? "repeated" : "new",
9503
9747
  repeatCount: repeats,
9504
9748
  stop: repeats >= 2,
9505
- signature: createHash12("sha256").update(`${callKey}\0${fingerprint}`).digest("hex")
9749
+ signature: createHash13("sha256").update(`${callKey}\0${fingerprint}`).digest("hex")
9506
9750
  };
9507
9751
  }
9508
9752
  receipt(call, failureClass, attempt, circuitOpen) {
@@ -9538,10 +9782,10 @@ function isRetryable(failureClass) {
9538
9782
  return RETRY_BUDGET[failureClass] > 0;
9539
9783
  }
9540
9784
  function callSignature(call) {
9541
- return createHash12("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
9785
+ return createHash13("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}`).digest("hex");
9542
9786
  }
9543
9787
  function failureSignature(call, failureClass) {
9544
- return createHash12("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
9788
+ return createHash13("sha256").update(`${call.name}\0${stableJson(redact(call.arguments))}\0${failureClass}`).digest("hex");
9545
9789
  }
9546
9790
  function redact(value, key = "") {
9547
9791
  if (/api[_-]?key|authorization|cookie|password|secret|token/i.test(key)) return "[redacted]";
@@ -9889,7 +10133,7 @@ function escapeAttribute5(value) {
9889
10133
  }
9890
10134
 
9891
10135
  // src/agent/reuse-gate.ts
9892
- import { createHash as createHash13 } from "node:crypto";
10136
+ import { createHash as createHash14 } from "node:crypto";
9893
10137
  import { readFile as readFile14, stat as stat10 } from "node:fs/promises";
9894
10138
  import { basename as basename8, extname as extname3 } from "node:path";
9895
10139
  var MAX_CANDIDATES = 5;
@@ -10098,14 +10342,14 @@ function unresolvedReceipt(input2, queryHash, targetPaths, trigger, detail) {
10098
10342
  };
10099
10343
  }
10100
10344
  function hash3(value) {
10101
- return createHash13("sha256").update(value).digest("hex");
10345
+ return createHash14("sha256").update(value).digest("hex");
10102
10346
  }
10103
10347
  function roundScore2(value) {
10104
10348
  return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : 0;
10105
10349
  }
10106
10350
 
10107
10351
  // src/agent/duplication-audit.ts
10108
- import { createHash as createHash14 } from "node:crypto";
10352
+ import { createHash as createHash15 } from "node:crypto";
10109
10353
  import { readFile as readFile15 } from "node:fs/promises";
10110
10354
  var DEFAULT_NEAR_CLONE_THRESHOLD = 0.55;
10111
10355
  var MAX_MATCHES = 8;
@@ -10280,7 +10524,7 @@ function round(value) {
10280
10524
  return Math.round(value * 1e3) / 1e3;
10281
10525
  }
10282
10526
  function matchId(generation, changeSequence, changed, candidate, similarity) {
10283
- return createHash14("sha256").update([
10527
+ return createHash15("sha256").update([
10284
10528
  generation,
10285
10529
  String(changeSequence),
10286
10530
  changed.path,
@@ -10357,6 +10601,7 @@ var AgentRunner = class {
10357
10601
  throw new Error("User input is too large; pass a focused request or attach files with @path.");
10358
10602
  }
10359
10603
  this.running = true;
10604
+ this.contextEngine.resetDiagnostics?.();
10360
10605
  const emit = async (event) => {
10361
10606
  await options.onEvent?.(event);
10362
10607
  };
@@ -10383,7 +10628,13 @@ var AgentRunner = class {
10383
10628
  this.changeSequence,
10384
10629
  this.config.agent.verifyCommands
10385
10630
  );
10386
- if (evidence) verificationEvidence.push(evidence);
10631
+ if (evidence) {
10632
+ verificationEvidence.push(evidence);
10633
+ this.contextEngine.recordDiagnostics?.({
10634
+ commandKey: evidence.commandKey,
10635
+ paths: verificationDiagnosticPaths(result, this.workspace.roots)
10636
+ });
10637
+ }
10387
10638
  };
10388
10639
  const completionReport = () => {
10389
10640
  const completion = buildRunCompletion(
@@ -11698,9 +11949,9 @@ function numeric(...values) {
11698
11949
  }
11699
11950
 
11700
11951
  // src/agent/team-store.ts
11701
- import { createHash as createHash15, randomUUID as randomUUID13 } from "node:crypto";
11952
+ import { createHash as createHash16, randomUUID as randomUUID13 } from "node:crypto";
11702
11953
  import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
11703
- import { join as join16, resolve as resolve16 } from "node:path";
11954
+ import { join as join16, resolve as resolve18 } from "node:path";
11704
11955
  import { z as z18 } from "zod";
11705
11956
  var runIdSchema = z18.string().uuid();
11706
11957
  var hashSchema = z18.string().regex(/^[a-f0-9]{64}$/u);
@@ -11784,9 +12035,9 @@ var TeamRunStore = class {
11784
12035
  managedDirectory;
11785
12036
  writes = Promise.resolve();
11786
12037
  constructor(workspace, directory) {
11787
- this.workspace = resolve16(workspace);
12038
+ this.workspace = resolve18(workspace);
11788
12039
  this.managedDirectory = directory === void 0;
11789
- this.directory = directory ? resolve16(directory) : join16(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
12040
+ this.directory = directory ? resolve18(directory) : join16(resolveProjectNamespaceSync(this.workspace).active, "team-runs");
11790
12041
  }
11791
12042
  async create(input2) {
11792
12043
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -11873,7 +12124,7 @@ var TeamRunStore = class {
11873
12124
  const path = join16(this.runDirectory(runId), "manifest.json");
11874
12125
  await this.assertRegularFile(path);
11875
12126
  const manifest = manifestSchema2.parse(JSON.parse(await readFile17(path, "utf8")));
11876
- if (manifest.id !== runId || resolve16(manifest.workspace) !== this.workspace) {
12127
+ if (manifest.id !== runId || resolve18(manifest.workspace) !== this.workspace) {
11877
12128
  throw new Error("Team run manifest identity does not match its location.");
11878
12129
  }
11879
12130
  if (verify) {
@@ -11956,7 +12207,7 @@ var TeamRunStore = class {
11956
12207
  async writeArtifact(runId, content, truncate = true) {
11957
12208
  const data = boundedArtifactText(content, 5e5, truncate);
11958
12209
  const bytes = Buffer.byteLength(data);
11959
- const sha256 = createHash15("sha256").update(data).digest("hex");
12210
+ const sha256 = createHash16("sha256").update(data).digest("hex");
11960
12211
  const directory = join16(this.runDirectory(runId), "blobs");
11961
12212
  await ensureWorkspaceStorageDirectory(this.workspace, directory, {
11962
12213
  requireActiveNamespace: this.managedDirectory
@@ -11965,7 +12216,7 @@ var TeamRunStore = class {
11965
12216
  try {
11966
12217
  await this.assertRegularFile(path);
11967
12218
  const existing = await readFile17(path);
11968
- if (createHash15("sha256").update(existing).digest("hex") !== sha256) {
12219
+ if (createHash16("sha256").update(existing).digest("hex") !== sha256) {
11969
12220
  throw new Error(`Team artifact hash collision or corruption: ${sha256}`);
11970
12221
  }
11971
12222
  } catch (error) {
@@ -11986,7 +12237,7 @@ var TeamRunStore = class {
11986
12237
  const path = this.artifactPath(runId, artifact.sha256);
11987
12238
  await this.assertRegularFile(path);
11988
12239
  const data = await readFile17(path);
11989
- const hash4 = createHash15("sha256").update(data).digest("hex");
12240
+ const hash4 = createHash16("sha256").update(data).digest("hex");
11990
12241
  if (hash4 !== artifact.sha256 || data.byteLength !== artifact.bytes) {
11991
12242
  throw new Error(`Team artifact integrity check failed: ${artifact.sha256}`);
11992
12243
  }
@@ -12004,7 +12255,7 @@ var TeamRunStore = class {
12004
12255
  if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Team run storage is not a regular directory.");
12005
12256
  }
12006
12257
  async assertRegularFile(path) {
12007
- await assertNoSymlinkPath(this.workspace, resolve16(path, ".."));
12258
+ await assertNoSymlinkPath(this.workspace, resolve18(path, ".."));
12008
12259
  const info = await lstat18(path);
12009
12260
  if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Team run file is not a regular file: ${path}`);
12010
12261
  }
@@ -12049,10 +12300,10 @@ function resolveAgentModelRoute(team, parent, profile) {
12049
12300
  }
12050
12301
 
12051
12302
  // src/agent/writer-lane.ts
12052
- import { createHash as createHash16 } from "node:crypto";
12303
+ import { createHash as createHash17 } from "node:crypto";
12053
12304
  import { lstat as lstat19, mkdtemp, realpath as realpath7, rm as rm4 } from "node:fs/promises";
12054
12305
  import { tmpdir as tmpdir2 } from "node:os";
12055
- import { isAbsolute as isAbsolute6, join as join17, resolve as resolve17 } from "node:path";
12306
+ import { isAbsolute as isAbsolute7, join as join17, resolve as resolve19 } from "node:path";
12056
12307
  var WriterLaneApplyError = class extends Error {
12057
12308
  constructor(message2, attempted, cause) {
12058
12309
  super(message2, cause === void 0 ? void 0 : { cause });
@@ -12065,8 +12316,8 @@ var WriterLane = class {
12065
12316
  workspace;
12066
12317
  constructor(workspace, workspaceRoots) {
12067
12318
  this.workspace = new WorkspaceAccess([
12068
- resolve17(workspace),
12069
- ...workspaceRoots.map((root) => resolve17(root))
12319
+ resolve19(workspace),
12320
+ ...workspaceRoots.map((root) => resolve19(root))
12070
12321
  ]);
12071
12322
  }
12072
12323
  async createDraft(maxPatchBytes, operation, signal) {
@@ -12147,7 +12398,7 @@ var WriterLane = class {
12147
12398
  return {
12148
12399
  baseCommit,
12149
12400
  patch,
12150
- patchSha256: createHash16("sha256").update(patch).digest("hex"),
12401
+ patchSha256: createHash17("sha256").update(patch).digest("hex"),
12151
12402
  files,
12152
12403
  worktreeCleaned,
12153
12404
  value
@@ -12271,7 +12522,7 @@ var WriterLane = class {
12271
12522
  const files = unique2(parseNumstatPaths(numstat.stdout));
12272
12523
  if (!files.length) throw new Error("Writer patch contains no file changes.");
12273
12524
  for (const file of files) {
12274
- if (isAbsolute6(file) || file === ".git" || file.startsWith(".git/") || file.includes("\0")) {
12525
+ if (isAbsolute7(file) || file === ".git" || file.startsWith(".git/") || file.includes("\0")) {
12275
12526
  throw new Error(`Writer patch contains an unsafe path: ${file}`);
12276
12527
  }
12277
12528
  await this.workspace.resolvePath(join17(repository.root, file), { allowMissing: true });
@@ -12289,7 +12540,7 @@ var WriterLane = class {
12289
12540
  if (topLevel.exitCode !== 0 || !topLevel.stdout.trim()) {
12290
12541
  throw new Error("The primary workspace must be a Git repository root for writer lanes.");
12291
12542
  }
12292
- const discoveredRoot = resolve17(topLevel.stdout.trim());
12543
+ const discoveredRoot = resolve19(topLevel.stdout.trim());
12293
12544
  if (await realpath7(discoveredRoot) !== await realpath7(root)) {
12294
12545
  throw new Error("The primary workspace must be the Git repository root for writer lanes.");
12295
12546
  }
@@ -12298,7 +12549,7 @@ var WriterLane = class {
12298
12549
  if (common.exitCode !== 0 || !common.stdout.trim()) {
12299
12550
  throw new Error(`Unable to resolve the Git common directory: ${processDetail(common)}`);
12300
12551
  }
12301
- const commonDirectory = await realpath7(isAbsolute6(common.stdout.trim()) ? common.stdout.trim() : resolve17(repositoryRoot, common.stdout.trim()));
12552
+ const commonDirectory = await realpath7(isAbsolute7(common.stdout.trim()) ? common.stdout.trim() : resolve19(repositoryRoot, common.stdout.trim()));
12302
12553
  return { root: repositoryRoot, commonDirectory, runtime };
12303
12554
  }
12304
12555
  async head(repository) {
@@ -12310,7 +12561,7 @@ var WriterLane = class {
12310
12561
  return value;
12311
12562
  }
12312
12563
  async cleanupWorktree(repository, worktree, added) {
12313
- const physicalWorktree = await realpath7(worktree).catch(() => resolve17(worktree));
12564
+ const physicalWorktree = await realpath7(worktree).catch(() => resolve19(worktree));
12314
12565
  if (added) {
12315
12566
  await runIsolatedGit(repository.runtime, [
12316
12567
  "worktree",
@@ -14236,7 +14487,7 @@ function formatResultDetail(result, paint = chalk2, glyphs = resolveCliGlyphs())
14236
14487
  }
14237
14488
 
14238
14489
  // src/cli/namespace-leases.ts
14239
- import { resolve as resolve18 } from "node:path";
14490
+ import { resolve as resolve20 } from "node:path";
14240
14491
  function cliNamespaceLeaseScopes(actionCommand) {
14241
14492
  const names = commandNames(actionCommand);
14242
14493
  const topLevel = names[1];
@@ -14258,7 +14509,7 @@ async function acquireCliNamespaceLeases(actionCommand) {
14258
14509
  const scopes = cliNamespaceLeaseScopes(actionCommand);
14259
14510
  const localOptions = actionCommand.opts();
14260
14511
  const globalOptions = actionCommand.optsWithGlobals();
14261
- const workspace = resolve18(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
14512
+ const workspace = resolve20(localOptions.workspace ?? globalOptions.workspace ?? process.cwd());
14262
14513
  const targets = scopes.map((scope) => scope === "project" ? projectNamespacePaths(workspace).canonical : homeNamespacePaths().canonical);
14263
14514
  const leases = [];
14264
14515
  try {
@@ -14510,7 +14761,7 @@ function graphemes(value) {
14510
14761
 
14511
14762
  // src/ui/theme.ts
14512
14763
  import { lstat as lstat20, readdir as readdir8, readFile as readFile18 } from "node:fs/promises";
14513
- import { basename as basename10, join as join19, resolve as resolve19 } from "node:path";
14764
+ import { basename as basename10, join as join19, resolve as resolve21 } from "node:path";
14514
14765
  import React, { createContext, useContext } from "react";
14515
14766
  function defineTheme(seed) {
14516
14767
  return {
@@ -14632,7 +14883,7 @@ async function reloadUserThemes(directory = userThemeDirectory()) {
14632
14883
  userThemeNames.clear();
14633
14884
  const loaded = [];
14634
14885
  const errors = [];
14635
- const resolvedDirectory = resolve19(directory);
14886
+ const resolvedDirectory = resolve21(directory);
14636
14887
  let entries;
14637
14888
  try {
14638
14889
  entries = await readdir8(resolvedDirectory, { encoding: "utf8" });
@@ -17096,7 +17347,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17096
17347
  return () => clearInterval(timer);
17097
17348
  }, [busy]);
17098
17349
  const requestPermission = useCallback((call, category) => {
17099
- return new Promise((resolve24) => setPermission({ call, category, resolve: resolve24 }));
17350
+ return new Promise((resolve26) => setPermission({ call, category, resolve: resolve26 }));
17100
17351
  }, []);
17101
17352
  const onEvent = useCallback((event) => {
17102
17353
  switch (event.type) {
@@ -17932,8 +18183,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17932
18183
  }, [append]);
17933
18184
  function settlePermission(grant, stop = false) {
17934
18185
  if (!permission) return;
17935
- const { call, category, resolve: resolve24 } = permission;
17936
- resolve24(grant);
18186
+ const { call, category, resolve: resolve26 } = permission;
18187
+ resolve26(grant);
17937
18188
  setPermission(void 0);
17938
18189
  if (grant === "session") {
17939
18190
  append({
@@ -19268,7 +19519,7 @@ async function runFirstRunOnboarding(initialConfig, options = {}) {
19268
19519
  }
19269
19520
 
19270
19521
  // src/runtime/extensions.ts
19271
- import { resolve as resolve22 } from "node:path";
19522
+ import { resolve as resolve24 } from "node:path";
19272
19523
 
19273
19524
  // src/mcp/manager.ts
19274
19525
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -19277,7 +19528,7 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
19277
19528
  import stripAnsi5 from "strip-ansi";
19278
19529
 
19279
19530
  // src/mcp/tool.ts
19280
- import { createHash as createHash17 } from "node:crypto";
19531
+ import { createHash as createHash18 } from "node:crypto";
19281
19532
  import stripAnsi4 from "strip-ansi";
19282
19533
  var MAX_ARGUMENT_BYTES = 256e3;
19283
19534
  var MAX_DESCRIPTION_LENGTH = 4e3;
@@ -19448,7 +19699,7 @@ function fitToolName(name, identity2) {
19448
19699
  return `${name.slice(0, 64 - suffix.length).replace(/_+$/g, "")}${suffix}`;
19449
19700
  }
19450
19701
  function shortHash(value) {
19451
- return createHash17("sha256").update(value).digest("hex").slice(0, 8);
19702
+ return createHash18("sha256").update(value).digest("hex").slice(0, 8);
19452
19703
  }
19453
19704
  function sanitizeOutputText(value) {
19454
19705
  return stripAnsi4(value).replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "");
@@ -19481,7 +19732,7 @@ function isRecord2(value) {
19481
19732
 
19482
19733
  // src/mcp/validation.ts
19483
19734
  import { stat as stat11, realpath as realpath8 } from "node:fs/promises";
19484
- import { isAbsolute as isAbsolute7, resolve as resolve20 } from "node:path";
19735
+ import { isAbsolute as isAbsolute8, resolve as resolve22 } from "node:path";
19485
19736
  var SERVER_NAME = /^[a-z][a-z0-9_-]{0,63}$/;
19486
19737
  var ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
19487
19738
  var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
@@ -19553,9 +19804,9 @@ async function validateCwd(configured, options) {
19553
19804
  if (configured !== void 0 && (configured.length > 4e3 || CONTROL_CHARACTERS.test(configured))) {
19554
19805
  throw new Error("MCP working directory is invalid or too long");
19555
19806
  }
19556
- const defaultCwd = resolve20(options.cwd ?? process.cwd());
19557
- const candidate = configured ? isAbsolute7(configured) ? resolve20(configured) : resolve20(defaultCwd, configured) : defaultCwd;
19558
- const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) => resolve20(root)) : [defaultCwd];
19807
+ const defaultCwd = resolve22(options.cwd ?? process.cwd());
19808
+ const candidate = configured ? isAbsolute8(configured) ? resolve22(configured) : resolve22(defaultCwd, configured) : defaultCwd;
19809
+ const roots = options.workspaceRoots?.length ? options.workspaceRoots.map((root) => resolve22(root)) : [defaultCwd];
19559
19810
  const resolvedCandidate = await realpath8(candidate).catch(() => {
19560
19811
  throw new Error(`MCP working directory does not exist: ${candidate}`);
19561
19812
  });
@@ -20209,7 +20460,7 @@ function scopeKey(scope, context) {
20209
20460
  // src/skills/catalog.ts
20210
20461
  import { lstat as lstat21, readFile as readFile20, readdir as readdir9, realpath as realpath9 } from "node:fs/promises";
20211
20462
  import { homedir as homedir4 } from "node:os";
20212
- import { basename as basename12, join as join21, resolve as resolve21 } from "node:path";
20463
+ import { basename as basename12, join as join21, resolve as resolve23 } from "node:path";
20213
20464
  import { parse as parseYaml3 } from "yaml";
20214
20465
  var SkillCatalog = class {
20215
20466
  constructor(workspace, config) {
@@ -20278,14 +20529,14 @@ ${skill.content}
20278
20529
  }
20279
20530
  function discoveryLocations(workspace, configured) {
20280
20531
  const home = homedir4();
20281
- const workspaceRoot = resolve21(workspace);
20532
+ const workspaceRoot = resolve23(workspace);
20282
20533
  return [
20283
20534
  { path: join21(home, ".agents", "skills"), scope: "user", trusted: true },
20284
20535
  { path: join21(home, ".claude", "skills"), scope: "user", trusted: true },
20285
20536
  { path: join21(home, ".augment", "skills"), scope: "user", trusted: true },
20286
20537
  { path: join21(resolveHomeNamespace(), "skills"), scope: "user", trusted: true },
20287
20538
  ...configured.map((path) => {
20288
- const resolved = resolve21(workspaceRoot, path);
20539
+ const resolved = resolve23(workspaceRoot, path);
20289
20540
  return {
20290
20541
  path: resolved,
20291
20542
  scope: "configured",
@@ -20314,7 +20565,7 @@ async function readMetadata(path) {
20314
20565
  const { frontmatter } = splitFrontmatter(raw);
20315
20566
  if (!frontmatter) return void 0;
20316
20567
  const parsed = parseYaml3(frontmatter);
20317
- const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename12(resolve21(path, ".."));
20568
+ const name = typeof parsed?.name === "string" ? parsed.name.trim() : basename12(resolve23(path, ".."));
20318
20569
  const description = typeof parsed?.description === "string" ? parsed.description.trim() : "";
20319
20570
  if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || !description || description.length > 1e3) {
20320
20571
  return void 0;
@@ -20335,7 +20586,7 @@ async function safeRead(path, maxBytes) {
20335
20586
  try {
20336
20587
  const info = await lstat21(path);
20337
20588
  if (!info.isFile() || info.isSymbolicLink() || info.size > maxBytes) return void 0;
20338
- const parent = resolve21(path, "..");
20589
+ const parent = resolve23(path, "..");
20339
20590
  const resolvedParent = await realpath9(parent);
20340
20591
  const resolvedPath = await realpath9(path);
20341
20592
  if (!resolvedPath.startsWith(`${resolvedParent}/`)) return void 0;
@@ -20520,7 +20771,7 @@ var ExtensionRuntime = class _ExtensionRuntime {
20520
20771
  delegation;
20521
20772
  initialized = false;
20522
20773
  static async create(config, registry, options = {}) {
20523
- const runtime = new _ExtensionRuntime(config, resolve22(config.workspaceRoots[0] ?? process.cwd()), options);
20774
+ const runtime = new _ExtensionRuntime(config, resolve24(config.workspaceRoots[0] ?? process.cwd()), options);
20524
20775
  try {
20525
20776
  await runtime.initialize(registry, options.signal);
20526
20777
  return runtime;
@@ -20725,15 +20976,15 @@ function upgradeCommandOverride(env = process.env) {
20725
20976
  return trimmed ? trimmed : void 0;
20726
20977
  }
20727
20978
  function runUpgrade(plan) {
20728
- return new Promise((resolve24) => {
20979
+ return new Promise((resolve26) => {
20729
20980
  const child = spawn2(plan.command, plan.args, {
20730
20981
  stdio: "inherit",
20731
20982
  shell: plan.shell ?? false
20732
20983
  });
20733
- child.on("error", () => resolve24({ ok: false, exitCode: 127, display: plan.display }));
20984
+ child.on("error", () => resolve26({ ok: false, exitCode: 127, display: plan.display }));
20734
20985
  child.on("close", (code) => {
20735
20986
  const exitCode = code ?? 1;
20736
- resolve24({ ok: exitCode === 0, exitCode, display: plan.display });
20987
+ resolve26({ ok: exitCode === 0, exitCode, display: plan.display });
20737
20988
  });
20738
20989
  });
20739
20990
  }
@@ -20995,7 +21246,7 @@ sessionCommand.command("export <id>").description("Export a session as Markdown"
20995
21246
  const store = new SessionStore(workspaceOption(options.workspace));
20996
21247
  const session = await requireSessionSelector(store, id);
20997
21248
  const markdown = sessionMarkdown(session);
20998
- if (options.output) await writeFile3(resolve23(options.output), markdown, "utf8");
21249
+ if (options.output) await writeFile3(resolve25(options.output), markdown, "utf8");
20999
21250
  else process.stdout.write(markdown);
21000
21251
  });
21001
21252
  var checkpointCommand = program.command("checkpoint").description("Inspect and restore pre-mutation snapshots");
@@ -21372,7 +21623,7 @@ async function runChat(prompts, options) {
21372
21623
  const stdinPrompt = !process.stdin.isTTY ? await readStdin() : "";
21373
21624
  const firstPrompt = [...prompts, stdinPrompt].filter(Boolean).join("\n\n").trim();
21374
21625
  if (shouldPrint && !firstPrompt) throw new Error("Provide a prompt argument or pipe input on stdin.");
21375
- const workspace = resolve23(options.workspace);
21626
+ const workspace = resolve25(options.workspace);
21376
21627
  let config = await runtimeConfig(workspace, options);
21377
21628
  let completedOnboarding = false;
21378
21629
  if (!shouldPrint && needsFirstRunOnboarding(config)) {
@@ -21597,14 +21848,14 @@ async function runAgentSetup(options) {
21597
21848
  `);
21598
21849
  }
21599
21850
  async function runtimeConfig(workspaceInput, options) {
21600
- const workspace = resolve23(workspaceInput);
21851
+ const workspace = resolve25(workspaceInput);
21601
21852
  const loaded = await loadConfig(workspace, options.config, {
21602
21853
  trustProjectConfig: options.trustProjectConfig === true
21603
21854
  });
21604
21855
  const roots = [
21605
21856
  workspace,
21606
21857
  ...loaded.workspaceRoots,
21607
- ...(options.addWorkspace ?? []).map((root) => resolve23(workspace, root))
21858
+ ...(options.addWorkspace ?? []).map((root) => resolve25(workspace, root))
21608
21859
  ];
21609
21860
  const provider = options.provider ? validateProvider(options.provider) : loaded.model.provider;
21610
21861
  return {
@@ -21760,7 +22011,7 @@ function collect(value, previous) {
21760
22011
  }
21761
22012
  function workspaceOption(value) {
21762
22013
  const rootOptions = program.opts();
21763
- return resolve23(value ?? rootOptions.workspace ?? process.cwd());
22014
+ return resolve25(value ?? rootOptions.workspace ?? process.cwd());
21764
22015
  }
21765
22016
  function runtimeOptions(options) {
21766
22017
  const root = program.opts();