@posthog/agent 2.1.47 → 2.1.53

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.
@@ -809,10 +809,10 @@ var require_src2 = __commonJS({
809
809
  var fs_1 = __require("fs");
810
810
  var debug_1 = __importDefault(require_src());
811
811
  var log = debug_1.default("@kwsites/file-exists");
812
- function check(path7, isFile, isDirectory) {
813
- log(`checking %s`, path7);
812
+ function check(path8, isFile, isDirectory) {
813
+ log(`checking %s`, path8);
814
814
  try {
815
- const stat = fs_1.statSync(path7);
815
+ const stat = fs_1.statSync(path8);
816
816
  if (stat.isFile() && isFile) {
817
817
  log(`[OK] path represents a file`);
818
818
  return true;
@@ -832,8 +832,8 @@ var require_src2 = __commonJS({
832
832
  throw e;
833
833
  }
834
834
  }
835
- function exists2(path7, type = exports.READABLE) {
836
- return check(path7, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
835
+ function exists2(path8, type = exports.READABLE) {
836
+ return check(path8, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
837
837
  }
838
838
  exports.exists = exists2;
839
839
  exports.FILE = 1;
@@ -1178,174 +1178,12 @@ import {
1178
1178
  import {
1179
1179
  query
1180
1180
  } from "@anthropic-ai/claude-agent-sdk";
1181
-
1182
- // ../shared/dist/index.js
1183
- var consoleLogger = {
1184
- info: (_message, _data) => {
1185
- },
1186
- debug: (_message, _data) => {
1187
- },
1188
- error: (_message, _data) => {
1189
- },
1190
- warn: (_message, _data) => {
1191
- }
1192
- };
1193
- var Saga = class {
1194
- completedSteps = [];
1195
- currentStepName = "unknown";
1196
- stepTimings = [];
1197
- log;
1198
- constructor(logger) {
1199
- this.log = logger ?? consoleLogger;
1200
- }
1201
- /**
1202
- * Run the saga with the given input.
1203
- * Returns a discriminated union result - either success with data or failure with error details.
1204
- */
1205
- async run(input) {
1206
- this.completedSteps = [];
1207
- this.currentStepName = "unknown";
1208
- this.stepTimings = [];
1209
- const sagaStart = performance.now();
1210
- this.log.info("Starting saga", { sagaName: this.constructor.name });
1211
- try {
1212
- const result = await this.execute(input);
1213
- const totalDuration = performance.now() - sagaStart;
1214
- this.log.debug("Saga completed successfully", {
1215
- sagaName: this.constructor.name,
1216
- stepsCompleted: this.completedSteps.length,
1217
- totalDurationMs: Math.round(totalDuration),
1218
- stepTimings: this.stepTimings
1219
- });
1220
- return { success: true, data: result };
1221
- } catch (error) {
1222
- this.log.error("Saga failed, initiating rollback", {
1223
- sagaName: this.constructor.name,
1224
- failedStep: this.currentStepName,
1225
- error: error instanceof Error ? error.message : String(error)
1226
- });
1227
- await this.rollback();
1228
- return {
1229
- success: false,
1230
- error: error instanceof Error ? error.message : String(error),
1231
- failedStep: this.currentStepName
1232
- };
1233
- }
1234
- }
1235
- /**
1236
- * Execute a step with its rollback action.
1237
- * If the step succeeds, its rollback action is stored for potential rollback.
1238
- * The step name is automatically tracked for error reporting.
1239
- *
1240
- * @param config - Step configuration with name, execute, and rollback functions
1241
- * @returns The result of the execute function
1242
- * @throws Re-throws any error from the execute function (triggers rollback)
1243
- */
1244
- async step(config) {
1245
- this.currentStepName = config.name;
1246
- this.log.debug(`Executing step: ${config.name}`);
1247
- const stepStart = performance.now();
1248
- const result = await config.execute();
1249
- const durationMs = Math.round(performance.now() - stepStart);
1250
- this.stepTimings.push({ name: config.name, durationMs });
1251
- this.log.debug(`Step completed: ${config.name}`, { durationMs });
1252
- this.completedSteps.push({
1253
- name: config.name,
1254
- rollback: () => config.rollback(result)
1255
- });
1256
- return result;
1257
- }
1258
- /**
1259
- * Execute a step that doesn't need rollback.
1260
- * Useful for read-only operations or operations that are idempotent.
1261
- * The step name is automatically tracked for error reporting.
1262
- *
1263
- * @param name - Step name for logging and error tracking
1264
- * @param execute - The action to execute
1265
- * @returns The result of the execute function
1266
- */
1267
- async readOnlyStep(name, execute) {
1268
- this.currentStepName = name;
1269
- this.log.debug(`Executing read-only step: ${name}`);
1270
- const stepStart = performance.now();
1271
- const result = await execute();
1272
- const durationMs = Math.round(performance.now() - stepStart);
1273
- this.stepTimings.push({ name, durationMs });
1274
- this.log.debug(`Read-only step completed: ${name}`, { durationMs });
1275
- return result;
1276
- }
1277
- /**
1278
- * Roll back all completed steps in reverse order.
1279
- * Rollback errors are logged but don't stop the rollback of other steps.
1280
- */
1281
- async rollback() {
1282
- this.log.info("Rolling back saga", {
1283
- stepsToRollback: this.completedSteps.length
1284
- });
1285
- const stepsReversed = [...this.completedSteps].reverse();
1286
- for (const step of stepsReversed) {
1287
- try {
1288
- this.log.debug(`Rolling back step: ${step.name}`);
1289
- await step.rollback();
1290
- this.log.debug(`Step rolled back: ${step.name}`);
1291
- } catch (error) {
1292
- this.log.error(`Failed to rollback step: ${step.name}`, {
1293
- error: error instanceof Error ? error.message : String(error)
1294
- });
1295
- }
1296
- }
1297
- this.log.info("Rollback completed", {
1298
- stepsAttempted: this.completedSteps.length
1299
- });
1300
- }
1301
- /**
1302
- * Get the number of completed steps (useful for testing)
1303
- */
1304
- getCompletedStepCount() {
1305
- return this.completedSteps.length;
1306
- }
1307
- };
1308
- var NOOP_COLLECTOR = {
1309
- time: (_label, fn) => fn(),
1310
- timeSync: (_label, fn) => fn(),
1311
- record: () => {
1312
- },
1313
- summarize: () => {
1314
- }
1315
- };
1316
- function createTimingCollector(enabled, log) {
1317
- if (!enabled) return NOOP_COLLECTOR;
1318
- const steps = {};
1319
- return {
1320
- async time(label, fn) {
1321
- const start = Date.now();
1322
- const result = await fn();
1323
- steps[label] = Date.now() - start;
1324
- return result;
1325
- },
1326
- timeSync(label, fn) {
1327
- const start = Date.now();
1328
- const result = fn();
1329
- steps[label] = Date.now() - start;
1330
- return result;
1331
- },
1332
- record(label, ms) {
1333
- steps[label] = ms;
1334
- },
1335
- summarize(label) {
1336
- const total = Object.values(steps).reduce((a, b) => a + b, 0);
1337
- log(`[timing] ${label}: ${total}ms`, steps);
1338
- }
1339
- };
1340
- }
1341
-
1342
- // src/adapters/claude/claude-agent.ts
1343
1181
  import { v7 as uuidv7 } from "uuid";
1344
1182
 
1345
1183
  // package.json
1346
1184
  var package_default = {
1347
1185
  name: "@posthog/agent",
1348
- version: "2.1.47",
1186
+ version: "2.1.53",
1349
1187
  repository: "https://github.com/PostHog/twig",
1350
1188
  description: "TypeScript agent framework wrapping Claude Agent SDK with Git-based task execution for PostHog",
1351
1189
  exports: {
@@ -1723,8 +1561,8 @@ var ToolContentBuilder = class {
1723
1561
  this.items.push({ type: "content", content: image(data, mimeType, uri) });
1724
1562
  return this;
1725
1563
  }
1726
- diff(path7, oldText, newText) {
1727
- this.items.push({ type: "diff", path: path7, oldText, newText });
1564
+ diff(path8, oldText, newText) {
1565
+ this.items.push({ type: "diff", path: path8, oldText, newText });
1728
1566
  return this;
1729
1567
  }
1730
1568
  build() {
@@ -1914,13 +1752,13 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1914
1752
  locations: []
1915
1753
  };
1916
1754
  case "Edit": {
1917
- const path7 = input?.file_path ? String(input.file_path) : void 0;
1755
+ const path8 = input?.file_path ? String(input.file_path) : void 0;
1918
1756
  let oldText = input?.old_string ? String(input.old_string) : null;
1919
1757
  let newText = input?.new_string ? String(input.new_string) : "";
1920
1758
  let affectedLines = [];
1921
- if (path7 && oldText) {
1759
+ if (path8 && oldText) {
1922
1760
  try {
1923
- const oldContent = cachedFileContent[path7] || "";
1761
+ const oldContent = cachedFileContent[path8] || "";
1924
1762
  const newContent = replaceAndCalculateLocation(oldContent, [
1925
1763
  {
1926
1764
  oldText,
@@ -1936,17 +1774,17 @@ function toolInfoFromToolUse(toolUse, cachedFileContent, logger = new Logger({ d
1936
1774
  }
1937
1775
  }
1938
1776
  return {
1939
- title: path7 ? `Edit \`${path7}\`` : "Edit",
1777
+ title: path8 ? `Edit \`${path8}\`` : "Edit",
1940
1778
  kind: "edit",
1941
- content: input && path7 ? [
1779
+ content: input && path8 ? [
1942
1780
  {
1943
1781
  type: "diff",
1944
- path: path7,
1782
+ path: path8,
1945
1783
  oldText,
1946
1784
  newText
1947
1785
  }
1948
1786
  ] : [],
1949
- locations: path7 ? affectedLines.length > 0 ? affectedLines.map((line) => ({ line, path: path7 })) : [{ path: path7 }] : []
1787
+ locations: path8 ? affectedLines.length > 0 ? affectedLines.map((line) => ({ line, path: path8 })) : [{ path: path8 }] : []
1950
1788
  };
1951
1789
  }
1952
1790
  case "Write": {
@@ -3420,12 +3258,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3420
3258
  logWriter;
3421
3259
  options;
3422
3260
  lastSentConfigOptions;
3423
- debug;
3424
3261
  constructor(client, logWriter, options) {
3425
3262
  super(client);
3426
3263
  this.logWriter = logWriter;
3427
3264
  this.options = options;
3428
- this.debug = options?.debug ?? false;
3429
3265
  this.toolUseCache = {};
3430
3266
  this.logger = new Logger({ debug: true, prefix: "[ClaudeAcpAgent]" });
3431
3267
  }
@@ -3468,36 +3304,26 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3468
3304
  }
3469
3305
  async newSession(params) {
3470
3306
  this.checkAuthStatus();
3471
- const tc = createTimingCollector(
3472
- this.debug,
3473
- (msg, data) => this.logger.info(msg, data)
3474
- );
3475
3307
  const meta = params._meta;
3476
3308
  const sessionId = uuidv7();
3477
3309
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3478
- const mcpServers = tc.timeSync(
3479
- "parseMcpServers",
3480
- () => parseMcpServers(params)
3481
- );
3482
- const options = tc.timeSync(
3483
- "buildSessionOptions",
3484
- () => buildSessionOptions({
3485
- cwd: params.cwd,
3486
- mcpServers,
3487
- permissionMode,
3488
- canUseTool: this.createCanUseTool(sessionId),
3489
- logger: this.logger,
3490
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3491
- userProvidedOptions: meta?.claudeCode?.options,
3492
- sessionId,
3493
- isResume: false,
3494
- onModeChange: this.createOnModeChange(sessionId),
3495
- onProcessSpawned: this.options?.onProcessSpawned,
3496
- onProcessExited: this.options?.onProcessExited
3497
- })
3498
- );
3310
+ const mcpServers = parseMcpServers(params);
3311
+ const options = buildSessionOptions({
3312
+ cwd: params.cwd,
3313
+ mcpServers,
3314
+ permissionMode,
3315
+ canUseTool: this.createCanUseTool(sessionId),
3316
+ logger: this.logger,
3317
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3318
+ userProvidedOptions: meta?.claudeCode?.options,
3319
+ sessionId,
3320
+ isResume: false,
3321
+ onModeChange: this.createOnModeChange(sessionId),
3322
+ onProcessSpawned: this.options?.onProcessSpawned,
3323
+ onProcessExited: this.options?.onProcessExited
3324
+ });
3499
3325
  const input = new Pushable();
3500
- const q = tc.timeSync("sdkQuery", () => query({ prompt: input, options }));
3326
+ const q = query({ prompt: input, options });
3501
3327
  const session = this.createSession(
3502
3328
  sessionId,
3503
3329
  q,
@@ -3509,27 +3335,17 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3509
3335
  session.taskRunId = meta?.taskRunId;
3510
3336
  this.registerPersistence(sessionId, meta);
3511
3337
  if (meta?.taskRunId) {
3512
- await tc.time(
3513
- "extNotification",
3514
- () => this.client.extNotification("_posthog/sdk_session", {
3515
- taskRunId: meta.taskRunId,
3516
- sessionId,
3517
- adapter: "claude"
3518
- })
3519
- );
3338
+ await this.client.extNotification("_posthog/sdk_session", {
3339
+ taskRunId: meta.taskRunId,
3340
+ sessionId,
3341
+ adapter: "claude"
3342
+ });
3520
3343
  }
3521
- const modelOptions = await tc.time(
3522
- "fetchModels",
3523
- () => this.getModelConfigOptions()
3524
- );
3525
- this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
3344
+ const modelOptions = await this.getModelConfigOptions();
3345
+ this.deferBackgroundFetches(q, sessionId, mcpServers);
3526
3346
  session.modelId = modelOptions.currentModelId;
3527
3347
  await this.trySetModel(q, modelOptions.currentModelId);
3528
- const configOptions = await tc.time(
3529
- "buildConfigOptions",
3530
- () => this.buildConfigOptions(modelOptions)
3531
- );
3532
- tc.summarize("newSession");
3348
+ const configOptions = await this.buildConfigOptions(modelOptions);
3533
3349
  return {
3534
3350
  sessionId,
3535
3351
  configOptions
@@ -3539,10 +3355,6 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3539
3355
  return this.resumeSession(params);
3540
3356
  }
3541
3357
  async resumeSession(params) {
3542
- const tc = createTimingCollector(
3543
- this.debug,
3544
- (msg, data) => this.logger.info(msg, data)
3545
- );
3546
3358
  const meta = params._meta;
3547
3359
  const sessionId = meta?.sessionId;
3548
3360
  if (!sessionId) {
@@ -3551,32 +3363,22 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3551
3363
  if (this.sessionId === sessionId) {
3552
3364
  return {};
3553
3365
  }
3554
- const mcpServers = tc.timeSync(
3555
- "parseMcpServers",
3556
- () => parseMcpServers(params)
3557
- );
3366
+ const mcpServers = parseMcpServers(params);
3558
3367
  const permissionMode = meta?.permissionMode && TWIG_EXECUTION_MODES.includes(meta.permissionMode) ? meta.permissionMode : "default";
3559
- const { query: q, session } = await tc.time(
3560
- "initializeQuery",
3561
- () => this.initializeQuery({
3562
- cwd: params.cwd,
3563
- permissionMode,
3564
- mcpServers,
3565
- systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3566
- userProvidedOptions: meta?.claudeCode?.options,
3567
- sessionId,
3568
- isResume: true,
3569
- additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3570
- })
3571
- );
3368
+ const { query: q, session } = await this.initializeQuery({
3369
+ cwd: params.cwd,
3370
+ permissionMode,
3371
+ mcpServers,
3372
+ systemPrompt: buildSystemPrompt(meta?.systemPrompt),
3373
+ userProvidedOptions: meta?.claudeCode?.options,
3374
+ sessionId,
3375
+ isResume: true,
3376
+ additionalDirectories: meta?.claudeCode?.options?.additionalDirectories
3377
+ });
3572
3378
  session.taskRunId = meta?.taskRunId;
3573
3379
  this.registerPersistence(sessionId, meta);
3574
- this.deferBackgroundFetches(tc, q, sessionId, mcpServers);
3575
- const configOptions = await tc.time(
3576
- "buildConfigOptions",
3577
- () => this.buildConfigOptions()
3578
- );
3579
- tc.summarize("resumeSession");
3380
+ this.deferBackgroundFetches(q, sessionId, mcpServers);
3381
+ const configOptions = await this.buildConfigOptions();
3580
3382
  return { configOptions };
3581
3383
  }
3582
3384
  async prompt(params) {
@@ -3764,13 +3566,10 @@ var ClaudeAcpAgent = class extends BaseAcpAgent {
3764
3566
  * Fire-and-forget: fetch slash commands and MCP tool metadata in parallel.
3765
3567
  * Both populate caches used later — neither is needed to return configOptions.
3766
3568
  */
3767
- deferBackgroundFetches(tc, q, sessionId, mcpServers) {
3569
+ deferBackgroundFetches(q, sessionId, mcpServers) {
3768
3570
  Promise.all([
3769
- tc.time("slashCommands", () => getAvailableSlashCommands(q)),
3770
- tc.time(
3771
- "mcpMetadata",
3772
- () => fetchMcpToolMetadata(mcpServers, this.logger)
3773
- )
3571
+ getAvailableSlashCommands(q),
3572
+ fetchMcpToolMetadata(mcpServers, this.logger)
3774
3573
  ]).then(([slashCommands]) => {
3775
3574
  this.sendAvailableCommandsUpdate(sessionId, slashCommands);
3776
3575
  }).catch((err) => {
@@ -4064,10 +3863,7 @@ function createClaudeConnection(config) {
4064
3863
  const agentStream = ndJsonStream(agentWritable, streams.agent.readable);
4065
3864
  let agent = null;
4066
3865
  const agentConnection = new AgentSideConnection((client) => {
4067
- agent = new ClaudeAcpAgent(client, logWriter, {
4068
- ...config.processCallbacks,
4069
- debug: config.debug
4070
- });
3866
+ agent = new ClaudeAcpAgent(client, logWriter, config.processCallbacks);
4071
3867
  logger.info(`Created ${agent.adapterName} agent`);
4072
3868
  return agent;
4073
3869
  }, agentStream);
@@ -4482,6 +4278,8 @@ var PostHogAPIClient = class {
4482
4278
  };
4483
4279
 
4484
4280
  // src/session-log-writer.ts
4281
+ import fs3 from "fs";
4282
+ import path4 from "path";
4485
4283
  var SessionLogWriter = class _SessionLogWriter {
4486
4284
  static FLUSH_DEBOUNCE_MS = 500;
4487
4285
  static FLUSH_MAX_INTERVAL_MS = 5e3;
@@ -4495,8 +4293,10 @@ var SessionLogWriter = class _SessionLogWriter {
4495
4293
  sessions = /* @__PURE__ */ new Map();
4496
4294
  messageCounts = /* @__PURE__ */ new Map();
4497
4295
  logger;
4296
+ localCachePath;
4498
4297
  constructor(options = {}) {
4499
4298
  this.posthogAPI = options.posthogAPI;
4299
+ this.localCachePath = options.localCachePath;
4500
4300
  this.logger = options.logger ?? new Logger({ debug: false, prefix: "[SessionLogWriter]" });
4501
4301
  }
4502
4302
  async flushAll() {
@@ -4526,6 +4326,21 @@ var SessionLogWriter = class _SessionLogWriter {
4526
4326
  });
4527
4327
  this.sessions.set(sessionId, { context });
4528
4328
  this.lastFlushAttemptTime.set(sessionId, Date.now());
4329
+ if (this.localCachePath) {
4330
+ const sessionDir = path4.join(
4331
+ this.localCachePath,
4332
+ "sessions",
4333
+ context.runId
4334
+ );
4335
+ try {
4336
+ fs3.mkdirSync(sessionDir, { recursive: true });
4337
+ } catch (error) {
4338
+ this.logger.warn("Failed to create local cache directory", {
4339
+ sessionDir,
4340
+ error
4341
+ });
4342
+ }
4343
+ }
4529
4344
  }
4530
4345
  isRegistered(sessionId) {
4531
4346
  return this.sessions.has(sessionId);
@@ -4563,6 +4378,7 @@ var SessionLogWriter = class _SessionLogWriter {
4563
4378
  timestamp,
4564
4379
  notification: message
4565
4380
  };
4381
+ this.writeToLocalCache(sessionId, entry);
4566
4382
  if (this.posthogAPI) {
4567
4383
  const pending = this.pendingEntries.get(sessionId) ?? [];
4568
4384
  pending.push(entry);
@@ -4663,6 +4479,7 @@ var SessionLogWriter = class _SessionLogWriter {
4663
4479
  }
4664
4480
  }
4665
4481
  };
4482
+ this.writeToLocalCache(sessionId, entry);
4666
4483
  if (this.posthogAPI) {
4667
4484
  const pending = this.pendingEntries.get(sessionId) ?? [];
4668
4485
  pending.push(entry);
@@ -4690,11 +4507,28 @@ var SessionLogWriter = class _SessionLogWriter {
4690
4507
  const timeout = setTimeout(() => this.flush(sessionId), delay2);
4691
4508
  this.flushTimeouts.set(sessionId, timeout);
4692
4509
  }
4510
+ writeToLocalCache(sessionId, entry) {
4511
+ if (!this.localCachePath) return;
4512
+ const session = this.sessions.get(sessionId);
4513
+ if (!session) return;
4514
+ const logPath = path4.join(
4515
+ this.localCachePath,
4516
+ "sessions",
4517
+ session.context.runId,
4518
+ "logs.ndjson"
4519
+ );
4520
+ try {
4521
+ fs3.appendFileSync(logPath, `${JSON.stringify(entry)}
4522
+ `);
4523
+ } catch (error) {
4524
+ this.logger.warn("Failed to write to local cache", { logPath, error });
4525
+ }
4526
+ }
4693
4527
  };
4694
4528
 
4695
4529
  // ../git/dist/queries.js
4696
- import * as fs4 from "fs/promises";
4697
- import * as path5 from "path";
4530
+ import * as fs5 from "fs/promises";
4531
+ import * as path6 from "path";
4698
4532
 
4699
4533
  // ../../node_modules/simple-git/dist/esm/index.js
4700
4534
  var import_file_exists = __toESM(require_dist(), 1);
@@ -4733,8 +4567,8 @@ function pathspec(...paths) {
4733
4567
  cache.set(key, paths);
4734
4568
  return key;
4735
4569
  }
4736
- function isPathSpec(path7) {
4737
- return path7 instanceof String && cache.has(path7);
4570
+ function isPathSpec(path8) {
4571
+ return path8 instanceof String && cache.has(path8);
4738
4572
  }
4739
4573
  function toPaths(pathSpec) {
4740
4574
  return cache.get(pathSpec) || [];
@@ -4823,8 +4657,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
4823
4657
  function forEachLineWithContent(input, callback) {
4824
4658
  return toLinesWithContent(input, true).map((line) => callback(line));
4825
4659
  }
4826
- function folderExists(path7) {
4827
- return (0, import_file_exists.exists)(path7, import_file_exists.FOLDER);
4660
+ function folderExists(path8) {
4661
+ return (0, import_file_exists.exists)(path8, import_file_exists.FOLDER);
4828
4662
  }
4829
4663
  function append(target, item) {
4830
4664
  if (Array.isArray(target)) {
@@ -5228,8 +5062,8 @@ function checkIsRepoRootTask() {
5228
5062
  commands,
5229
5063
  format: "utf-8",
5230
5064
  onError,
5231
- parser(path7) {
5232
- return /^\.(git)?$/.test(path7.trim());
5065
+ parser(path8) {
5066
+ return /^\.(git)?$/.test(path8.trim());
5233
5067
  }
5234
5068
  };
5235
5069
  }
@@ -5663,11 +5497,11 @@ function parseGrep(grep) {
5663
5497
  const paths = /* @__PURE__ */ new Set();
5664
5498
  const results = {};
5665
5499
  forEachLineWithContent(grep, (input) => {
5666
- const [path7, line, preview] = input.split(NULL);
5667
- paths.add(path7);
5668
- (results[path7] = results[path7] || []).push({
5500
+ const [path8, line, preview] = input.split(NULL);
5501
+ paths.add(path8);
5502
+ (results[path8] = results[path8] || []).push({
5669
5503
  line: asNumber(line),
5670
- path: path7,
5504
+ path: path8,
5671
5505
  preview
5672
5506
  });
5673
5507
  });
@@ -6432,14 +6266,14 @@ var init_hash_object = __esm({
6432
6266
  init_task();
6433
6267
  }
6434
6268
  });
6435
- function parseInit(bare, path7, text2) {
6269
+ function parseInit(bare, path8, text2) {
6436
6270
  const response = String(text2).trim();
6437
6271
  let result;
6438
6272
  if (result = initResponseRegex.exec(response)) {
6439
- return new InitSummary(bare, path7, false, result[1]);
6273
+ return new InitSummary(bare, path8, false, result[1]);
6440
6274
  }
6441
6275
  if (result = reInitResponseRegex.exec(response)) {
6442
- return new InitSummary(bare, path7, true, result[1]);
6276
+ return new InitSummary(bare, path8, true, result[1]);
6443
6277
  }
6444
6278
  let gitDir = "";
6445
6279
  const tokens = response.split(" ");
@@ -6450,7 +6284,7 @@ function parseInit(bare, path7, text2) {
6450
6284
  break;
6451
6285
  }
6452
6286
  }
6453
- return new InitSummary(bare, path7, /^re/i.test(response), gitDir);
6287
+ return new InitSummary(bare, path8, /^re/i.test(response), gitDir);
6454
6288
  }
6455
6289
  var InitSummary;
6456
6290
  var initResponseRegex;
@@ -6459,9 +6293,9 @@ var init_InitSummary = __esm({
6459
6293
  "src/lib/responses/InitSummary.ts"() {
6460
6294
  "use strict";
6461
6295
  InitSummary = class {
6462
- constructor(bare, path7, existing, gitDir) {
6296
+ constructor(bare, path8, existing, gitDir) {
6463
6297
  this.bare = bare;
6464
- this.path = path7;
6298
+ this.path = path8;
6465
6299
  this.existing = existing;
6466
6300
  this.gitDir = gitDir;
6467
6301
  }
@@ -6473,7 +6307,7 @@ var init_InitSummary = __esm({
6473
6307
  function hasBareCommand(command) {
6474
6308
  return command.includes(bareCommand);
6475
6309
  }
6476
- function initTask(bare = false, path7, customArgs) {
6310
+ function initTask(bare = false, path8, customArgs) {
6477
6311
  const commands = ["init", ...customArgs];
6478
6312
  if (bare && !hasBareCommand(commands)) {
6479
6313
  commands.splice(1, 0, bareCommand);
@@ -6482,7 +6316,7 @@ function initTask(bare = false, path7, customArgs) {
6482
6316
  commands,
6483
6317
  format: "utf-8",
6484
6318
  parser(text2) {
6485
- return parseInit(commands.includes("--bare"), path7, text2);
6319
+ return parseInit(commands.includes("--bare"), path8, text2);
6486
6320
  }
6487
6321
  };
6488
6322
  }
@@ -7298,12 +7132,12 @@ var init_FileStatusSummary = __esm({
7298
7132
  "use strict";
7299
7133
  fromPathRegex = /^(.+)\0(.+)$/;
7300
7134
  FileStatusSummary = class {
7301
- constructor(path7, index, working_dir) {
7302
- this.path = path7;
7135
+ constructor(path8, index, working_dir) {
7136
+ this.path = path8;
7303
7137
  this.index = index;
7304
7138
  this.working_dir = working_dir;
7305
7139
  if (index === "R" || working_dir === "R") {
7306
- const detail = fromPathRegex.exec(path7) || [null, path7, path7];
7140
+ const detail = fromPathRegex.exec(path8) || [null, path8, path8];
7307
7141
  this.from = detail[2] || "";
7308
7142
  this.path = detail[1] || "";
7309
7143
  }
@@ -7334,14 +7168,14 @@ function splitLine(result, lineStr) {
7334
7168
  default:
7335
7169
  return;
7336
7170
  }
7337
- function data(index, workingDir, path7) {
7171
+ function data(index, workingDir, path8) {
7338
7172
  const raw = `${index}${workingDir}`;
7339
7173
  const handler = parsers6.get(raw);
7340
7174
  if (handler) {
7341
- handler(result, path7);
7175
+ handler(result, path8);
7342
7176
  }
7343
7177
  if (raw !== "##" && raw !== "!!") {
7344
- result.files.push(new FileStatusSummary(path7, index, workingDir));
7178
+ result.files.push(new FileStatusSummary(path8, index, workingDir));
7345
7179
  }
7346
7180
  }
7347
7181
  }
@@ -7654,9 +7488,9 @@ var init_simple_git_api = __esm({
7654
7488
  next
7655
7489
  );
7656
7490
  }
7657
- hashObject(path7, write) {
7491
+ hashObject(path8, write) {
7658
7492
  return this._runTask(
7659
- hashObjectTask(path7, write === true),
7493
+ hashObjectTask(path8, write === true),
7660
7494
  trailingFunctionArgument(arguments)
7661
7495
  );
7662
7496
  }
@@ -8009,8 +7843,8 @@ var init_branch = __esm({
8009
7843
  }
8010
7844
  });
8011
7845
  function toPath(input) {
8012
- const path7 = input.trim().replace(/^["']|["']$/g, "");
8013
- return path7 && normalize(path7);
7846
+ const path8 = input.trim().replace(/^["']|["']$/g, "");
7847
+ return path8 && normalize(path8);
8014
7848
  }
8015
7849
  var parseCheckIgnore;
8016
7850
  var init_CheckIgnore = __esm({
@@ -8324,8 +8158,8 @@ __export(sub_module_exports, {
8324
8158
  subModuleTask: () => subModuleTask,
8325
8159
  updateSubModuleTask: () => updateSubModuleTask
8326
8160
  });
8327
- function addSubModuleTask(repo, path7) {
8328
- return subModuleTask(["add", repo, path7]);
8161
+ function addSubModuleTask(repo, path8) {
8162
+ return subModuleTask(["add", repo, path8]);
8329
8163
  }
8330
8164
  function initSubModuleTask(customArgs) {
8331
8165
  return subModuleTask(["init", ...customArgs]);
@@ -8655,8 +8489,8 @@ var require_git = __commonJS2({
8655
8489
  }
8656
8490
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
8657
8491
  };
8658
- Git2.prototype.submoduleAdd = function(repo, path7, then) {
8659
- return this._runTask(addSubModuleTask2(repo, path7), trailingFunctionArgument2(arguments));
8492
+ Git2.prototype.submoduleAdd = function(repo, path8, then) {
8493
+ return this._runTask(addSubModuleTask2(repo, path8), trailingFunctionArgument2(arguments));
8660
8494
  };
8661
8495
  Git2.prototype.submoduleUpdate = function(args, then) {
8662
8496
  return this._runTask(
@@ -9257,22 +9091,22 @@ function createGitClient(baseDir, options) {
9257
9091
 
9258
9092
  // ../git/dist/lock-detector.js
9259
9093
  import { execFile } from "child_process";
9260
- import fs3 from "fs/promises";
9261
- import path4 from "path";
9094
+ import fs4 from "fs/promises";
9095
+ import path5 from "path";
9262
9096
  import { promisify } from "util";
9263
9097
  var execFileAsync = promisify(execFile);
9264
9098
  async function getIndexLockPath(repoPath) {
9265
9099
  try {
9266
9100
  const { stdout } = await execFileAsync("git", ["rev-parse", "--git-path", "index.lock"], { cwd: repoPath });
9267
- return path4.resolve(repoPath, stdout.trim());
9101
+ return path5.resolve(repoPath, stdout.trim());
9268
9102
  } catch {
9269
- return path4.join(repoPath, ".git", "index.lock");
9103
+ return path5.join(repoPath, ".git", "index.lock");
9270
9104
  }
9271
9105
  }
9272
9106
  async function getLockInfo(repoPath) {
9273
9107
  const lockPath = await getIndexLockPath(repoPath);
9274
9108
  try {
9275
- const stat = await fs3.stat(lockPath);
9109
+ const stat = await fs4.stat(lockPath);
9276
9110
  return {
9277
9111
  path: lockPath,
9278
9112
  ageMs: Date.now() - stat.mtimeMs
@@ -9283,7 +9117,7 @@ async function getLockInfo(repoPath) {
9283
9117
  }
9284
9118
  async function removeLock(repoPath) {
9285
9119
  const lockPath = await getIndexLockPath(repoPath);
9286
- await fs3.rm(lockPath, { force: true });
9120
+ await fs4.rm(lockPath, { force: true });
9287
9121
  }
9288
9122
  async function isLocked(repoPath) {
9289
9123
  return await getLockInfo(repoPath) !== null;
@@ -9450,10 +9284,137 @@ async function getHeadSha(baseDir, options) {
9450
9284
  import { mkdir as mkdir3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
9451
9285
  import { join as join5 } from "path";
9452
9286
 
9287
+ // ../shared/dist/index.js
9288
+ var consoleLogger = {
9289
+ info: (_message, _data) => {
9290
+ },
9291
+ debug: (_message, _data) => {
9292
+ },
9293
+ error: (_message, _data) => {
9294
+ },
9295
+ warn: (_message, _data) => {
9296
+ }
9297
+ };
9298
+ var Saga = class {
9299
+ completedSteps = [];
9300
+ currentStepName = "unknown";
9301
+ stepTimings = [];
9302
+ log;
9303
+ constructor(logger) {
9304
+ this.log = logger ?? consoleLogger;
9305
+ }
9306
+ /**
9307
+ * Run the saga with the given input.
9308
+ * Returns a discriminated union result - either success with data or failure with error details.
9309
+ */
9310
+ async run(input) {
9311
+ this.completedSteps = [];
9312
+ this.currentStepName = "unknown";
9313
+ this.stepTimings = [];
9314
+ const sagaStart = performance.now();
9315
+ this.log.info("Starting saga", { sagaName: this.constructor.name });
9316
+ try {
9317
+ const result = await this.execute(input);
9318
+ const totalDuration = performance.now() - sagaStart;
9319
+ this.log.debug("Saga completed successfully", {
9320
+ sagaName: this.constructor.name,
9321
+ stepsCompleted: this.completedSteps.length,
9322
+ totalDurationMs: Math.round(totalDuration),
9323
+ stepTimings: this.stepTimings
9324
+ });
9325
+ return { success: true, data: result };
9326
+ } catch (error) {
9327
+ this.log.error("Saga failed, initiating rollback", {
9328
+ sagaName: this.constructor.name,
9329
+ failedStep: this.currentStepName,
9330
+ error: error instanceof Error ? error.message : String(error)
9331
+ });
9332
+ await this.rollback();
9333
+ return {
9334
+ success: false,
9335
+ error: error instanceof Error ? error.message : String(error),
9336
+ failedStep: this.currentStepName
9337
+ };
9338
+ }
9339
+ }
9340
+ /**
9341
+ * Execute a step with its rollback action.
9342
+ * If the step succeeds, its rollback action is stored for potential rollback.
9343
+ * The step name is automatically tracked for error reporting.
9344
+ *
9345
+ * @param config - Step configuration with name, execute, and rollback functions
9346
+ * @returns The result of the execute function
9347
+ * @throws Re-throws any error from the execute function (triggers rollback)
9348
+ */
9349
+ async step(config) {
9350
+ this.currentStepName = config.name;
9351
+ this.log.debug(`Executing step: ${config.name}`);
9352
+ const stepStart = performance.now();
9353
+ const result = await config.execute();
9354
+ const durationMs = Math.round(performance.now() - stepStart);
9355
+ this.stepTimings.push({ name: config.name, durationMs });
9356
+ this.log.debug(`Step completed: ${config.name}`, { durationMs });
9357
+ this.completedSteps.push({
9358
+ name: config.name,
9359
+ rollback: () => config.rollback(result)
9360
+ });
9361
+ return result;
9362
+ }
9363
+ /**
9364
+ * Execute a step that doesn't need rollback.
9365
+ * Useful for read-only operations or operations that are idempotent.
9366
+ * The step name is automatically tracked for error reporting.
9367
+ *
9368
+ * @param name - Step name for logging and error tracking
9369
+ * @param execute - The action to execute
9370
+ * @returns The result of the execute function
9371
+ */
9372
+ async readOnlyStep(name, execute) {
9373
+ this.currentStepName = name;
9374
+ this.log.debug(`Executing read-only step: ${name}`);
9375
+ const stepStart = performance.now();
9376
+ const result = await execute();
9377
+ const durationMs = Math.round(performance.now() - stepStart);
9378
+ this.stepTimings.push({ name, durationMs });
9379
+ this.log.debug(`Read-only step completed: ${name}`, { durationMs });
9380
+ return result;
9381
+ }
9382
+ /**
9383
+ * Roll back all completed steps in reverse order.
9384
+ * Rollback errors are logged but don't stop the rollback of other steps.
9385
+ */
9386
+ async rollback() {
9387
+ this.log.info("Rolling back saga", {
9388
+ stepsToRollback: this.completedSteps.length
9389
+ });
9390
+ const stepsReversed = [...this.completedSteps].reverse();
9391
+ for (const step of stepsReversed) {
9392
+ try {
9393
+ this.log.debug(`Rolling back step: ${step.name}`);
9394
+ await step.rollback();
9395
+ this.log.debug(`Step rolled back: ${step.name}`);
9396
+ } catch (error) {
9397
+ this.log.error(`Failed to rollback step: ${step.name}`, {
9398
+ error: error instanceof Error ? error.message : String(error)
9399
+ });
9400
+ }
9401
+ }
9402
+ this.log.info("Rollback completed", {
9403
+ stepsAttempted: this.completedSteps.length
9404
+ });
9405
+ }
9406
+ /**
9407
+ * Get the number of completed steps (useful for testing)
9408
+ */
9409
+ getCompletedStepCount() {
9410
+ return this.completedSteps.length;
9411
+ }
9412
+ };
9413
+
9453
9414
  // ../git/dist/sagas/tree.js
9454
9415
  import { existsSync as existsSync4 } from "fs";
9455
- import * as fs5 from "fs/promises";
9456
- import * as path6 from "path";
9416
+ import * as fs6 from "fs/promises";
9417
+ import * as path7 from "path";
9457
9418
  import * as tar from "tar";
9458
9419
 
9459
9420
  // ../git/dist/git-saga.js
@@ -9479,14 +9440,14 @@ var CaptureTreeSaga = class extends GitSaga {
9479
9440
  tempIndexPath = null;
9480
9441
  async executeGitOperations(input) {
9481
9442
  const { baseDir, lastTreeHash, archivePath, signal } = input;
9482
- const tmpDir = path6.join(baseDir, ".git", "twig-tmp");
9443
+ const tmpDir = path7.join(baseDir, ".git", "twig-tmp");
9483
9444
  await this.step({
9484
9445
  name: "create_tmp_dir",
9485
- execute: () => fs5.mkdir(tmpDir, { recursive: true }),
9446
+ execute: () => fs6.mkdir(tmpDir, { recursive: true }),
9486
9447
  rollback: async () => {
9487
9448
  }
9488
9449
  });
9489
- this.tempIndexPath = path6.join(tmpDir, `index-${Date.now()}`);
9450
+ this.tempIndexPath = path7.join(tmpDir, `index-${Date.now()}`);
9490
9451
  const tempIndexGit = this.git.env({
9491
9452
  ...process.env,
9492
9453
  GIT_INDEX_FILE: this.tempIndexPath
@@ -9496,7 +9457,7 @@ var CaptureTreeSaga = class extends GitSaga {
9496
9457
  execute: () => tempIndexGit.raw(["read-tree", "HEAD"]),
9497
9458
  rollback: async () => {
9498
9459
  if (this.tempIndexPath) {
9499
- await fs5.rm(this.tempIndexPath, { force: true }).catch(() => {
9460
+ await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
9500
9461
  });
9501
9462
  }
9502
9463
  }
@@ -9505,7 +9466,7 @@ var CaptureTreeSaga = class extends GitSaga {
9505
9466
  const treeHash = await this.readOnlyStep("write_tree", () => tempIndexGit.raw(["write-tree"]));
9506
9467
  if (lastTreeHash && treeHash === lastTreeHash) {
9507
9468
  this.log.debug("No changes since last capture", { treeHash });
9508
- await fs5.rm(this.tempIndexPath, { force: true }).catch(() => {
9469
+ await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
9509
9470
  });
9510
9471
  return { snapshot: null, changed: false };
9511
9472
  }
@@ -9517,7 +9478,7 @@ var CaptureTreeSaga = class extends GitSaga {
9517
9478
  }
9518
9479
  });
9519
9480
  const changes = await this.readOnlyStep("get_changes", () => this.getChanges(this.git, baseCommit, treeHash));
9520
- await fs5.rm(this.tempIndexPath, { force: true }).catch(() => {
9481
+ await fs6.rm(this.tempIndexPath, { force: true }).catch(() => {
9521
9482
  });
9522
9483
  const snapshot = {
9523
9484
  treeHash,
@@ -9541,15 +9502,15 @@ var CaptureTreeSaga = class extends GitSaga {
9541
9502
  if (filesToArchive.length === 0) {
9542
9503
  return void 0;
9543
9504
  }
9544
- const existingFiles = filesToArchive.filter((f) => existsSync4(path6.join(baseDir, f)));
9505
+ const existingFiles = filesToArchive.filter((f) => existsSync4(path7.join(baseDir, f)));
9545
9506
  if (existingFiles.length === 0) {
9546
9507
  return void 0;
9547
9508
  }
9548
9509
  await this.step({
9549
9510
  name: "create_archive",
9550
9511
  execute: async () => {
9551
- const archiveDir = path6.dirname(archivePath);
9552
- await fs5.mkdir(archiveDir, { recursive: true });
9512
+ const archiveDir = path7.dirname(archivePath);
9513
+ await fs6.mkdir(archiveDir, { recursive: true });
9553
9514
  await tar.create({
9554
9515
  gzip: true,
9555
9516
  file: archivePath,
@@ -9557,7 +9518,7 @@ var CaptureTreeSaga = class extends GitSaga {
9557
9518
  }, existingFiles);
9558
9519
  },
9559
9520
  rollback: async () => {
9560
- await fs5.rm(archivePath, { force: true }).catch(() => {
9521
+ await fs6.rm(archivePath, { force: true }).catch(() => {
9561
9522
  });
9562
9523
  }
9563
9524
  });
@@ -9656,9 +9617,9 @@ var ApplyTreeSaga = class extends GitSaga {
9656
9617
  const filesToExtract = changes.filter((c) => c.status !== "D").map((c) => c.path);
9657
9618
  await this.readOnlyStep("backup_existing_files", async () => {
9658
9619
  for (const filePath of filesToExtract) {
9659
- const fullPath = path6.join(baseDir, filePath);
9620
+ const fullPath = path7.join(baseDir, filePath);
9660
9621
  try {
9661
- const content = await fs5.readFile(fullPath);
9622
+ const content = await fs6.readFile(fullPath);
9662
9623
  this.fileBackups.set(filePath, content);
9663
9624
  } catch {
9664
9625
  }
@@ -9675,16 +9636,16 @@ var ApplyTreeSaga = class extends GitSaga {
9675
9636
  },
9676
9637
  rollback: async () => {
9677
9638
  for (const filePath of this.extractedFiles) {
9678
- const fullPath = path6.join(baseDir, filePath);
9639
+ const fullPath = path7.join(baseDir, filePath);
9679
9640
  const backup = this.fileBackups.get(filePath);
9680
9641
  if (backup) {
9681
- const dir = path6.dirname(fullPath);
9682
- await fs5.mkdir(dir, { recursive: true }).catch(() => {
9642
+ const dir = path7.dirname(fullPath);
9643
+ await fs6.mkdir(dir, { recursive: true }).catch(() => {
9683
9644
  });
9684
- await fs5.writeFile(fullPath, backup).catch(() => {
9645
+ await fs6.writeFile(fullPath, backup).catch(() => {
9685
9646
  });
9686
9647
  } else {
9687
- await fs5.rm(fullPath, { force: true }).catch(() => {
9648
+ await fs6.rm(fullPath, { force: true }).catch(() => {
9688
9649
  });
9689
9650
  }
9690
9651
  }
@@ -9692,10 +9653,10 @@ var ApplyTreeSaga = class extends GitSaga {
9692
9653
  });
9693
9654
  }
9694
9655
  for (const change of changes.filter((c) => c.status === "D")) {
9695
- const fullPath = path6.join(baseDir, change.path);
9656
+ const fullPath = path7.join(baseDir, change.path);
9696
9657
  const backupContent = await this.readOnlyStep(`backup_${change.path}`, async () => {
9697
9658
  try {
9698
- return await fs5.readFile(fullPath);
9659
+ return await fs6.readFile(fullPath);
9699
9660
  } catch {
9700
9661
  return null;
9701
9662
  }
@@ -9703,15 +9664,15 @@ var ApplyTreeSaga = class extends GitSaga {
9703
9664
  await this.step({
9704
9665
  name: `delete_${change.path}`,
9705
9666
  execute: async () => {
9706
- await fs5.rm(fullPath, { force: true });
9667
+ await fs6.rm(fullPath, { force: true });
9707
9668
  this.log.debug(`Deleted file: ${change.path}`);
9708
9669
  },
9709
9670
  rollback: async () => {
9710
9671
  if (backupContent) {
9711
- const dir = path6.dirname(fullPath);
9712
- await fs5.mkdir(dir, { recursive: true }).catch(() => {
9672
+ const dir = path7.dirname(fullPath);
9673
+ await fs6.mkdir(dir, { recursive: true }).catch(() => {
9713
9674
  });
9714
- await fs5.writeFile(fullPath, backupContent).catch(() => {
9675
+ await fs6.writeFile(fullPath, backupContent).catch(() => {
9715
9676
  });
9716
9677
  }
9717
9678
  }