@neriros/ralphy 2.4.0 → 2.5.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.
Files changed (3) hide show
  1. package/dist/cli/index.js +139 -107
  2. package/dist/mcp/index.js +6099 -1703
  3. package/package.json +15 -8
package/dist/cli/index.js CHANGED
@@ -35498,9 +35498,8 @@ var require_cjs = __commonJS((exports, module) => {
35498
35498
  });
35499
35499
 
35500
35500
  // apps/cli/src/index.ts
35501
- import { resolve, join as join11 } from "path";
35502
- import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
35503
- import { spawnSync as spawnSync2 } from "child_process";
35501
+ import { resolve, join as join11, dirname as dirname3 } from "path";
35502
+ import { exists, mkdir as mkdir2 } from "fs/promises";
35504
35503
 
35505
35504
  // node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
35506
35505
  import { Stream } from "stream";
@@ -41044,9 +41043,6 @@ var import_react21 = __toESM(require_react(), 1);
41044
41043
  // apps/cli/src/index.ts
41045
41044
  var import_react58 = __toESM(require_react(), 1);
41046
41045
 
41047
- // apps/cli/src/cli.ts
41048
- import { readFileSync as readFileSync2 } from "fs";
41049
-
41050
41046
  // packages/output/src/output.ts
41051
41047
  var formatters = {
41052
41048
  bold: (text) => source_default.bold(text),
@@ -41111,7 +41107,7 @@ var HELP_TEXT = [
41111
41107
  function printHelp() {
41112
41108
  log(HELP_TEXT);
41113
41109
  }
41114
- function parseArgs(argv) {
41110
+ async function parseArgs(argv) {
41115
41111
  const result2 = {
41116
41112
  mode: "task",
41117
41113
  name: "",
@@ -41150,7 +41146,7 @@ function parseArgs(argv) {
41150
41146
  }
41151
41147
  if (expectModelFlag) {
41152
41148
  if (!VALID_MODELS.has(arg)) {
41153
- throw new Error(`Invalid model '${arg}'. Valid models: ${[...VALID_MODELS].join(", ")}`);
41149
+ throw new Error("Invalid model");
41154
41150
  }
41155
41151
  result2.model = arg;
41156
41152
  expectModelFlag = false;
@@ -41167,7 +41163,7 @@ function parseArgs(argv) {
41167
41163
  continue;
41168
41164
  }
41169
41165
  if (expectPromptFile) {
41170
- result2.prompt = readFileSync2(arg, "utf-8");
41166
+ result2.prompt = await Bun.file(arg).text();
41171
41167
  expectPromptFile = false;
41172
41168
  continue;
41173
41169
  }
@@ -41270,9 +41266,7 @@ function parseArgs(argv) {
41270
41266
  if (VALID_MODES.has(arg)) {
41271
41267
  result2.mode = arg;
41272
41268
  } else {
41273
- throw new Error(`Unknown argument '${arg}'
41274
-
41275
- Run 'ralph --help' for usage information.`);
41269
+ throw new Error("Unknown argument. Run 'ralph --help' for usage information.");
41276
41270
  }
41277
41271
  break;
41278
41272
  }
@@ -41283,7 +41277,7 @@ Run 'ralph --help' for usage information.`);
41283
41277
  // packages/context/src/context.ts
41284
41278
  import { AsyncLocalStorage } from "async_hooks";
41285
41279
  import {
41286
- readFileSync as readFileSync3,
41280
+ readFileSync as readFileSync2,
41287
41281
  writeFileSync,
41288
41282
  existsSync as existsSync2,
41289
41283
  unlinkSync,
@@ -41296,7 +41290,7 @@ class FileSystemProvider {
41296
41290
  read(path) {
41297
41291
  if (!existsSync2(path))
41298
41292
  return null;
41299
- return readFileSync3(path, "utf-8");
41293
+ return readFileSync2(path, "utf-8");
41300
41294
  }
41301
41295
  write(path, content) {
41302
41296
  mkdirSync(dirname(path), { recursive: true });
@@ -41336,11 +41330,9 @@ function createDefaultContext() {
41336
41330
  // apps/cli/src/components/App.tsx
41337
41331
  var import_react57 = __toESM(require_react(), 1);
41338
41332
  import { join as join10 } from "path";
41339
- import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
41340
41333
 
41341
41334
  // packages/core/src/state.ts
41342
41335
  import { join as join2 } from "path";
41343
- import { execSync } from "child_process";
41344
41336
 
41345
41337
  // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
41346
41338
  var exports_external = {};
@@ -45394,7 +45386,7 @@ function readState(changeDir) {
45394
45386
  const filePath = join2(changeDir, STATE_FILE);
45395
45387
  const raw = getStorage().read(filePath);
45396
45388
  if (raw === null)
45397
- throw new Error(`.ralph-state.json not found in ${changeDir}`);
45389
+ throw new Error(".ralph-state.json not found");
45398
45390
  return StateSchema.parse(JSON.parse(raw));
45399
45391
  }
45400
45392
  function writeState(changeDir, state) {
@@ -45412,7 +45404,16 @@ function buildInitialState(options) {
45412
45404
  const now2 = new Date().toISOString();
45413
45405
  let branch = "main";
45414
45406
  try {
45415
- branch = execSync("git branch --show-current", { encoding: "utf-8" }).trim();
45407
+ const proc = Bun.spawnSync({
45408
+ cmd: ["git", "branch", "--show-current"],
45409
+ stdout: "pipe",
45410
+ stderr: "pipe"
45411
+ });
45412
+ if (proc.exitCode === 0) {
45413
+ const out = new TextDecoder().decode(proc.stdout).trim();
45414
+ if (out)
45415
+ branch = out;
45416
+ }
45416
45417
  } catch {}
45417
45418
  return StateSchema.parse({
45418
45419
  version: "2",
@@ -49316,7 +49317,7 @@ var {spawn: bunSpawn } = globalThis.Bun;
49316
49317
  var spawn = bunSpawn;
49317
49318
 
49318
49319
  // packages/engine/src/engine.ts
49319
- import { writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, existsSync as existsSync3, mkdtempSync } from "fs";
49320
+ import { mkdtemp, unlink } from "fs/promises";
49320
49321
  import { join as join5 } from "path";
49321
49322
  import { tmpdir } from "os";
49322
49323
 
@@ -50049,8 +50050,8 @@ function buildCodexArgs() {
50049
50050
  return ["exec", "--json", "--color", "never", "--dangerously-bypass-approvals-and-sandbox", "-"];
50050
50051
  }
50051
50052
  async function runInteractive(model, prompt, taskDir) {
50052
- const promptFile = taskDir ? join5(taskDir, "_interactive_prompt.md") : join5(mkdtempSync(join5(tmpdir(), "ralph-")), "prompt.md");
50053
- writeFileSync2(promptFile, prompt);
50053
+ const promptFile = taskDir ? join5(taskDir, "_interactive_prompt.md") : join5(await mkdtemp(join5(tmpdir(), "ralph-")), "prompt.md");
50054
+ await Bun.write(promptFile, prompt);
50054
50055
  try {
50055
50056
  const cmd = [
50056
50057
  "claude",
@@ -50076,13 +50077,13 @@ async function runInteractive(model, prompt, taskDir) {
50076
50077
  });
50077
50078
  const exitCode = await proc.exited;
50078
50079
  const doneFile = taskDir ? join5(taskDir, "_interactive_done") : null;
50079
- if (doneFile && existsSync3(doneFile)) {
50080
+ if (doneFile && await Bun.file(doneFile).exists()) {
50080
50081
  return { exitCode: 0, usage: null, sessionId: null, rateLimited: false };
50081
50082
  }
50082
50083
  return { exitCode, usage: null, sessionId: null, rateLimited: false };
50083
50084
  } finally {
50084
50085
  try {
50085
- unlinkSync2(promptFile);
50086
+ await unlink(promptFile);
50086
50087
  } catch {}
50087
50088
  }
50088
50089
  }
@@ -50218,37 +50219,44 @@ function isRateLimitText(text) {
50218
50219
  }
50219
50220
 
50220
50221
  // packages/core/src/git.ts
50221
- import { execSync as execSync2 } from "child_process";
50222
+ function runGit(args) {
50223
+ const proc = Bun.spawnSync({
50224
+ cmd: ["git", ...args],
50225
+ stdout: "pipe",
50226
+ stderr: "pipe"
50227
+ });
50228
+ const decoder = new TextDecoder;
50229
+ return {
50230
+ exitCode: proc.exitCode,
50231
+ stdout: proc.stdout ? decoder.decode(proc.stdout) : "",
50232
+ stderr: proc.stderr ? decoder.decode(proc.stderr) : ""
50233
+ };
50234
+ }
50222
50235
  function getCurrentBranch() {
50223
- try {
50224
- return execSync2("git branch --show-current", { encoding: "utf-8" }).trim();
50225
- } catch {
50236
+ const result2 = runGit(["branch", "--show-current"]);
50237
+ if (result2.exitCode !== 0)
50226
50238
  return "main";
50227
- }
50239
+ return result2.stdout.trim() || "main";
50228
50240
  }
50229
50241
  function gitAdd(files) {
50230
- execSync2(`git add ${files.map((f) => `"${f}"`).join(" ")}`, {
50231
- stdio: "pipe"
50232
- });
50242
+ const result2 = runGit(["add", ...files]);
50243
+ if (result2.exitCode !== 0) {
50244
+ throw new Error("git add failed", { cause: { stderr: result2.stderr.trim() } });
50245
+ }
50233
50246
  }
50234
50247
  function gitCommit(message) {
50235
- execSync2(`git commit -m "${message.replace(/"/g, "\\\"")}"`, {
50236
- stdio: "pipe"
50237
- });
50248
+ const result2 = runGit(["commit", "-m", message]);
50249
+ if (result2.exitCode !== 0) {
50250
+ throw new Error("git commit failed", { cause: { stderr: result2.stderr.trim() } });
50251
+ }
50238
50252
  }
50239
50253
  function gitPush() {
50240
50254
  const branch = getCurrentBranch();
50241
- try {
50242
- execSync2("git push", { stdio: "pipe" });
50243
- } catch {
50244
- try {
50245
- execSync2(`git push -u origin ${branch}`, { stdio: "pipe" });
50246
- } catch {
50247
- try {
50248
- execSync2(`git push --set-upstream origin ${branch}`, { stdio: "pipe" });
50249
- } catch {}
50250
- }
50251
- }
50255
+ if (runGit(["push"]).exitCode === 0)
50256
+ return;
50257
+ if (runGit(["push", "-u", "origin", branch]).exitCode === 0)
50258
+ return;
50259
+ runGit(["push", "--set-upstream", "origin", branch]);
50252
50260
  }
50253
50261
  function commitTaskDir(taskDir, message) {
50254
50262
  try {
@@ -50815,106 +50823,121 @@ function TaskLoop({ opts }) {
50815
50823
  }
50816
50824
 
50817
50825
  // packages/openspec/src/openspec-change-store.ts
50818
- import { join as join9 } from "path";
50819
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
50820
- import { spawnSync } from "child_process";
50826
+ import { join as join9, dirname as dirname2 } from "path";
50827
+ import { readdir, mkdir } from "fs/promises";
50828
+ function resolveOpenspecBin() {
50829
+ const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
50830
+ return join9(dirname2(pkgJsonPath), "bin", "openspec.js");
50831
+ }
50832
+ function runOpenspec(args, options = {}) {
50833
+ const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
50834
+ const proc = Bun.spawnSync({
50835
+ cmd: [process.execPath, resolveOpenspecBin(), ...args],
50836
+ stdio
50837
+ });
50838
+ const decoder = new TextDecoder;
50839
+ return {
50840
+ status: proc.exitCode,
50841
+ stdout: proc.stdout ? decoder.decode(proc.stdout) : "",
50842
+ stderr: proc.stderr ? decoder.decode(proc.stderr) : ""
50843
+ };
50844
+ }
50821
50845
 
50822
50846
  class OpenSpecChangeStore {
50823
- createChange(name, description) {
50824
- const result2 = spawnSync("bunx", ["openspec", "new", "change", name, "--description", description], { stdio: "inherit", encoding: "utf-8" });
50847
+ async createChange(name, description) {
50848
+ const result2 = runOpenspec(["new", "change", name, "--description", description], {
50849
+ inherit: true
50850
+ });
50825
50851
  if (result2.status !== 0) {
50826
- throw new Error(`openspec new change failed with exit code ${result2.status ?? "unknown"}`);
50852
+ throw new Error("openspec new change failed");
50827
50853
  }
50828
- return Promise.resolve();
50829
50854
  }
50830
50855
  getChangeDirectory(name) {
50831
50856
  return join9("openspec", "changes", name);
50832
50857
  }
50833
- listChanges() {
50834
- const result2 = spawnSync("bunx", ["openspec", "list", "--json"], { encoding: "utf-8" });
50858
+ async listChanges() {
50859
+ const result2 = runOpenspec(["list", "--json"]);
50835
50860
  if (result2.stdout) {
50836
50861
  try {
50837
50862
  const parsed = JSON.parse(result2.stdout);
50838
- if (Array.isArray(parsed)) {
50839
- return Promise.resolve(parsed.map((item) => String(item)));
50840
- }
50863
+ if (Array.isArray(parsed))
50864
+ return parsed.map((item) => String(item));
50841
50865
  if (parsed && typeof parsed === "object" && "changes" in parsed && parsed.changes) {
50842
- return Promise.resolve(parsed.changes.map((change) => change.name));
50866
+ return parsed.changes.map((change) => change.name);
50843
50867
  }
50844
50868
  } catch {}
50845
50869
  }
50846
50870
  const changesDir = join9("openspec", "changes");
50847
- if (!existsSync4(changesDir))
50848
- return Promise.resolve([]);
50849
- const names = readdirSync2(changesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
50850
- return Promise.resolve(names);
50871
+ if (!await Bun.file(changesDir).exists())
50872
+ return [];
50873
+ try {
50874
+ const entries = await readdir(changesDir, { withFileTypes: true });
50875
+ return entries.filter((entry) => entry.isDirectory() && entry.name !== "archive").map((entry) => entry.name);
50876
+ } catch {
50877
+ return [];
50878
+ }
50851
50879
  }
50852
- readTaskList(name) {
50853
- const path = join9("openspec", "changes", name, "tasks.md");
50854
- if (!existsSync4(path))
50855
- return Promise.resolve("");
50856
- return Promise.resolve(readFileSync4(path, "utf-8"));
50880
+ async readTaskList(name) {
50881
+ const file = Bun.file(join9("openspec", "changes", name, "tasks.md"));
50882
+ if (!await file.exists())
50883
+ return "";
50884
+ return await file.text();
50857
50885
  }
50858
- writeTaskList(name, content) {
50886
+ async writeTaskList(name, content) {
50859
50887
  const path = join9("openspec", "changes", name, "tasks.md");
50860
- writeFileSync3(path, content, "utf-8");
50861
- return Promise.resolve();
50888
+ await mkdir(dirname2(path), { recursive: true });
50889
+ await Bun.write(path, content);
50862
50890
  }
50863
- appendSteering(name, message) {
50891
+ async appendSteering(name, message) {
50864
50892
  const path = join9("openspec", "changes", name, "steering.md");
50865
- const existing = existsSync4(path) ? readFileSync4(path, "utf-8") : null;
50893
+ const file = Bun.file(path);
50894
+ const existing = await file.exists() ? await file.text() : null;
50866
50895
  const updated = existing ? `${message}
50867
50896
 
50868
50897
  ${existing.trimStart()}` : `${message}
50869
50898
  `;
50870
- writeFileSync3(path, updated, "utf-8");
50871
- return Promise.resolve();
50872
- }
50873
- readSection(name, artifact, heading) {
50874
- const path = join9("openspec", "changes", name, artifact);
50875
- if (!existsSync4(path))
50876
- return Promise.resolve("");
50877
- const content = readFileSync4(path, "utf-8");
50899
+ await mkdir(dirname2(path), { recursive: true });
50900
+ await Bun.write(path, updated);
50901
+ }
50902
+ async readSection(name, artifact, heading) {
50903
+ const file = Bun.file(join9("openspec", "changes", name, artifact));
50904
+ if (!await file.exists())
50905
+ return "";
50906
+ const content = await file.text();
50878
50907
  const headingIndex = content.indexOf(heading);
50879
50908
  if (headingIndex === -1)
50880
- return Promise.resolve("");
50909
+ return "";
50881
50910
  const afterHeading = content.slice(headingIndex + heading.length);
50882
50911
  const levelMatch = heading.match(/^(#+)/);
50883
50912
  const level = levelMatch ? levelMatch[1].length : 2;
50884
50913
  const nextHeadingPattern = new RegExp(`\\n#{1,${level}} `);
50885
50914
  const nextMatch = afterHeading.match(nextHeadingPattern);
50886
50915
  const sectionContent = nextMatch ? afterHeading.slice(0, nextMatch.index) : afterHeading;
50887
- return Promise.resolve(sectionContent.trim());
50916
+ return sectionContent.trim();
50888
50917
  }
50889
- validateChange(name) {
50890
- const result2 = spawnSync("bunx", ["openspec", "validate", name, "--json", "--no-interactive"], {
50891
- encoding: "utf-8"
50892
- });
50918
+ async validateChange(name) {
50919
+ const result2 = runOpenspec(["validate", name, "--json", "--no-interactive"]);
50893
50920
  if (result2.stdout) {
50894
50921
  try {
50895
50922
  const parsed = JSON.parse(result2.stdout);
50896
- return Promise.resolve({
50923
+ return {
50897
50924
  valid: parsed.valid ?? result2.status === 0,
50898
50925
  warnings: parsed.warnings ?? [],
50899
50926
  errors: parsed.errors ?? []
50900
- });
50927
+ };
50901
50928
  } catch {}
50902
50929
  }
50903
- return Promise.resolve({
50930
+ return {
50904
50931
  valid: result2.status === 0,
50905
50932
  warnings: [],
50906
50933
  errors: result2.stderr ? [result2.stderr] : []
50907
- });
50934
+ };
50908
50935
  }
50909
- archiveChange(name) {
50910
- const result2 = spawnSync("bunx", ["openspec", "archive", name, "-y", "--skip-specs"], {
50911
- stdio: "inherit",
50912
- encoding: "utf-8"
50913
- });
50936
+ async archiveChange(name) {
50937
+ const result2 = runOpenspec(["archive", name, "-y", "--skip-specs"], { inherit: true });
50914
50938
  if (result2.status !== 0) {
50915
- throw new Error(`openspec archive failed with exit code ${result2.status ?? "unknown"}`);
50939
+ throw new Error("openspec archive failed");
50916
50940
  }
50917
- return Promise.resolve();
50918
50941
  }
50919
50942
  }
50920
50943
  // apps/cli/src/components/App.tsx
@@ -50952,7 +50975,7 @@ function App2({ args, statesDir, tasksDir }) {
50952
50975
  }, undefined, false, undefined, this);
50953
50976
  }
50954
50977
  const stateDir = join10(statesDir, args.name);
50955
- if (!existsSync5(join10(stateDir, ".ralph-state.json"))) {
50978
+ if (getStorage().read(join10(stateDir, ".ralph-state.json")) === null) {
50956
50979
  return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
50957
50980
  message: `Error: change '${args.name}' not found`
50958
50981
  }, undefined, false, undefined, this);
@@ -50978,8 +51001,6 @@ function App2({ args, statesDir, tasksDir }) {
50978
51001
  message: "Error: --name is required for task mode"
50979
51002
  }, undefined, false, undefined, this);
50980
51003
  }
50981
- mkdirSync2(join10(statesDir, args.name), { recursive: true });
50982
- mkdirSync2(join10(tasksDir, args.name), { recursive: true });
50983
51004
  return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(TaskLoop, {
50984
51005
  opts: {
50985
51006
  name: args.name,
@@ -51003,27 +51024,38 @@ function App2({ args, statesDir, tasksDir }) {
51003
51024
  }
51004
51025
 
51005
51026
  // apps/cli/src/index.ts
51006
- function findProjectRoot() {
51027
+ if (typeof globalThis.Bun === "undefined") {
51028
+ process.stderr.write(`ralph requires the Bun runtime (https://bun.sh/). It is not compatible with plain Node.js.
51029
+ ` + "Install Bun and re-run with `bun` or `bunx ralphy`.\n");
51030
+ process.exit(1);
51031
+ }
51032
+ async function findProjectRoot() {
51007
51033
  let dir = process.cwd();
51008
51034
  while (dir !== "/") {
51009
- if (existsSync6(join11(dir, "openspec")))
51035
+ if (await exists(join11(dir, "openspec")))
51010
51036
  return dir;
51011
51037
  dir = resolve(dir, "..");
51012
51038
  }
51013
51039
  return process.cwd();
51014
51040
  }
51015
51041
  try {
51016
- const args = parseArgs(process.argv.slice(2));
51017
- const projectRoot = findProjectRoot();
51042
+ const args = await parseArgs(process.argv.slice(2));
51043
+ const projectRoot = await findProjectRoot();
51018
51044
  const statesDir = join11(projectRoot, ".ralph", "tasks");
51019
51045
  const tasksDir = join11(projectRoot, "openspec", "changes");
51020
51046
  if (args.mode === "init") {
51021
- mkdirSync3(statesDir, { recursive: true });
51022
- spawnSync2("bunx", ["openspec", "init", "--tools", "none", "--force"], {
51023
- stdio: "inherit",
51047
+ await mkdir2(statesDir, { recursive: true });
51048
+ const openspecBin = join11(dirname3(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
51049
+ Bun.spawnSync({
51050
+ cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
51051
+ stdio: ["inherit", "inherit", "inherit"],
51024
51052
  cwd: process.cwd()
51025
51053
  });
51026
51054
  }
51055
+ if (args.mode === "task" && args.name) {
51056
+ await mkdir2(join11(statesDir, args.name), { recursive: true });
51057
+ await mkdir2(join11(tasksDir, args.name), { recursive: true });
51058
+ }
51027
51059
  runWithContext(createDefaultContext(), () => {
51028
51060
  render_default(import_react58.createElement(App2, { args, statesDir, tasksDir }));
51029
51061
  });