@neriros/ralphy 3.10.0 → 3.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -6615,20 +6615,33 @@ import {
6615
6615
  writeFileSync,
6616
6616
  existsSync,
6617
6617
  unlinkSync,
6618
+ renameSync,
6618
6619
  mkdirSync,
6619
6620
  readdirSync
6620
6621
  } from "fs";
6621
6622
  import { dirname } from "path";
6622
6623
 
6623
6624
  class FileSystemProvider {
6625
+ writeSeq = 0;
6624
6626
  read(path) {
6625
6627
  if (!existsSync(path))
6626
6628
  return null;
6627
6629
  return readFileSync(path, "utf-8");
6628
6630
  }
6629
6631
  write(path, content) {
6630
- mkdirSync(dirname(path), { recursive: true });
6631
- writeFileSync(path, content, "utf-8");
6632
+ const dir = dirname(path);
6633
+ mkdirSync(dir, { recursive: true });
6634
+ const tmp = `${path}.tmp-${process.pid}-${this.writeSeq++}`;
6635
+ try {
6636
+ writeFileSync(tmp, content, "utf-8");
6637
+ renameSync(tmp, path);
6638
+ } catch (err) {
6639
+ try {
6640
+ if (existsSync(tmp))
6641
+ unlinkSync(tmp);
6642
+ } catch {}
6643
+ throw err;
6644
+ }
6632
6645
  }
6633
6646
  remove(path) {
6634
6647
  if (!existsSync(path))
@@ -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.0")
18932
- return "3.10.0";
18931
+ if ("3.10.1")
18932
+ return "3.10.1";
18933
18933
  } catch {}
18934
18934
  const dirsToTry = [];
18935
18935
  try {
@@ -81370,8 +81370,7 @@ var init_fields = __esm(() => {
81370
81370
  id: "linear.syncSpecsAsAttachments",
81371
81371
  label: "Upload proposal.md / design.md as attachments?",
81372
81372
  description: "Upload the OpenSpec planning docs (proposal.md, design.md) to the issue as attachments. OpenSpec is Ralphy's spec-driven planning format.",
81373
- spec: yes(),
81374
- when: isOn("linear.syncTasksToComment")
81373
+ spec: yes()
81375
81374
  },
81376
81375
  {
81377
81376
  id: "linear.specAttachmentFormats",
@@ -82780,6 +82779,14 @@ function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
82780
82779
  else
82781
82780
  delete values2["linear.indicators"];
82782
82781
  }
82782
+ if (values2["linear.confirmationMode.enabled"] === true && values2["linear.indicators"] && typeof values2["linear.indicators"] === "object") {
82783
+ const map3 = { ...values2["linear.indicators"] };
82784
+ if (!("getApproved" in map3)) {
82785
+ map3.getApproved = { filter: [{ type: "label", value: "approved" }] };
82786
+ map3.clearApproved = { type: "label", value: "approved" };
82787
+ values2["linear.indicators"] = map3;
82788
+ }
82789
+ }
82783
82790
  let bodyOverride;
82784
82791
  if (PROMPT_BODY_FIELD_ID in values2) {
82785
82792
  const body = values2[PROMPT_BODY_FIELD_ID];
@@ -83785,12 +83792,6 @@ var init_SetupWizard = __esm(async () => {
83785
83792
  }
83786
83793
  ];
83787
83794
  CONFIRMATION_STATES = [
83788
- {
83789
- key: "confirmGate",
83790
- label: "Confirmation gate",
83791
- description: "Opt-in: only require human approval for issues that match this.",
83792
- slots: ["getConfirmGate"]
83793
- },
83794
83795
  {
83795
83796
  key: "autoApprove",
83796
83797
  label: "Auto-approve",
@@ -84079,20 +84080,33 @@ import {
84079
84080
  writeFileSync,
84080
84081
  existsSync as existsSync2,
84081
84082
  unlinkSync,
84083
+ renameSync,
84082
84084
  mkdirSync,
84083
84085
  readdirSync
84084
84086
  } from "fs";
84085
84087
  import { dirname as dirname2 } from "path";
84086
84088
 
84087
84089
  class FileSystemProvider {
84090
+ writeSeq = 0;
84088
84091
  read(path) {
84089
84092
  if (!existsSync2(path))
84090
84093
  return null;
84091
84094
  return readFileSync3(path, "utf-8");
84092
84095
  }
84093
84096
  write(path, content) {
84094
- mkdirSync(dirname2(path), { recursive: true });
84095
- writeFileSync(path, content, "utf-8");
84097
+ const dir = dirname2(path);
84098
+ mkdirSync(dir, { recursive: true });
84099
+ const tmp = `${path}.tmp-${process.pid}-${this.writeSeq++}`;
84100
+ try {
84101
+ writeFileSync(tmp, content, "utf-8");
84102
+ renameSync(tmp, path);
84103
+ } catch (err) {
84104
+ try {
84105
+ if (existsSync2(tmp))
84106
+ unlinkSync(tmp);
84107
+ } catch {}
84108
+ throw err;
84109
+ }
84096
84110
  }
84097
84111
  remove(path) {
84098
84112
  if (!existsSync2(path))
@@ -99807,7 +99821,9 @@ function useLoop(opts) {
99807
99821
  startingStatus: currentState.status
99808
99822
  });
99809
99823
  while (!cancelled) {
99810
- currentState = readState(stateDir);
99824
+ const { state: polled } = tryReadStateRaw(stateDir);
99825
+ if (polled !== null)
99826
+ currentState = polled;
99811
99827
  setState(currentState);
99812
99828
  if (!actor.getSnapshot().matches("running"))
99813
99829
  break;
@@ -101475,7 +101491,16 @@ function branchForChange(changeName) {
101475
101491
  function worktreeDirNameForIssue(issue2) {
101476
101492
  return issue2.identifier.toLowerCase();
101477
101493
  }
101478
- async function createWorktree(projectRoot, changeName, baseBranch, runner) {
101494
+ function withRepoLock(projectRoot, fn) {
101495
+ const prev = repoWorktreeLocks.get(projectRoot) ?? Promise.resolve();
101496
+ const result2 = prev.then(fn, fn);
101497
+ repoWorktreeLocks.set(projectRoot, result2.then(() => {}, () => {}));
101498
+ return result2;
101499
+ }
101500
+ function createWorktree(projectRoot, changeName, baseBranch, runner) {
101501
+ return withRepoLock(projectRoot, () => provisionWorktree(projectRoot, changeName, baseBranch, runner));
101502
+ }
101503
+ async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
101479
101504
  const dir = worktreesDir2(projectRoot);
101480
101505
  const cwd2 = join20(dir, changeName);
101481
101506
  const branch = branchForChange(changeName);
@@ -101570,7 +101595,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
101570
101595
  await Bun.write(dst, JSON.stringify(parsed, null, 2) + `
101571
101596
  `);
101572
101597
  }
101573
- var PRE_PUSH_HOOK_SCRIPT = `#!/usr/bin/env bash
101598
+ var repoWorktreeLocks, PRE_PUSH_HOOK_SCRIPT = `#!/usr/bin/env bash
101574
101599
  # Installed by ralphy createWorktree (RLF-107).
101575
101600
  # Rejects any push whose remote ref is not refs/heads/ralph/*,
101576
101601
  # and rejects force pushes unless RALPH_ALLOW_FORCE_PUSH=1.
@@ -101590,7 +101615,9 @@ while read local_ref local_sha remote_ref remote_sha; do
101590
101615
  done
101591
101616
  exit 0
101592
101617
  `;
101593
- var init_worktree = () => {};
101618
+ var init_worktree = __esm(() => {
101619
+ repoWorktreeLocks = new Map;
101620
+ });
101594
101621
 
101595
101622
  // apps/agent/src/shared/capabilities/git.ts
101596
101623
  var createWorktree2, removeWorktree2, seedWorktreeMcpConfig2, git;
@@ -105811,7 +105838,10 @@ var init_runners = __esm(() => {
105811
105838
  const stderr = await new Response(proc.stderr).text();
105812
105839
  const code = await proc.exited;
105813
105840
  if (code !== 0) {
105814
- const err = new Error("git command failed");
105841
+ const firstStderrLine = stderr.trim().split(`
105842
+ `)[0] ?? "";
105843
+ const summary = firstStderrLine ? `: ${firstStderrLine}` : "";
105844
+ const err = new Error(`git \`${args.join(" ")}\` failed${summary}`);
105815
105845
  err.stderr = stderr;
105816
105846
  err.code = code;
105817
105847
  throw err;
@@ -107542,7 +107572,7 @@ var init_linear_sync = __esm(() => {
107542
107572
 
107543
107573
  // apps/agent/src/agent/linear-sync/comment-sync.ts
107544
107574
  import { dirname as dirname12, join as join30 } from "path";
107545
- import { mkdir as mkdir11 } from "fs/promises";
107575
+ import { mkdir as mkdir11, rename, unlink as unlink2 } from "fs/promises";
107546
107576
  async function readStateJson(statePath) {
107547
107577
  const file2 = Bun.file(statePath);
107548
107578
  if (!await file2.exists())
@@ -107555,8 +107585,15 @@ async function readStateJson(statePath) {
107555
107585
  }
107556
107586
  async function writeStateJson(statePath, state) {
107557
107587
  await mkdir11(dirname12(statePath), { recursive: true });
107558
- await Bun.write(statePath, JSON.stringify(state, null, 2) + `
107588
+ const tmp = `${statePath}.tmp-${process.pid}-${writeStateSeq++}`;
107589
+ try {
107590
+ await Bun.write(tmp, JSON.stringify(state, null, 2) + `
107559
107591
  `);
107592
+ await rename(tmp, statePath);
107593
+ } catch (err) {
107594
+ await unlink2(tmp).catch(() => {});
107595
+ throw err;
107596
+ }
107560
107597
  }
107561
107598
  function readComments(state) {
107562
107599
  const raw = state?.linearComments ?? {};
@@ -107763,7 +107800,7 @@ ${deps.message.trim()}`;
107763
107800
  iteration: deps.iteration
107764
107801
  });
107765
107802
  }
107766
- var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering";
107803
+ var PLAN_COMMENT_TITLE = "\uD83D\uDCCB Ralph plan", STEERING_COMMENT_TITLE = "\uD83E\uDDED Ralph steering", writeStateSeq = 0;
107767
107804
  var init_comment_sync = __esm(() => {
107768
107805
  init_linear_sync();
107769
107806
  });
@@ -260251,7 +260288,9 @@ var init_spec_attachments = __esm(() => {
260251
260288
  // apps/agent/src/agent/wire/comment-sync.ts
260252
260289
  function createCommentSyncHooks(input) {
260253
260290
  const { apiKey, cfg, projectRoot, onLog, diag, cwdByChange, issueByChange } = input;
260254
- const enabled2 = Boolean(cfg.linear.syncTasksToComment && apiKey);
260291
+ const commentsEnabled = Boolean(cfg.linear.syncTasksToComment && apiKey);
260292
+ const specAttachmentsEnabled = Boolean(cfg.linear.syncSpecsAsAttachments && apiKey);
260293
+ const enabled2 = commentsEnabled || specAttachmentsEnabled;
260255
260294
  if (!enabled2)
260256
260295
  return { enabled: false };
260257
260296
  const commentMutations = {
@@ -260265,7 +260304,6 @@ function createCommentSyncHooks(input) {
260265
260304
  deleteAttachment,
260266
260305
  findIssueAttachmentByTitle
260267
260306
  };
260268
- const specAttachmentsEnabled = Boolean(cfg.linear.syncSpecsAsAttachments);
260269
260307
  return {
260270
260308
  enabled: enabled2,
260271
260309
  syncTasks: async (worker, iteration) => {
@@ -260273,27 +260311,29 @@ function createCommentSyncHooks(input) {
260273
260311
  const layout = projectLayout(root);
260274
260312
  const changeDir = layout.changeDir(worker.changeName);
260275
260313
  const statePath = layout.stateFile(worker.changeName);
260276
- if (!specAttachmentsEnabled) {
260277
- await postPlanCommentOnce({
260314
+ if (commentsEnabled) {
260315
+ if (!specAttachmentsEnabled) {
260316
+ await postPlanCommentOnce({
260317
+ apiKey,
260318
+ issueId: worker.issueId,
260319
+ statePath,
260320
+ changeDir,
260321
+ changeName: worker.changeName,
260322
+ log: onLog,
260323
+ mutations: commentMutations
260324
+ });
260325
+ }
260326
+ await postOrUpdateTasksComment({
260278
260327
  apiKey,
260279
260328
  issueId: worker.issueId,
260280
260329
  statePath,
260281
260330
  changeDir,
260282
260331
  changeName: worker.changeName,
260332
+ iteration,
260283
260333
  log: onLog,
260284
260334
  mutations: commentMutations
260285
260335
  });
260286
260336
  }
260287
- await postOrUpdateTasksComment({
260288
- apiKey,
260289
- issueId: worker.issueId,
260290
- statePath,
260291
- changeDir,
260292
- changeName: worker.changeName,
260293
- iteration,
260294
- log: onLog,
260295
- mutations: commentMutations
260296
- });
260297
260337
  if (specAttachmentsEnabled) {
260298
260338
  await syncSpecAttachments({
260299
260339
  apiKey,
@@ -260307,37 +260347,39 @@ function createCommentSyncHooks(input) {
260307
260347
  });
260308
260348
  }
260309
260349
  },
260310
- onSteeringAppended: async (changeName, message) => {
260311
- const root = cwdByChange.get(changeName) ?? projectRoot;
260312
- const layout = projectLayout(root);
260313
- const changeDir = layout.changeDir(changeName);
260314
- const statePath = layout.stateFile(changeName);
260315
- const issue2 = issueByChange.get(changeName) ?? null;
260316
- const issueId = issue2?.id ?? null;
260317
- if (!issueId) {
260318
- diag("comment-sync", ` comment-sync: no Linear issue cached for ${changeName}; skipping steering refresh`, "gray");
260319
- return;
260320
- }
260321
- let iteration = 0;
260322
- try {
260323
- const f2 = Bun.file(statePath);
260324
- if (await f2.exists()) {
260325
- const json2 = await f2.json();
260326
- iteration = json2.iteration ?? 0;
260350
+ ...commentsEnabled ? {
260351
+ onSteeringAppended: async (changeName, message) => {
260352
+ const root = cwdByChange.get(changeName) ?? projectRoot;
260353
+ const layout = projectLayout(root);
260354
+ const changeDir = layout.changeDir(changeName);
260355
+ const statePath = layout.stateFile(changeName);
260356
+ const issue2 = issueByChange.get(changeName) ?? null;
260357
+ const issueId = issue2?.id ?? null;
260358
+ if (!issueId) {
260359
+ diag("comment-sync", ` comment-sync: no Linear issue cached for ${changeName}; skipping steering refresh`, "gray");
260360
+ return;
260327
260361
  }
260328
- } catch {}
260329
- await postSteeringAndRefreshTasks({
260330
- apiKey,
260331
- issueId,
260332
- statePath,
260333
- changeDir,
260334
- changeName,
260335
- iteration,
260336
- message,
260337
- log: onLog,
260338
- mutations: commentMutations
260339
- });
260340
- }
260362
+ let iteration = 0;
260363
+ try {
260364
+ const f2 = Bun.file(statePath);
260365
+ if (await f2.exists()) {
260366
+ const json2 = await f2.json();
260367
+ iteration = json2.iteration ?? 0;
260368
+ }
260369
+ } catch {}
260370
+ await postSteeringAndRefreshTasks({
260371
+ apiKey,
260372
+ issueId,
260373
+ statePath,
260374
+ changeDir,
260375
+ changeName,
260376
+ iteration,
260377
+ message,
260378
+ log: onLog,
260379
+ mutations: commentMutations
260380
+ });
260381
+ }
260382
+ } : {}
260341
260383
  };
260342
260384
  }
260343
260385
  var init_comment_sync2 = __esm(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "3.10.0",
3
+ "version": "3.10.1",
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",