@wrongstack/core 0.8.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -115,6 +115,15 @@ declare class WorktreeManager {
115
115
  private detectBaseBranch;
116
116
  private makeSlug;
117
117
  private collectStats;
118
+ /**
119
+ * `git -c user.*` fallback so commits succeed on machines and CI runners
120
+ * that have no global git identity configured. Returns `[]` when both
121
+ * `user.name` and `user.email` are already set (the common case), so a real
122
+ * user's identity is never overridden. The worktree branch commits are
123
+ * squashed away on merge, so the fallback identity never reaches the base
124
+ * branch history.
125
+ */
126
+ private identityArgs;
118
127
  private unmergedFiles;
119
128
  private emitCommitted;
120
129
  private fail;
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ import { S as SystemPromptBuilder, c as ModelCapabilities, a as BuildContext, C
2
2
  export { B as BindOptions, D as Decorator, F as Factory, M as Middleware, b as MiddlewareHandler, N as NextFn, P as Pipeline, d as PipelineOptions, e as Renderer, T as Token } from './system-prompt-CWA6ml-d.js';
3
3
  import { E as EventBus, c as EventName, L as Listener } from './events-yCpJD4wa.js';
4
4
  export { a as EventLogger, b as EventMap, S as ScopedEventBus } from './events-yCpJD4wa.js';
5
- import { d as WorktreeManager } from './index-BvZld4Dr.js';
6
- export { A as AllocateOpts, M as MergeOpts, a as MergeResult, R as RunController, b as RunControllerOptions, T as TOKENS, W as WorktreeHandle, e as WorktreeManagerOptions, c as WorktreeRunResult, f as WorktreeStatus, g as assertSafePath } from './index-BvZld4Dr.js';
5
+ import { d as WorktreeManager } from './index-Dl3PaD7f.js';
6
+ export { A as AllocateOpts, M as MergeOpts, a as MergeResult, R as RunController, b as RunControllerOptions, T as TOKENS, W as WorktreeHandle, e as WorktreeManagerOptions, c as WorktreeRunResult, f as WorktreeStatus, g as assertSafePath } from './index-Dl3PaD7f.js';
7
7
  import { d as Context, m as Provider, Q as Tool, c as ContentBlock, T as TextBlock } from './context-z2x5gv_V.js';
8
8
  export { A as AgentError, a as Capabilities, b as ConfigError, e as ContextInit, f as ConversationState, E as ERROR_CODES, g as ErrorCode, h as ErrorSeverity, i as ErrorSubsystem, F as FileSnapshot, j as FsError, I as ImageBlock, J as JSONSchema, M as Message, k as MessageRole, P as Permission, l as PluginError, n as ProviderError, o as ProviderErrorBody, R as ReadonlyConversationState, p as Request, q as Response, r as ResumedSession, s as RiskTier, t as RunEnv, u as RunOptions, S as SessionData, v as SessionError, w as SessionEvent, x as SessionMetadata, y as SessionStore, z as SessionSummary, B as SessionWriter, D as StateChange, G as StateChangeHandler, H as StopReason, K as StreamEvent, L as ThinkingBlock, N as TodoItem, O as TokenCounter, U as ToolCallContext, V as ToolError, W as ToolFinalEvent, X as ToolProgressEvent, Y as ToolResultBlock, Z as ToolStreamEvent, _ as ToolUseBlock, $ as Usage, a0 as WrongStackError, a1 as asBlocks, a2 as asText, a3 as extractRunEnv, a4 as isAgentError, a5 as isConfigError, a6 as isFsError, a7 as isImageBlock, a8 as isPluginError, a9 as isSessionError, aa as isTextBlock, ab as isThinkingBlock, ac as isToolError, ad as isToolResultBlock, ae as isToolUseBlock, af as isWrongStackError, ag as toWrongStackError, ah as wrapAsState } from './context-z2x5gv_V.js';
9
9
  export { P as ProviderRunner, R as RunProviderOptions } from './provider-runner-C4ouU7--.js';
@@ -36,7 +36,7 @@ export { C as CriticalPathResult, D as DEFAULT_SPEC_TEMPLATE, S as SpecAnalysis,
36
36
  export { A as AggregateHealth, H as HealthCheck, a as HealthCheckResult, b as HealthRegistry, c as HealthStatus, M as MetricLabels, d as MetricSeries, e as MetricsSink, f as MetricsSnapshot, S as Span, T as Tracer } from './observability-BhnVLBLS.js';
37
37
  export { AtomicWriteOptions, BuildChildEnvOptions, CompileFail, CompileResult, MessageRepairReport, MessageRepairResult, NewlineStyle, RequestTokenBreakdown, SafeParseResult, ToolOutputSerializerOptions, UnifiedDiffOptions, ValidationError, ValidationResult, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, createToolOutputSerializer, detectNewlineStyle, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, formatTodosList, matchAny, matchGlob, normalizeToLf, repairToolUseAdjacency, safeParse, safeStringify, sanitizeJsonString, stripAnsi, toStyle, unifiedDiff, validateAgainstSchema } from './utils/index.js';
38
38
  export { W as WstackPathOptions, a as WstackPaths, p as projectHash, r as resolveWstackPaths } from './wstack-paths-CJYrjQU8.js';
39
- export { A as AbandonedSession, a as AttachmentStoreOptions, C as ConfigLoaderOptions, b as ConfigMigration, c as ConfigMigrationError, d as ConfigSource, D as DEFAULT_CONFIG_MIGRATIONS, e as DefaultAttachmentStore, f as DefaultConfigLoader, g as DefaultConfigStore, h as DefaultMemoryStore, i as DefaultSessionStore, M as MemoryStoreOptions, j as MigrationContext, k as MigrationResult, P as PersistedQueueItem, l as PlanFile, m as PlanItem, n as PlanTemplate, Q as QueueStore, R as RecoveryLock, o as RecoveryLockOptions, S as SessionAnalyzer, p as SessionStoreOptions, T as TodosCheckpointFile, q as addPlanItem, r as attachPlanCheckpoint, s as attachTodosCheckpoint, t as clearPlan, u as deriveTodosFromPlanItem, v as emptyPlan, w as formatPlan, x as formatPlanTemplates, y as getPlanTemplate, z as listPlanTemplates, B as loadPlan, E as loadTodosCheckpoint, F as removePlanItem, G as runConfigMigrations, H as savePlan, I as saveTodosCheckpoint, J as setPlanItemStatus } from './plan-templates-Cqdy6rtM.js';
39
+ export { A as AbandonedSession, a as AttachmentStoreOptions, C as ConfigLoaderOptions, b as ConfigMigration, c as ConfigMigrationError, d as ConfigSource, D as DEFAULT_CONFIG_MIGRATIONS, e as DefaultAttachmentStore, f as DefaultConfigLoader, g as DefaultConfigStore, h as DefaultMemoryStore, i as DefaultSessionStore, M as MemoryStoreOptions, j as MigrationContext, k as MigrationResult, P as PersistedQueueItem, l as PlanFile, m as PlanItem, n as PlanTemplate, Q as QueueStore, R as RecoveryLock, o as RecoveryLockOptions, S as SessionAnalyzer, p as SessionStoreOptions, T as TodosCheckpointFile, q as addPlanItem, r as attachPlanCheckpoint, s as attachTodosCheckpoint, t as clearPlan, u as deriveTodosFromPlanItem, v as emptyPlan, w as formatPlan, x as formatPlanTemplates, y as getPlanTemplate, z as listPlanTemplates, B as loadPlan, E as loadTodosCheckpoint, F as removePlanItem, G as runConfigMigrations, H as savePlan, I as saveTodosCheckpoint, J as setPlanItemStatus } from './plan-templates-Cc-04Z8o.js';
40
40
  export { D as DirectorStateCheckpoint, a as DirectorStateSnapshot, b as DirectorSubagentState, c as DirectorTaskState, l as loadDirectorState } from './director-state-BmYi3DGA.js';
41
41
  export { A as AutoApprovePermissionPolicy, D as DefaultPermissionPolicy, P as PermissionPolicyOptions } from './permission-policy-BBa1M1xc.js';
42
42
  export { AutoCompactionMiddleware, AutonomousRunner, AutonomousRunnerOptions, AutonomyPromptContributorOptions, DefaultSkillLoader, DoneCheckResult, DoneConditionChecker, EternalAutonomyEngine, EternalAutonomyOptions, EternalEngineState, IntelligentCompactor, IntelligentCompactorOptions, ParallelEngineState, ParallelEternalEngine, ParallelEternalOptions, SelectiveCompactor, SelectiveCompactorOptions, SkillLoaderOptions, buildGoalPreamble, makeAutonomyPromptContributor } from './execution/index.js';
package/dist/index.js CHANGED
@@ -93,7 +93,7 @@ async function renameWithRetry(from, to) {
93
93
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
94
94
  throw err;
95
95
  }
96
- await new Promise((resolve8) => setTimeout(resolve8, delays[i]));
96
+ await new Promise((resolve9) => setTimeout(resolve9, delays[i]));
97
97
  }
98
98
  }
99
99
  throw lastErr;
@@ -2694,7 +2694,7 @@ var InMemoryAgentBridge = class {
2694
2694
  );
2695
2695
  }
