@neriros/ralphy 3.10.5 → 3.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/shell/index.js +328 -63
  2. package/package.json +1 -1
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
18928
18928
  import { resolve } from "path";
18929
18929
  function getVersion() {
18930
18930
  try {
18931
- if ("3.10.5")
18932
- return "3.10.5";
18931
+ if ("3.10.6")
18932
+ return "3.10.6";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -101471,6 +101471,7 @@ async function parseAgentArgs(argv) {
101471
101471
  noTmux: false,
101472
101472
  checks: false,
101473
101473
  review: false,
101474
+ agentDebug: false,
101474
101475
  ticketTokens: []
101475
101476
  };
101476
101477
  const state = emptyParseState();
@@ -101593,6 +101594,9 @@ async function parseAgentArgs(argv) {
101593
101594
  case "--debug":
101594
101595
  result2.debug = true;
101595
101596
  break;
101597
+ case "--agent-debug":
101598
+ result2.agentDebug = true;
101599
+ break;
101596
101600
  case "--pre-existing-error-check":
101597
101601
  result2.preExistingErrorCheck = true;
101598
101602
  break;
@@ -101692,6 +101696,7 @@ var init_cli2 = __esm(() => {
101692
101696
  " --checks List mode: show failing CI check names per PR",
101693
101697
  " --review List mode: show unresolved review comment count per PR",
101694
101698
  " --debug List mode: explain why a Linear ticket was not picked up (use with --name)",
101699
+ " --agent-debug After each ticket finishes, run a one-shot self-review and write a report to ~/.ralph/retro/",
101695
101700
  " --help, -h Show this help message",
101696
101701
  "",
101697
101702
  "Examples:",
@@ -104551,6 +104556,15 @@ async function runPostTask(input, deps) {
104551
104556
  emit3(succeeded ? "done" : "gave-up", succeeded ? undefined : `exit ${effectiveCode}`);
104552
104557
  if (!succeeded)
104553
104558
  await recordGaveUp(stateFilePath, log3, changeName);
104559
+ await deps.runRetrospective?.({
104560
+ changeName,
104561
+ cwd: cwd2,
104562
+ changeDir,
104563
+ stateFilePath,
104564
+ branch,
104565
+ issue: issue2,
104566
+ effectiveCode
104567
+ });
104554
104568
  await runWorktreeCleanupPhase({ changeName, cwd: cwd2, projectRoot, useWorktree, effectiveCode, cfg }, { git: git2, log: log3, emit: emit3 });
104555
104569
  await runTeardownPhase({ cwd: cwd2, teardownScript: cfg.teardownScript }, { runScript, log: log3, emit: emit3 });
104556
104570
  return effectiveCode;
@@ -105218,6 +105232,32 @@ class AgentCoordinator {
105218
105232
  if (!this.deps.syncTasks || !this.deps.getIterationCount)
105219
105233
  return;
105220
105234
  for (const w of this.workers) {
105235
+ if (this.deps.getTasksFingerprint) {
105236
+ let fingerprint;
105237
+ try {
105238
+ fingerprint = await this.deps.getTasksFingerprint(w.changeName);
105239
+ } catch (err) {
105240
+ this.deps.onLog(`! tasks fingerprint read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
105241
+ continue;
105242
+ }
105243
+ if (fingerprint === null || fingerprint === w.lastSyncedTasksFingerprint) {
105244
+ continue;
105245
+ }
105246
+ let iteration;
105247
+ try {
105248
+ iteration = await this.deps.getIterationCount(w.changeName);
105249
+ } catch (err) {
105250
+ this.deps.onLog(`! iteration count read failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
105251
+ continue;
105252
+ }
105253
+ try {
105254
+ await this.deps.syncTasks(w, iteration);
105255
+ w.lastSyncedTasksFingerprint = fingerprint;
105256
+ } catch (err) {
105257
+ this.deps.onLog(`! sync-tasks (poll) failed for ${w.issueIdentifier}: ${err.message}`, "yellow");
105258
+ }
105259
+ continue;
105260
+ }
105221
105261
  let count;
105222
105262
  try {
105223
105263
  count = await this.deps.getIterationCount(w.changeName);
@@ -105497,6 +105537,7 @@ class AgentCoordinator {
105497
105537
  kill: handle.kill,
105498
105538
  lastReportedIteration: 0,
105499
105539
  lastSyncedIteration: 0,
105540
+ lastSyncedTasksFingerprint: null,
105500
105541
  restarting: false,
105501
105542
  reapedForAwaiting: false
105502
105543
  };
@@ -105660,6 +105701,7 @@ class AgentCoordinator {
105660
105701
  kill: () => {},
105661
105702
  lastReportedIteration: 0,
105662
105703
  lastSyncedIteration: 0,
105704
+ lastSyncedTasksFingerprint: null,
105663
105705
  restarting: false,
105664
105706
  reapedForAwaiting: false
105665
105707
  };
@@ -107767,8 +107809,190 @@ var init_default2 = __esm(() => {
107767
107809
  init_log();
107768
107810
  });
107769
107811
 
107812
+ // apps/agent/src/agent/state/agent-run-state.ts
107813
+ import { basename as basename3, join as join29 } from "path";
107814
+ import { homedir as homedir6 } from "os";
107815
+ import { mkdir as mkdir11, writeFile } from "fs/promises";
107816
+ function agentRunStatePath(projectRoot) {
107817
+ return join29(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
107818
+ }
107819
+ async function writeAgentRunState(state) {
107820
+ const path = agentRunStatePath(state.projectRoot);
107821
+ try {
107822
+ await mkdir11(join29(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
107823
+ await writeFile(path, JSON.stringify(state, null, 2) + `
107824
+ `, "utf-8");
107825
+ } catch {}
107826
+ }
107827
+ var init_agent_run_state = () => {};
107828
+
107829
+ // packages/retro/src/disposition.ts
107830
+ function dispositionFromExitCode(code) {
107831
+ switch (code) {
107832
+ case 0:
107833
+ return "done";
107834
+ case NO_CHANGES_EXIT2:
107835
+ return "no-changes";
107836
+ case CI_FAILED_EXIT2:
107837
+ return "ci-failed";
107838
+ case PR_FAILED_EXIT2:
107839
+ return "pr-failed";
107840
+ default:
107841
+ return "error";
107842
+ }
107843
+ }
107844
+ var CI_FAILED_EXIT2 = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
107845
+
107846
+ // packages/retro/src/paths.ts
107847
+ import { homedir as homedir7 } from "os";
107848
+ import { mkdir as mkdir12 } from "fs/promises";
107849
+ import { join as join30 } from "path";
107850
+ function retroDir() {
107851
+ return join30(homedir7(), ".ralph", "retro");
107852
+ }
107853
+ async function resolveRetroOutputPath(identifier, date5, dir = retroDir()) {
107854
+ await mkdir12(dir, { recursive: true });
107855
+ const base2 = join30(dir, `${identifier}-${date5}.md`);
107856
+ if (!await Bun.file(base2).exists())
107857
+ return base2;
107858
+ for (let n = 2;; n++) {
107859
+ const candidate = join30(dir, `${identifier}-${date5}-${n}.md`);
107860
+ if (!await Bun.file(candidate).exists())
107861
+ return candidate;
107862
+ }
107863
+ }
107864
+ var init_paths2 = () => {};
107865
+
107866
+ // packages/retro/src/prompt.ts
107867
+ function buildRetroPrompt(ctx, outputPath) {
107868
+ const disposition = dispositionFromExitCode(ctx.exitCode);
107869
+ const { paths } = ctx;
107870
+ const line = (label, value) => value ? `- ${label}: ${value}` : `- ${label}: (unavailable \u2014 note this in the report)`;
107871
+ return [
107872
+ `You are a retrospective analysis agent reviewing a finished automated ticket run.`,
107873
+ `Your job is to read the run's artifacts and write a thorough, honest self-review`,
107874
+ `to a markdown file. You are NOT fixing anything \u2014 this is analysis only.`,
107875
+ ``,
107876
+ `## Ticket`,
107877
+ ``,
107878
+ `- Identifier: ${ctx.identifier}`,
107879
+ `- Change name: ${ctx.changeName}`,
107880
+ `- Terminal disposition: ${disposition} (worker exit code ${ctx.exitCode})`,
107881
+ ctx.prUrl ? `- Pull request: ${ctx.prUrl}` : `- Pull request: none was opened`,
107882
+ `- Date: ${ctx.date}`,
107883
+ ``,
107884
+ `### Ticket details`,
107885
+ ``,
107886
+ ctx.ticketDigest,
107887
+ ``,
107888
+ `## Data sources`,
107889
+ ``,
107890
+ `Read whatever of the following exist. If a path is missing or empty, say so`,
107891
+ `explicitly in the report rather than guessing.`,
107892
+ ``,
107893
+ line("Change directory (proposal/design/tasks/specs)", paths.changeDir),
107894
+ line("Loop state file", paths.stateFilePath),
107895
+ line("Worker log", paths.logFile),
107896
+ line("JSON event log", paths.jsonLogFile),
107897
+ line("Agent run state", paths.agentStateFile),
107898
+ ctx.prUrl ? `- You may inspect the PR read-only with \`gh pr view ${ctx.prUrl}\` and \`gh pr diff ${ctx.prUrl}\`.` : `- No PR exists; skip the PR section and note "no PR".`,
107899
+ ``,
107900
+ `## Required report structure`,
107901
+ ``,
107902
+ `Write GitHub-flavored markdown with these sections:`,
107903
+ `1. **Summary** \u2014 what the ticket asked for and how the run ended.`,
107904
+ `2. **What went well** \u2014 concrete things the run did right.`,
107905
+ `3. **What went wrong / friction** \u2014 failures, retries, wasted iterations,`,
107906
+ ` wrong turns, anything that cost time or quality.`,
107907
+ `4. **Root-cause analysis** \u2014 for each problem, why it happened.`,
107908
+ `5. **Recommendations** \u2014 specific, actionable improvements (to the prompt,`,
107909
+ ` the tasks, the codebase, or the workflow).`,
107910
+ `6. **Data gaps** \u2014 which data sources were unavailable or unread.`,
107911
+ ``,
107912
+ `## Output`,
107913
+ ``,
107914
+ `Write the complete report to this exact path using your file-write tool:`,
107915
+ ``,
107916
+ ` ${outputPath}`,
107917
+ ``,
107918
+ `## Hard rules`,
107919
+ ``,
107920
+ `- Do NOT run any git mutation: no commit, add, push, rebase, reset, checkout,`,
107921
+ ` branch, merge, tag, or stash.`,
107922
+ `- Do NOT create, edit, comment on, close, or merge any pull request or issue.`,
107923
+ `- Do NOT modify any source file. The ONLY file you may write is the report at`,
107924
+ ` the path above.`,
107925
+ `- Read-only inspection commands (\`git log\`, \`git diff\`, \`gh pr view\`,`,
107926
+ ` \`gh pr diff\`, reading files) are allowed.`
107927
+ ].join(`
107928
+ `);
107929
+ }
107930
+ var init_prompt = () => {};
107931
+
107932
+ // packages/retro/src/retro.ts
107933
+ async function runRetrospective(ctx, deps) {
107934
+ const { log: log3, runEngine: runEngine2, seen } = deps;
107935
+ const disposition = dispositionFromExitCode(ctx.exitCode);
107936
+ const key = `${ctx.identifier}:${disposition}:${ctx.date}`;
107937
+ if (seen.has(key)) {
107938
+ log3(` retrospective skipped for ${ctx.identifier} (already generated this run)`, "gray");
107939
+ return { written: false, skipped: "duplicate", disposition };
107940
+ }
107941
+ seen.add(key);
107942
+ try {
107943
+ const outputPath = await resolveRetroOutputPath(ctx.identifier, ctx.date);
107944
+ const prompt = buildRetroPrompt(ctx, outputPath);
107945
+ log3(` running retrospective for ${ctx.identifier} (${disposition}) \u2192 ${outputPath}`, "cyan");
107946
+ await runEngine2({
107947
+ engine: ctx.engine,
107948
+ model: ctx.model,
107949
+ prompt,
107950
+ cwd: ctx.cwd,
107951
+ onOutput: (l) => log3(l, "gray")
107952
+ });
107953
+ const written = await Bun.file(outputPath).exists();
107954
+ if (written) {
107955
+ log3(` retrospective written: ${outputPath}`, "green");
107956
+ } else {
107957
+ log3(`! retrospective engine finished but no report was written at ${outputPath}`, "yellow");
107958
+ }
107959
+ return { written, outputPath, disposition };
107960
+ } catch (err) {
107961
+ log3(`! retrospective failed for ${ctx.identifier}: ${err.message}`, "yellow");
107962
+ return { written: false, disposition };
107963
+ }
107964
+ }
107965
+ var init_retro = __esm(() => {
107966
+ init_paths2();
107967
+ init_prompt();
107968
+ init_paths2();
107969
+ init_prompt();
107970
+ });
107971
+
107770
107972
  // apps/agent/src/agent/wire/spawn/worker.ts
107771
- import { join as join29 } from "path";
107973
+ import { join as join31 } from "path";
107974
+ function localDateStamp(d) {
107975
+ const y = d.getFullYear();
107976
+ const m = String(d.getMonth() + 1).padStart(2, "0");
107977
+ const day = String(d.getDate()).padStart(2, "0");
107978
+ return `${y}-${m}-${day}`;
107979
+ }
107980
+ function buildTicketDigest(issue2, comments) {
107981
+ if (!issue2)
107982
+ return "(ticket details unavailable)";
107983
+ const lines = [`Title: ${issue2.title}`, "", issue2.description?.trim() || "(no description)"];
107984
+ if (comments.length > 0) {
107985
+ lines.push("", "Comments:");
107986
+ for (const c of comments) {
107987
+ lines.push(`- ${c.user?.name ?? "unknown"}: ${c.body}`);
107988
+ }
107989
+ }
107990
+ return lines.join(`
107991
+ `);
107992
+ }
107993
+ function retroDepEntry(agentDebug, hook) {
107994
+ return agentDebug ? { runRetrospective: hook } : {};
107995
+ }
107772
107996
  function createSpawnWorker(input) {
107773
107997
  const {
107774
107998
  args,
@@ -107847,10 +108071,52 @@ function createSpawnWorker(input) {
107847
108071
  c.push("--from-agent");
107848
108072
  return c;
107849
108073
  }
108074
+ const retroSeen = new Set;
108075
+ const runRetrospectiveHook = async (info) => {
108076
+ try {
108077
+ const identifier = info.issue?.identifier ?? info.changeName;
108078
+ const prUrl = prByChange?.get(info.changeName) ?? null;
108079
+ let digest = "(ticket details unavailable)";
108080
+ if (info.issue) {
108081
+ let comments = [];
108082
+ try {
108083
+ comments = await fetchIssueComments(apiKey, info.issue.id);
108084
+ } catch {}
108085
+ digest = buildTicketDigest(info.issue, comments);
108086
+ }
108087
+ const engine = args.engineSet ? args.engine : cfg.engine;
108088
+ const model = args.engineSet ? args.model : cfg.model;
108089
+ const ctx = {
108090
+ identifier,
108091
+ changeName: info.changeName,
108092
+ cwd: info.cwd,
108093
+ engine,
108094
+ model,
108095
+ exitCode: info.effectiveCode,
108096
+ prUrl,
108097
+ date: localDateStamp(new Date),
108098
+ ticketDigest: digest,
108099
+ paths: {
108100
+ changeDir: info.changeDir,
108101
+ stateFilePath: info.stateFilePath,
108102
+ logFile: join31(logsDir, `${info.changeName}.log`),
108103
+ jsonLogFile: args.jsonLogFile ?? null,
108104
+ agentStateFile: agentRunStatePath(projectRoot)
108105
+ }
108106
+ };
108107
+ await runRetrospective(ctx, {
108108
+ runEngine: (opts) => runEngine(opts),
108109
+ log: onLog,
108110
+ seen: retroSeen
108111
+ });
108112
+ } catch (err) {
108113
+ onLog(`! retrospective failed: ${err.message}`, "yellow");
108114
+ }
108115
+ };
107850
108116
  return function spawnWorker(changeName, _issue, trigger) {
107851
108117
  const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
107852
108118
  const injected = runners?.spawnWorker;
107853
- const missionTasksPath = join29(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
108119
+ const missionTasksPath = join31(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
107854
108120
  const prevTasksPromise = (async () => {
107855
108121
  const f2 = Bun.file(missionTasksPath);
107856
108122
  return await f2.exists() ? await f2.text() : "";
@@ -107858,7 +108124,7 @@ function createSpawnWorker(input) {
107858
108124
  let logFilePath;
107859
108125
  let handle;
107860
108126
  if (injected) {
107861
- logFilePath = join29(logsDir, `${changeName}.log`);
108127
+ logFilePath = join31(logsDir, `${changeName}.log`);
107862
108128
  handle = injected(buildTaskCmdFor(changeName), cwd2);
107863
108129
  } else {
107864
108130
  const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, logsDir, onWorkerOutput, `spawn at ${new Date().toISOString()}`);
@@ -107880,7 +108146,7 @@ function createSpawnWorker(input) {
107880
108146
  const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
107881
108147
  const wrapped = handle.exited.then(async (code) => {
107882
108148
  const workerLayout = projectLayout(cwd2);
107883
- const validateSpecPath = join29(workerLayout.changeDir(changeName), "specs", "validate.md");
108149
+ const validateSpecPath = join31(workerLayout.changeDir(changeName), "specs", "validate.md");
107884
108150
  const hasValidateSpec = await Bun.file(validateSpecPath).exists();
107885
108151
  const wantValidateOnly = hasValidateSpec && !wantPrBase;
107886
108152
  if (hasValidateSpec) {
@@ -107965,6 +108231,7 @@ function createSpawnWorker(input) {
107965
108231
  git: gitRunner,
107966
108232
  log: onLog,
107967
108233
  runScript,
108234
+ ...retroDepEntry(args.agentDebug, runRetrospectiveHook),
107968
108235
  registerPr: (cn, url2) => onPrRegistered(cn, url2),
107969
108236
  ...onWorkerPhase && {
107970
108237
  onPhase: (phase2, detail) => onWorkerPhase(changeName, phase2, detail)
@@ -108000,6 +108267,9 @@ var init_worker = __esm(() => {
108000
108267
  init_runners();
108001
108268
  init_pr_helpers();
108002
108269
  init_wait_for_mergeability();
108270
+ init_agent_run_state();
108271
+ init_retro();
108272
+ init_engine();
108003
108273
  });
108004
108274
 
108005
108275
  // apps/agent/src/agent/baseline/runner.ts
@@ -108342,8 +108612,8 @@ var init_linear_sync = __esm(() => {
108342
108612
  });
108343
108613
 
108344
108614
  // apps/agent/src/agent/linear-sync/comment-sync.ts
108345
- import { dirname as dirname13, join as join30 } from "path";
108346
- import { mkdir as mkdir11, rename, unlink as unlink2 } from "fs/promises";
108615
+ import { dirname as dirname13, join as join32 } from "path";
108616
+ import { mkdir as mkdir13, rename, unlink as unlink2 } from "fs/promises";
108347
108617
  async function readStateJson(statePath) {
108348
108618
  const file2 = Bun.file(statePath);
108349
108619
  if (!await file2.exists())
@@ -108355,7 +108625,7 @@ async function readStateJson(statePath) {
108355
108625
  }
108356
108626
  }
108357
108627
  async function writeStateJson(statePath, state) {
108358
- await mkdir11(dirname13(statePath), { recursive: true });
108628
+ await mkdir13(dirname13(statePath), { recursive: true });
108359
108629
  const tmp = `${statePath}.tmp-${process.pid}-${writeStateSeq++}`;
108360
108630
  try {
108361
108631
  await Bun.write(tmp, JSON.stringify(state, null, 2) + `
@@ -108394,7 +108664,7 @@ function isCommentNotFoundError(err) {
108394
108664
  return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
108395
108665
  }
108396
108666
  async function readTasksMd(changeDir, log3) {
108397
- const file2 = Bun.file(join30(changeDir, "tasks.md"));
108667
+ const file2 = Bun.file(join32(changeDir, "tasks.md"));
108398
108668
  if (!await file2.exists()) {
108399
108669
  log3(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
108400
108670
  return null;
@@ -108502,14 +108772,14 @@ async function postPlanCommentOnce(deps) {
108502
108772
  const check2 = parsePlanningSection(tasksMd);
108503
108773
  if (!check2.allChecked)
108504
108774
  return null;
108505
- const proposalPath = join30(deps.changeDir, "proposal.md");
108775
+ const proposalPath = join32(deps.changeDir, "proposal.md");
108506
108776
  const why = await readSection(proposalPath, "Why");
108507
108777
  const whatChanges = await readSection(proposalPath, "What Changes");
108508
108778
  if (!why && !whatChanges) {
108509
108779
  deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
108510
108780
  return null;
108511
108781
  }
108512
- const designSummary = await readFirstParagraph(join30(deps.changeDir, "design.md"));
108782
+ const designSummary = await readFirstParagraph(join32(deps.changeDir, "design.md"));
108513
108783
  const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
108514
108784
  if (why) {
108515
108785
  parts.push("", "**Why**", "", why);
@@ -260785,7 +261055,7 @@ var init_render_pdf = __esm(() => {
260785
261055
  });
260786
261056
 
260787
261057
  // apps/agent/src/agent/linear-sync/spec-attachments.ts
260788
- import { dirname as dirname14, join as join31 } from "path";
261058
+ import { dirname as dirname14, join as join33 } from "path";
260789
261059
  function describeLinearError(err) {
260790
261060
  const e = err;
260791
261061
  const parts = [e.message ?? String(err)];
@@ -260877,7 +261147,7 @@ async function syncSlot(deps, slot) {
260877
261147
  const [primaryName, ...trailingNames] = spec.sourceFiles;
260878
261148
  if (!primaryName)
260879
261149
  return;
260880
- const primary = Bun.file(join31(deps.changeDir, primaryName));
261150
+ const primary = Bun.file(join33(deps.changeDir, primaryName));
260881
261151
  if (!await primary.exists()) {
260882
261152
  deps.log(` spec-attachments: ${primaryName} missing, skipping`, "gray");
260883
261153
  return;
@@ -260896,7 +261166,7 @@ async function syncSlot(deps, slot) {
260896
261166
  const parts = [primaryBytes];
260897
261167
  const enc = new TextEncoder;
260898
261168
  for (const name of trailingNames) {
260899
- const f2 = Bun.file(join31(deps.changeDir, name));
261169
+ const f2 = Bun.file(join33(deps.changeDir, name));
260900
261170
  if (!await f2.exists())
260901
261171
  continue;
260902
261172
  try {
@@ -261161,9 +261431,9 @@ var init_comment_sync2 = __esm(() => {
261161
261431
  });
261162
261432
 
261163
261433
  // apps/agent/src/features/pr-tracker/state.ts
261164
- import { join as join32 } from "path";
261434
+ import { join as join34 } from "path";
261165
261435
  async function readState2(projectRoot) {
261166
- const path = join32(projectRoot, PR_TRACKER_STATE_RELPATH);
261436
+ const path = join34(projectRoot, PR_TRACKER_STATE_RELPATH);
261167
261437
  const file2 = Bun.file(path);
261168
261438
  if (!await file2.exists())
261169
261439
  return {};
@@ -261179,7 +261449,7 @@ async function readState2(projectRoot) {
261179
261449
  }
261180
261450
  }
261181
261451
  async function writeState2(projectRoot, state) {
261182
- const path = join32(projectRoot, PR_TRACKER_STATE_RELPATH);
261452
+ const path = join34(projectRoot, PR_TRACKER_STATE_RELPATH);
261183
261453
  await Bun.write(path, JSON.stringify(state, null, 2));
261184
261454
  }
261185
261455
  var PR_TRACKER_STATE_RELPATH = ".ralph/pr-tracker-state.json";
@@ -261258,7 +261528,7 @@ var init_pr_tracker = __esm(() => {
261258
261528
  });
261259
261529
 
261260
261530
  // apps/agent/src/agent/wire.ts
261261
- import { join as join33 } from "path";
261531
+ import { join as join35 } from "path";
261262
261532
  function buildAgentCoordinator(input) {
261263
261533
  const {
261264
261534
  args,
@@ -261277,7 +261547,7 @@ function buildAgentCoordinator(input) {
261277
261547
  onWorkerCmd,
261278
261548
  onAwaitingTicket
261279
261549
  } = input;
261280
- const logsDir = join33(projectRoot, ".ralph", "logs");
261550
+ const logsDir = join35(projectRoot, ".ralph", "logs");
261281
261551
  const bus = createBus();
261282
261552
  subscribeAgentDiag(bus, onLog);
261283
261553
  const diag = (area, message, color) => {
@@ -261493,6 +261763,18 @@ function buildAgentCoordinator(input) {
261493
261763
  const json2 = await file2.json();
261494
261764
  return json2.iteration ?? 0;
261495
261765
  },
261766
+ getTasksFingerprint: async (changeName) => {
261767
+ const root = cwdByChange.get(changeName) ?? projectRoot;
261768
+ const changeDir = projectLayout(root).changeDir(changeName);
261769
+ const parts = [];
261770
+ for (const name of ["tasks.md", "proposal.md", "design.md"]) {
261771
+ const file2 = Bun.file(join35(changeDir, name));
261772
+ if (!await file2.exists())
261773
+ continue;
261774
+ parts.push(`${name}:${file2.lastModified}:${file2.size}`);
261775
+ }
261776
+ return parts.length > 0 ? parts.join("|") : null;
261777
+ },
261496
261778
  ...commentSync.enabled && commentSync.syncTasks ? { syncTasks: commentSync.syncTasks } : {},
261497
261779
  ...commentSync.enabled && commentSync.onSteeringAppended ? { onSteeringAppended: commentSync.onSteeringAppended } : {}
261498
261780
  }, {
@@ -261531,7 +261813,7 @@ function buildAgentCoordinator(input) {
261531
261813
  getGaveUpTotal: async () => {
261532
261814
  let total = 0;
261533
261815
  for (const [changeName, root] of cwdByChange) {
261534
- const file2 = Bun.file(join33(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
261816
+ const file2 = Bun.file(join35(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
261535
261817
  if (!await file2.exists())
261536
261818
  continue;
261537
261819
  try {
@@ -261566,14 +261848,14 @@ var init_wire = __esm(() => {
261566
261848
  });
261567
261849
 
261568
261850
  // apps/agent/src/agent/json-log/json-log-file.ts
261569
- import { mkdir as mkdir12, appendFile as appendFile2 } from "fs/promises";
261851
+ import { mkdir as mkdir14, appendFile as appendFile2 } from "fs/promises";
261570
261852
  import { dirname as dirname15 } from "path";
261571
261853
  function createJsonLogFileSink(path) {
261572
261854
  if (!path)
261573
261855
  return { emit: () => {} };
261574
261856
  let chain = (async () => {
261575
261857
  try {
261576
- await mkdir12(dirname15(path), { recursive: true });
261858
+ await mkdir14(dirname15(path), { recursive: true });
261577
261859
  await Bun.write(path, "");
261578
261860
  } catch {}
261579
261861
  })();
@@ -261819,7 +262101,7 @@ var init_output_utils = __esm(() => {
261819
262101
  });
261820
262102
 
261821
262103
  // apps/agent/src/agent/state/worker-state-poll.ts
261822
- import { join as join34 } from "path";
262104
+ import { join as join36 } from "path";
261823
262105
  function parseSubtasks(tasksMd) {
261824
262106
  const out = [];
261825
262107
  let skipSection = false;
@@ -261852,7 +262134,7 @@ function initialWorkerSnapshot() {
261852
262134
  async function readWorkerSnapshot(input) {
261853
262135
  const next = { ...input.prev };
261854
262136
  try {
261855
- const file2 = Bun.file(join34(input.statesDir, input.changeName, ".ralph-state.json"));
262137
+ const file2 = Bun.file(join36(input.statesDir, input.changeName, ".ralph-state.json"));
261856
262138
  if (await file2.exists()) {
261857
262139
  const json2 = await file2.json();
261858
262140
  next.iter = json2.iteration ?? next.iter;
@@ -261861,10 +262143,10 @@ async function readWorkerSnapshot(input) {
261861
262143
  } catch {}
261862
262144
  if (input.changeDir) {
261863
262145
  try {
261864
- const tasksFile = Bun.file(join34(input.changeDir, "tasks.md"));
261865
- const proposalFile = Bun.file(join34(input.changeDir, "proposal.md"));
261866
- const designFile = Bun.file(join34(input.changeDir, "design.md"));
261867
- const reviewFindingsFile = Bun.file(join34(input.changeDir, "review-findings.md"));
262146
+ const tasksFile = Bun.file(join36(input.changeDir, "tasks.md"));
262147
+ const proposalFile = Bun.file(join36(input.changeDir, "proposal.md"));
262148
+ const designFile = Bun.file(join36(input.changeDir, "design.md"));
262149
+ const reviewFindingsFile = Bun.file(join36(input.changeDir, "review-findings.md"));
261868
262150
  const [tasksText, proposalText, designText, reviewFindingsText] = await Promise.all([
261869
262151
  tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
261870
262152
  proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
@@ -261927,7 +262209,7 @@ var init_worker_state_poll = __esm(() => {
261927
262209
  });
261928
262210
 
261929
262211
  // apps/agent/src/components/AgentMode.tsx
261930
- import { join as join35 } from "path";
262212
+ import { join as join37 } from "path";
261931
262213
  async function appendSteeringImpl(changeDir, message) {
261932
262214
  await runWithContext(createDefaultContext(), async () => {
261933
262215
  appendSteeringMessage(changeDir, message);
@@ -263500,7 +263782,7 @@ function AgentMode({
263500
263782
  },
263501
263783
  onSubmit: async (message) => {
263502
263784
  try {
263503
- await appendSteering2(join35(tasksDir, w2.changeName), message);
263785
+ await appendSteering2(join37(tasksDir, w2.changeName), message);
263504
263786
  fileEmit({ type: "steering_submitted", changeName: w2.changeName, message });
263505
263787
  } catch (err) {
263506
263788
  const text = err.message;
@@ -263614,7 +263896,7 @@ function shouldFallbackToJsonOutput(args, stdinIsTty) {
263614
263896
  }
263615
263897
 
263616
263898
  // apps/agent/src/runtime/tmux.ts
263617
- import { basename as basename3 } from "path";
263899
+ import { basename as basename4 } from "path";
263618
263900
  function tmuxAvailable() {
263619
263901
  const result2 = Bun.spawnSync({ cmd: ["tmux", "-V"], stderr: "pipe" });
263620
263902
  return result2.exitCode === 0;
@@ -263623,7 +263905,7 @@ function sessionName(projectRoot) {
263623
263905
  const override = process.env["RALPH_SESSION_NAME"];
263624
263906
  if (override)
263625
263907
  return override;
263626
- return `ralphy-agent-${basename3(projectRoot)}`;
263908
+ return `ralphy-agent-${basename4(projectRoot)}`;
263627
263909
  }
263628
263910
  function sessionExists(name) {
263629
263911
  const result2 = Bun.spawnSync({ cmd: ["tmux", "has-session", "-t", name], stderr: "pipe" });
@@ -263804,7 +264086,7 @@ __export(exports_list, {
263804
264086
  buildBuckets: () => buildBuckets,
263805
264087
  backlogRankByIssueId: () => backlogRankByIssueId
263806
264088
  });
263807
- import { join as join36 } from "path";
264089
+ import { join as join38 } from "path";
263808
264090
  function countTaskItems(content) {
263809
264091
  const checked = (content.match(/^- \[x\]/gm) ?? []).length;
263810
264092
  const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
@@ -263820,13 +264102,13 @@ function buildLocalRows() {
263820
264102
  const sources = [{ dir: statesDir, label: "main" }];
263821
264103
  const worktreesRoot = worktreesDir2(projectRoot);
263822
264104
  for (const wt of storage.list(worktreesRoot)) {
263823
- sources.push({ dir: join36(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
264105
+ sources.push({ dir: join38(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
263824
264106
  }
263825
264107
  for (const { dir, label } of sources) {
263826
264108
  for (const entry of storage.list(dir)) {
263827
264109
  if (seen.has(entry))
263828
264110
  continue;
263829
- const raw = storage.read(join36(dir, entry, ".ralph-state.json"));
264111
+ const raw = storage.read(join38(dir, entry, ".ralph-state.json"));
263830
264112
  if (raw === null)
263831
264113
  continue;
263832
264114
  let state;
@@ -263841,7 +264123,7 @@ function buildLocalRows() {
263841
264123
  const firstLine = promptRaw.split(`
263842
264124
  `).find((l3) => l3.trim() !== "") ?? "";
263843
264125
  let progress = "\u2014";
263844
- const tasksContent = storage.read(join36(dir, entry, "tasks.md"));
264126
+ const tasksContent = storage.read(join38(dir, entry, "tasks.md"));
263845
264127
  if (tasksContent !== null) {
263846
264128
  const { checked, unchecked } = countTaskItems(tasksContent);
263847
264129
  const total = checked + unchecked;
@@ -264336,31 +264618,14 @@ var init_list = __esm(() => {
264336
264618
  };
264337
264619
  });
264338
264620
 
264339
- // apps/agent/src/agent/state/agent-run-state.ts
264340
- import { basename as basename4, join as join37 } from "path";
264341
- import { homedir as homedir6 } from "os";
264342
- import { mkdir as mkdir13, writeFile } from "fs/promises";
264343
- function agentRunStatePath(projectRoot) {
264344
- return join37(homedir6(), ".ralph", basename4(projectRoot), "agent-state.json");
264345
- }
264346
- async function writeAgentRunState(state) {
264347
- const path = agentRunStatePath(state.projectRoot);
264348
- try {
264349
- await mkdir13(join37(homedir6(), ".ralph", basename4(state.projectRoot)), { recursive: true });
264350
- await writeFile(path, JSON.stringify(state, null, 2) + `
264351
- `, "utf-8");
264352
- } catch {}
264353
- }
264354
- var init_agent_run_state = () => {};
264355
-
264356
264621
  // apps/agent/src/agent/json-runner.ts
264357
264622
  var exports_json_runner = {};
264358
264623
  __export(exports_json_runner, {
264359
264624
  runAgentJson: () => runAgentJson
264360
264625
  });
264361
- import { join as join38 } from "path";
264362
- import { mkdir as mkdir14 } from "fs/promises";
264363
- import { homedir as homedir7 } from "os";
264626
+ import { join as join39 } from "path";
264627
+ import { mkdir as mkdir15 } from "fs/promises";
264628
+ import { homedir as homedir8 } from "os";
264364
264629
  function makeEmit(fileSink) {
264365
264630
  return (event) => {
264366
264631
  const payload = { ts: Date.now(), ...event };
@@ -264380,7 +264645,7 @@ async function runAgentJson({
264380
264645
  tasksDir,
264381
264646
  runPreflight: runPreflight2 = runPreflight
264382
264647
  }) {
264383
- await mkdir14(join38(homedir7(), ".ralph"), { recursive: true }).catch(() => {
264648
+ await mkdir15(join39(homedir8(), ".ralph"), { recursive: true }).catch(() => {
264384
264649
  return;
264385
264650
  });
264386
264651
  const fileSink = createJsonLogFileSink(args.jsonLogFile);
@@ -264596,8 +264861,8 @@ var exports_src3 = {};
264596
264861
  __export(exports_src3, {
264597
264862
  main: () => main3
264598
264863
  });
264599
- import { mkdir as mkdir15 } from "fs/promises";
264600
- import { join as join39 } from "path";
264864
+ import { mkdir as mkdir16 } from "fs/promises";
264865
+ import { join as join40 } from "path";
264601
264866
  async function main3(argv) {
264602
264867
  if (argv.includes("--help") || argv.includes("-h")) {
264603
264868
  printAgentHelp();
@@ -264661,9 +264926,9 @@ async function main3(argv) {
264661
264926
  return 1;
264662
264927
  }
264663
264928
  }
264664
- await mkdir15(statesDir, { recursive: true });
264665
- await mkdir15(tasksDir, { recursive: true });
264666
- await mkdir15(join39(projectRoot, ".ralph"), { recursive: true });
264929
+ await mkdir16(statesDir, { recursive: true });
264930
+ await mkdir16(tasksDir, { recursive: true });
264931
+ await mkdir16(join40(projectRoot, ".ralph"), { recursive: true });
264667
264932
  if (shouldFallbackToJsonOutput(args, process.stdin.isTTY)) {
264668
264933
  process.stderr.write(`agent: stdin is not a TTY \u2014 falling back to --json-output mode.
264669
264934
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "3.10.5",
3
+ "version": "3.10.6",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",