2696
2696
  this.inflightGuards.add(correlationId);
2697
- return new Promise((resolve8, reject) => {
2697
+ return new Promise((resolve9, reject) => {
2698
2698
  const timer = setTimeout(() => {
2699
2699
  this.inflightGuards.delete(correlationId);
2700
2700
  this.pendingRequests.delete(correlationId);
@@ -2706,7 +2706,7 @@ var InMemoryAgentBridge = class {
2706
2706
  return;
2707
2707
  }
2708
2708
  this.pendingRequests.set(correlationId, {
2709
- resolve: resolve8,
2709
+ resolve: resolve9,
2710
2710
  reject,
2711
2711
  timer
2712
2712
  });
@@ -3990,7 +3990,9 @@ function looksSecret(name) {
3990
3990
  }
3991
3991
  function buildChildEnv(optsOrSessionId) {
3992
3992
  const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
3993
- const passthrough = process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
3993
+ const hasOwn = Object.prototype.hasOwnProperty.call(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
3994
+ const legacyHasOwn = Object.prototype.hasOwnProperty.call(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
3995
+ const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
3994
3996
  if (passthrough && !process.env["CI"]) {
3995
3997
  console.warn(
3996
3998
  "[WrongStack] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
@@ -5369,9 +5371,14 @@ var RecoveryLock = class {
5369
5371
  };
5370
5372
  }
5371
5373
  /**
5372
- * Claim the lock for the given session. Overwrites any existing lock
5373
- * the caller should have already handled abandonment (via
5374
- * `checkAbandoned`) before calling this.
5374
+ * Claim the lock for the given session. Uses exclusive-create (`O_EXCL`)
5375
+ * to detect whether another process acquired the lock between our
5376
+ * `checkAbandoned()` call and now. If the file already exists, it means
5377
+ * another process won the race and we throw instead of silently
5378
+ * overwriting their recovery record.
5379
+ *
5380
+ * The caller MUST have already called `checkAbandoned()` and handled its
5381
+ * null return before calling this.
5375
5382
  */
5376
5383
  async write(sessionId) {
5377
5384
  await ensureDir(path6.dirname(this.file));
@@ -5382,7 +5389,15 @@ var RecoveryLock = class {
5382
5389
  hostname: this.hostname,
5383
5390
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
5384
5391
  };
5385
- await atomicWrite(this.file, JSON.stringify(lock), { mode: 384 });
5392
+ try {
5393
+ await fsp2.writeFile(this.file, JSON.stringify(lock), { flag: "wx", mode: 384 });
5394
+ } catch (err) {
5395
+ const code = err.code;
5396
+ if (code === "EEXIST") {
5397
+ throw new Error(`Recovery lock already held by another process`);
5398
+ }
5399
+ throw err;
5400
+ }
5386
5401
  }
5387
5402
  /**
5388
5403
  * Release the lock. Idempotent — silently succeeds if the file is
@@ -6624,8 +6639,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
6624
6639
  try {
6625
6640
  await Promise.race([
6626
6641
  Promise.resolve(iter.return?.()),
6627
- new Promise((resolve8) => {
6628
- drainTimer = setTimeout(resolve8, 500);
6642
+ new Promise((resolve9) => {
6643
+ drainTimer = setTimeout(resolve9, 500);
6629
6644
  })
6630
6645
  ]);
6631
6646
  } finally {
@@ -6686,7 +6701,7 @@ async function runProviderWithRetry(opts) {
6686
6701
  description
6687
6702
  });
6688
6703
  }
6689
- await new Promise((resolve8, reject) => {
6704
+ await new Promise((resolve9, reject) => {
6690
6705
  let settled = false;
6691
6706
  const onAbort = () => {
6692
6707
  if (settled) return;
@@ -6699,7 +6714,7 @@ async function runProviderWithRetry(opts) {
6699
6714
  settled = true;
6700
6715
  clearTimeout(t2);
6701
6716
  signal.removeEventListener("abort", onAbort);
6702
- resolve8();
6717
+ resolve9();
6703
6718
  }, delay);
6704
6719
  if (signal.aborted) {
6705
6720
  onAbort();
@@ -8343,7 +8358,7 @@ ${recentJournal}` : "No prior iterations.",
8343
8358
  }
8344
8359
  };
8345
8360
  function sleep(ms) {
8346
- return new Promise((resolve8) => setTimeout(resolve8, ms));
8361
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
8347
8362
  }
8348
8363
 
8349
8364
  // src/coordination/subagent-budget.ts
@@ -8493,12 +8508,12 @@ var SubagentBudget = class _SubagentBudget {
8493
8508
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
8494
8509
  return Promise.resolve("stop");
8495
8510
  }
8496
- return new Promise((resolve8) => {
8511
+ return new Promise((resolve9) => {
8497
8512
  let resolved = false;
8498
8513
  const respond = (d) => {
8499
8514
  if (resolved) return;
8500
8515
  resolved = true;
8501
- resolve8(d);
8516
+ resolve9(d);
8502
8517
  };
8503
8518
  const fallback = setTimeout(
8504
8519
  () => respond("stop"),
@@ -11539,7 +11554,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
11539
11554
  taskIds.map((id) => {
11540
11555
  const cached = this.completedResults.find((r) => r.taskId === id);
11541
11556
  if (cached) return cached;
11542
- return new Promise((resolve8, reject) => {
11557
+ return new Promise((resolve9, reject) => {
11543
11558
  const timeout = setTimeout(() => {
11544
11559
  this.off("task.completed", handler);
11545
11560
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -11548,7 +11563,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
11548
11563
  if (result.taskId === id) {
11549
11564
  clearTimeout(timeout);
11550
11565
  this.off("task.completed", handler);
11551
- resolve8(result);
11566
+ resolve9(result);
11552
11567
  }
11553
11568
  };
11554
11569
  this.on("task.completed", handler);
@@ -11978,7 +11993,7 @@ function providerErrorToSubagentError(err, message, cause) {
11978
11993
 
11979
11994
  // src/execution/parallel-eternal-engine.ts
11980
11995
  function sleep2(ms) {
11981
- return new Promise((resolve8) => setTimeout(resolve8, ms));
11996
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
11982
11997
  }
11983
11998
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
11984
11999
  var ParallelEternalEngine = class {
@@ -13620,11 +13635,11 @@ var Director = class {
13620
13635
  if (cached) return cached;
13621
13636
  const existing = this.taskWaiters.get(id);
13622
13637
  if (existing) return existing.promise;
13623
- let resolve8;
13638
+ let resolve9;
13624
13639
  const promise = new Promise((res) => {
13625
- resolve8 = res;
13640
+ resolve9 = res;
13626
13641
  });
13627
- this.taskWaiters.set(id, { promise, resolve: resolve8 });
13642
+ this.taskWaiters.set(id, { promise, resolve: resolve9 });
13628
13643
  return promise;
13629
13644
  })
13630
13645
  );
@@ -13940,7 +13955,7 @@ function createDelegateTool(opts) {
13940
13955
  subagentId
13941
13956
  });
13942
13957
  const dir = director;
13943
- const result = await new Promise((resolve8) => {
13958
+ const result = await new Promise((resolve9) => {
13944
13959
  let settled = false;
13945
13960
  let timer;
13946
13961
  const finish = (value) => {
@@ -13949,7 +13964,7 @@ function createDelegateTool(opts) {
13949
13964
  if (timer) clearTimeout(timer);
13950
13965
  offTool();
13951
13966
  offIter();
13952
- resolve8(value);
13967
+ resolve9(value);
13953
13968
  };
13954
13969
  const arm = () => {
13955
13970
  if (timer) clearTimeout(timer);
@@ -17092,9 +17107,9 @@ var DefaultHealthRegistry = class {
17092
17107
  }
17093
17108
  async runOne(check) {
17094
17109
  let timer = null;
17095
- const timeout = new Promise((resolve8) => {
17110
+ const timeout = new Promise((resolve9) => {
17096
17111
  timer = setTimeout(
17097
- () => resolve8({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
17112
+ () => resolve9({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
17098
17113
  this.timeoutMs
17099
17114
  );
17100
17115
  });
@@ -17333,14 +17348,14 @@ async function startMetricsServer(opts) {
17333
17348
  const { createServer } = await import('http');
17334
17349
  server = createServer(listener);
17335
17350
  }
17336
- await new Promise((resolve8, reject) => {
17351
+ await new Promise((resolve9, reject) => {
17337
17352
  const onError = (err) => {
17338
17353
  server.off("listening", onListening);
17339
17354
  reject(err);
17340
17355
  };
17341
17356
  const onListening = () => {
17342
17357
  server.off("error", onError);
17343
- resolve8();
17358
+ resolve9();
17344
17359
  };
17345
17360
  server.once("error", onError);
17346
17361
  server.once("listening", onListening);
@@ -17352,8 +17367,8 @@ async function startMetricsServer(opts) {
17352
17367
  return {
17353
17368
  port: boundPort,
17354
17369
  url: `${protocol}://${host}:${boundPort}${path28}`,
17355
- close: () => new Promise((resolve8, reject) => {
17356
- server.close((err) => err ? reject(err) : resolve8());
17370
+ close: () => new Promise((resolve9, reject) => {
17371
+ server.close((err) => err ? reject(err) : resolve9());
17357
17372
  })
17358
17373
  };
17359
17374
  }
@@ -18449,10 +18464,12 @@ async function collectFiles(dir, baseDir) {
18449
18464
  // src/storage/session-rewinder.ts
18450
18465
  init_atomic_write();
18451
18466
  var DefaultSessionRewinder = class {
18452
- constructor(sessionsDir) {
18467
+ constructor(sessionsDir, projectRoot) {
18453
18468
  this.sessionsDir = sessionsDir;
18469
+ this.projectRoot = projectRoot;
18454
18470
  }
18455
18471
  sessionsDir;
18472
+ projectRoot;
18456
18473
  async listCheckpoints(sessionId) {
18457
18474
  const file = path6.join(this.sessionsDir, `${sessionId}.jsonl`);
18458
18475
  const raw = await fsp2.readFile(file, "utf8");
@@ -18509,7 +18526,7 @@ var DefaultSessionRewinder = class {
18509
18526
  }
18510
18527
  }
18511
18528
  }
18512
- const result = await revertSnapshots(snapshotsToRevert);
18529
+ const result = await revertSnapshots(snapshotsToRevert, this.projectRoot);
18513
18530
  const removedEvents = events.length - targetIdx - 1;
18514
18531
  return { ...result, toPromptIndex: checkpointIndex, removedEvents };
18515
18532
  }
@@ -18539,7 +18556,7 @@ var DefaultSessionRewinder = class {
18539
18556
  snapshotsToRevert.push({ promptIndex: event.promptIndex, files: event.files });
18540
18557
  }
18541
18558
  }
18542
- const result = await revertSnapshots(snapshotsToRevert.reverse());
18559
+ const result = await revertSnapshots(snapshotsToRevert.reverse(), this.projectRoot);
18543
18560
  return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
18544
18561
  }
18545
18562
  async rewindToStart(sessionId) {
@@ -18555,7 +18572,7 @@ var DefaultSessionRewinder = class {
18555
18572
  if (allSnapshots.length === 0) {
18556
18573
  return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };
18557
18574
  }
18558
- const result = await revertSnapshots(allSnapshots.reverse());
18575
+ const result = await revertSnapshots(allSnapshots.reverse(), this.projectRoot);
18559
18576
  return { ...result, toPromptIndex: 0, removedEvents: allSnapshots.length };
18560
18577
  }
18561
18578
  };
@@ -18573,12 +18590,19 @@ function parseEvents(raw) {
18573
18590
  }
18574
18591
  return events;
18575
18592
  }
18576
- async function revertSnapshots(snapshots) {
18593
+ async function revertSnapshots(snapshots, projectRoot) {
18577
18594
  const revertedFiles = [];
18578
18595
  const errors = [];
18579
18596
  for (const snapshot of snapshots) {
18580
18597
  for (const file of snapshot.files) {
18581
18598
  try {
18599
+ const absPath = path6.resolve(file.path);
18600
+ const root = path6.resolve(projectRoot);
18601
+ const rel = path6.relative(root, absPath);
18602
+ if (rel.startsWith("..") || path6.isAbsolute(rel)) {
18603
+ errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
18604
+ continue;
18605
+ }
18582
18606
  if (file.action === "deleted") {
18583
18607
  if (file.before !== null) {
18584
18608
  await atomicWrite(file.path, file.before, { mode: 420 });
@@ -19754,7 +19778,7 @@ var SecurityScannerOrchestrator = class {
19754
19778
  const delay = Math.round(policy.delayMs(attempt));
19755
19779
  const status = isProviderErr ? err.status : 0;
19756
19780
  console.warn(`[SecurityScanner] retry ${attempt + 1} after ${delay}ms (status=${status}) \u2014 ${errAsErr.message}`);
19757
- await new Promise((resolve8) => setTimeout(resolve8, delay));
19781
+ await new Promise((resolve9) => setTimeout(resolve9, delay));
19758
19782
  return this.completeWithRetry(provider, request, abortController, attempt + 1);
19759
19783
  }
19760
19784
  }
@@ -21251,12 +21275,12 @@ function makeContinueToNextIterationTool() {
21251
21275
  // src/core/iteration-limit.ts
21252
21276
  function requestLimitExtension(opts) {
21253
21277
  const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
21254
- return new Promise((resolve8) => {
21278
+ return new Promise((resolve9) => {
21255
21279
  let resolved = false;
21256
21280
  const timerFired = () => {
21257
21281
  if (!resolved) {
21258
21282
  resolved = true;
21259
- resolve8(0);
21283
+ resolve9(0);
21260
21284
  }
21261
21285
  };
21262
21286
  const timer = setTimeout(timerFired, timeoutMs);
@@ -21265,14 +21289,14 @@ function requestLimitExtension(opts) {
21265
21289
  if (!resolved) {
21266
21290
  resolved = true;
21267
21291
  clearTimeout(timer);
21268
- resolve8(0);
21292
+ resolve9(0);
21269
21293
  }
21270
21294
  };
21271
21295
  const grant = (extra) => {
21272
21296
  if (!resolved) {
21273
21297
  resolved = true;
21274
21298
  clearTimeout(timer);
21275
- resolve8(Math.max(0, extra));
21299
+ resolve9(Math.max(0, extra));
21276
21300
  }
21277
21301
  };
21278
21302
  events.emit("iteration.limit_reached", {
@@ -21286,7 +21310,7 @@ function requestLimitExtension(opts) {
21286
21310
  if (!resolved) {
21287
21311
  resolved = true;
21288
21312
  clearTimeout(timer);
21289
- resolve8(100);
21313
+ resolve9(100);
21290
21314
  }
21291
21315
  });
21292
21316
  }
@@ -21855,13 +21879,13 @@ var Agent = class {
21855
21879
  }
21856
21880
  }
21857
21881
  waitForConfirm(info) {
21858
- return new Promise((resolve8) => {
21882
+ return new Promise((resolve9) => {
21859
21883
  this.events.emit("tool.confirm_needed", {
21860
21884
  tool: info.tool,
21861
21885
  input: info.input,
21862
21886
  toolUseId: info.toolUseId,
21863
21887
  suggestedPattern: info.suggestedPattern,
21864
- resolve: resolve8
21888
+ resolve: resolve9
21865
21889
  });
21866
21890
  });
21867
21891
  }
@@ -22578,12 +22602,12 @@ ${mem}`);
22578
22602
  }
22579
22603
  }
22580
22604
  async gitStatus(root) {
22581
- return new Promise((resolve8) => {
22605
+ return new Promise((resolve9) => {
22582
22606
  let settled = false;
22583
22607
  const finish = (s) => {
22584
22608
  if (settled) return;
22585
22609
  settled = true;
22586
- resolve8(s);
22610
+ resolve9(s);
22587
22611
  };
22588
22612
  let proc;
22589
22613
  const timer = setTimeout(() => {
@@ -23941,7 +23965,7 @@ var PhaseOrchestrator = class {
23941
23965
  };
23942
23966
  }
23943
23967
  delay(ms) {
23944
- return new Promise((resolve8) => setTimeout(resolve8, ms));
23968
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
23945
23969
  }
23946
23970
  };
23947
23971
 
@@ -24609,7 +24633,11 @@ var WorktreeManager = class {
24609
24633
  const slug = this.makeSlug(opts.slugHint ?? ownerId);
24610
24634
  const branch = `wstack/ap/${slug}`;
24611
24635
  const dir = join(this.worktreesRoot(), slug);
24612
- assertSafePath(dir, this.projectRoot);
24636
+ const absDir = resolve(dir);
24637
+ const absRoot = resolve(this.projectRoot);
24638
+ if (!absDir.startsWith(absRoot + sep)) {
24639
+ throw new Error(`Worktree dir "${absDir}" resolves outside project root`);
24640
+ }
24613
24641
  const baseBranch = opts.baseBranch ?? await this.detectBaseBranch();
24614
24642
  const handle = {
24615
24643
  id: slug,
@@ -24660,7 +24688,8 @@ var WorktreeManager = class {
24660
24688
  this.emitCommitted(handle, false);
24661
24689
  return { committed: false };
24662
24690
  }
24663
- const committed = await this.runGit(["commit", "-m", message], handle.dir);
24691
+ const idArgs = await this.identityArgs(handle.dir);
24692
+ const committed = await this.runGit([...idArgs, "commit", "-m", message], handle.dir);
24664
24693
  if (committed.code !== 0) {
24665
24694
  this.fail(handle, committed.stderr || "git commit failed");
24666
24695
  return { committed: false };
@@ -24686,7 +24715,10 @@ var WorktreeManager = class {
24686
24715
  const mergeArgs = squash ? ["merge", "--squash", handle.branch] : ["merge", "--no-ff", handle.branch];
24687
24716
  const merged = await this.runGit(mergeArgs, this.projectRoot);
24688
24717
  if (merged.code !== 0) {
24689
- const conflictFiles = await this.unmergedFiles();
24718
+ const fromOutput = parseConflictPaths(`${merged.stdout}
24719
+ ${merged.stderr}`);
24720
+ const fromIndex = await this.unmergedFiles();
24721
+ const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
24690
24722
  await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot);
24691
24723
  handle.conflictFiles = conflictFiles;
24692
24724
  this.setStatus(handle, "needs-review", { lastError: merged.stderr });
@@ -24700,7 +24732,8 @@ var WorktreeManager = class {
24700
24732
  }
24701
24733
  if (squash) {
24702
24734
  const msg = opts.message ?? `merge ${handle.branch} (squash)`;
24703
- const commit = await this.runGit(["commit", "-m", msg], this.projectRoot);
24735
+ const idArgs = await this.identityArgs(this.projectRoot);
24736
+ const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
24704
24737
  if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
24705
24738
  this.fail(handle, commit.stderr || "squash commit failed");
24706
24739
  return { ok: false, stderr: commit.stderr };
@@ -24775,11 +24808,27 @@ var WorktreeManager = class {
24775
24808
  }
24776
24809
  return { insertions, deletions, files, sha };
24777
24810
  }
24811
+ /**
24812
+ * `git -c user.*` fallback so commits succeed on machines and CI runners
24813
+ * that have no global git identity configured. Returns `[]` when both
24814
+ * `user.name` and `user.email` are already set (the common case), so a real
24815
+ * user's identity is never overridden. The worktree branch commits are
24816
+ * squashed away on merge, so the fallback identity never reaches the base
24817
+ * branch history.
24818
+ */
24819
+ async identityArgs(cwd) {
24820
+ const name = (await this.runGit(["config", "user.name"], cwd)).stdout.trim();
24821
+ const email = (await this.runGit(["config", "user.email"], cwd)).stdout.trim();
24822
+ if (name && email) return [];
24823
+ return [
24824
+ "-c",
24825
+ `user.name=${name || "WrongStack AutoPhase"}`,
24826
+ "-c",
24827
+ `user.email=${email || "autophase@wrongstack.local"}`
24828
+ ];
24829
+ }
24778
24830
  async unmergedFiles() {
24779
- const res = await this.runGit(
24780
- ["diff", "--name-only", "--diff-filter=U"],
24781
- this.projectRoot
24782
- );
24831
+ const res = await this.runGit(["diff", "--name-only", "--diff-filter=U"], this.projectRoot);
24783
24832
  return res.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
24784
24833
  }
24785
24834
  emitCommitted(handle, committed) {
@@ -24832,6 +24881,14 @@ var WorktreeManager = class {
24832
24881
  });
24833
24882
  }
24834
24883
  };
24884
+ function parseConflictPaths(output) {
24885
+ const paths = /* @__PURE__ */ new Set();
24886
+ for (const line of output.split("\n")) {
24887
+ const m = line.match(/^CONFLICT \([^)]*\): Merge conflict in (.+?)\s*$/);
24888
+ if (m?.[1]) paths.add(m[1]);
24889
+ }
24890
+ return [...paths];
24891
+ }
24835
24892
  function assertSafePath(dir, projectRoot) {
24836
24893
  const root = resolve(projectRoot);
24837
24894
  const abs = resolve(dir);