@neriros/ralphy 2.4.1 → 2.5.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/cli/index.js +145 -121
- package/dist/mcp/index.js +85 -78
- 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 {
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
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(
|
|
50053
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50224
|
-
|
|
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
|
-
|
|
50231
|
-
|
|
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
|
-
|
|
50236
|
-
|
|
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
|
-
|
|
50242
|
-
|
|
50243
|
-
|
|
50244
|
-
|
|
50245
|
-
|
|
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,123 +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 {
|
|
50820
|
-
|
|
50821
|
-
|
|
50822
|
-
|
|
50823
|
-
if (runnerCache)
|
|
50824
|
-
return runnerCache;
|
|
50825
|
-
for (const candidate of ["bunx", "npx"]) {
|
|
50826
|
-
const probe = spawnSync(candidate, ["--version"], { encoding: "utf-8" });
|
|
50827
|
-
if (!probe.error) {
|
|
50828
|
-
runnerCache = candidate;
|
|
50829
|
-
return candidate;
|
|
50830
|
-
}
|
|
50831
|
-
}
|
|
50832
|
-
throw new Error("ralph requires `bunx` or `npx` on PATH to run `openspec`. Install bun (https://bun.sh/) or Node.js and re-run.");
|
|
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");
|
|
50833
50831
|
}
|
|
50834
|
-
function
|
|
50835
|
-
const
|
|
50836
|
-
|
|
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
|
+
};
|
|
50837
50844
|
}
|
|
50838
50845
|
|
|
50839
50846
|
class OpenSpecChangeStore {
|
|
50840
|
-
createChange(name, description) {
|
|
50841
|
-
const result2 =
|
|
50842
|
-
|
|
50847
|
+
async createChange(name, description) {
|
|
50848
|
+
const result2 = runOpenspec(["new", "change", name, "--description", description], {
|
|
50849
|
+
inherit: true
|
|
50843
50850
|
});
|
|
50844
50851
|
if (result2.status !== 0) {
|
|
50845
|
-
throw new Error(
|
|
50852
|
+
throw new Error("openspec new change failed");
|
|
50846
50853
|
}
|
|
50847
|
-
return Promise.resolve();
|
|
50848
50854
|
}
|
|
50849
50855
|
getChangeDirectory(name) {
|
|
50850
50856
|
return join9("openspec", "changes", name);
|
|
50851
50857
|
}
|
|
50852
|
-
listChanges() {
|
|
50853
|
-
const result2 =
|
|
50858
|
+
async listChanges() {
|
|
50859
|
+
const result2 = runOpenspec(["list", "--json"]);
|
|
50854
50860
|
if (result2.stdout) {
|
|
50855
50861
|
try {
|
|
50856
50862
|
const parsed = JSON.parse(result2.stdout);
|
|
50857
|
-
if (Array.isArray(parsed))
|
|
50858
|
-
return
|
|
50859
|
-
}
|
|
50863
|
+
if (Array.isArray(parsed))
|
|
50864
|
+
return parsed.map((item) => String(item));
|
|
50860
50865
|
if (parsed && typeof parsed === "object" && "changes" in parsed && parsed.changes) {
|
|
50861
|
-
return
|
|
50866
|
+
return parsed.changes.map((change) => change.name);
|
|
50862
50867
|
}
|
|
50863
50868
|
} catch {}
|
|
50864
50869
|
}
|
|
50865
50870
|
const changesDir = join9("openspec", "changes");
|
|
50866
|
-
if (!
|
|
50867
|
-
return
|
|
50868
|
-
|
|
50869
|
-
|
|
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
|
+
}
|
|
50870
50879
|
}
|
|
50871
|
-
readTaskList(name) {
|
|
50872
|
-
const
|
|
50873
|
-
if (!
|
|
50874
|
-
return
|
|
50875
|
-
return
|
|
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();
|
|
50876
50885
|
}
|
|
50877
|
-
writeTaskList(name, content) {
|
|
50886
|
+
async writeTaskList(name, content) {
|
|
50878
50887
|
const path = join9("openspec", "changes", name, "tasks.md");
|
|
50879
|
-
|
|
50880
|
-
|
|
50888
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
50889
|
+
await Bun.write(path, content);
|
|
50881
50890
|
}
|
|
50882
|
-
appendSteering(name, message) {
|
|
50891
|
+
async appendSteering(name, message) {
|
|
50883
50892
|
const path = join9("openspec", "changes", name, "steering.md");
|
|
50884
|
-
const
|
|
50893
|
+
const file = Bun.file(path);
|
|
50894
|
+
const existing = await file.exists() ? await file.text() : null;
|
|
50885
50895
|
const updated = existing ? `${message}
|
|
50886
50896
|
|
|
50887
50897
|
${existing.trimStart()}` : `${message}
|
|
50888
50898
|
`;
|
|
50889
|
-
|
|
50890
|
-
|
|
50891
|
-
}
|
|
50892
|
-
readSection(name, artifact, heading) {
|
|
50893
|
-
const
|
|
50894
|
-
if (!
|
|
50895
|
-
return
|
|
50896
|
-
const content =
|
|
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();
|
|
50897
50907
|
const headingIndex = content.indexOf(heading);
|
|
50898
50908
|
if (headingIndex === -1)
|
|
50899
|
-
return
|
|
50909
|
+
return "";
|
|
50900
50910
|
const afterHeading = content.slice(headingIndex + heading.length);
|
|
50901
50911
|
const levelMatch = heading.match(/^(#+)/);
|
|
50902
50912
|
const level = levelMatch ? levelMatch[1].length : 2;
|
|
50903
50913
|
const nextHeadingPattern = new RegExp(`\\n#{1,${level}} `);
|
|
50904
50914
|
const nextMatch = afterHeading.match(nextHeadingPattern);
|
|
50905
50915
|
const sectionContent = nextMatch ? afterHeading.slice(0, nextMatch.index) : afterHeading;
|
|
50906
|
-
return
|
|
50916
|
+
return sectionContent.trim();
|
|
50907
50917
|
}
|
|
50908
|
-
validateChange(name) {
|
|
50909
|
-
const result2 =
|
|
50918
|
+
async validateChange(name) {
|
|
50919
|
+
const result2 = runOpenspec(["validate", name, "--json", "--no-interactive"]);
|
|
50910
50920
|
if (result2.stdout) {
|
|
50911
50921
|
try {
|
|
50912
50922
|
const parsed = JSON.parse(result2.stdout);
|
|
50913
|
-
return
|
|
50923
|
+
return {
|
|
50914
50924
|
valid: parsed.valid ?? result2.status === 0,
|
|
50915
50925
|
warnings: parsed.warnings ?? [],
|
|
50916
50926
|
errors: parsed.errors ?? []
|
|
50917
|
-
}
|
|
50927
|
+
};
|
|
50918
50928
|
} catch {}
|
|
50919
50929
|
}
|
|
50920
|
-
return
|
|
50930
|
+
return {
|
|
50921
50931
|
valid: result2.status === 0,
|
|
50922
50932
|
warnings: [],
|
|
50923
50933
|
errors: result2.stderr ? [result2.stderr] : []
|
|
50924
|
-
}
|
|
50934
|
+
};
|
|
50925
50935
|
}
|
|
50926
|
-
archiveChange(name) {
|
|
50927
|
-
const result2 =
|
|
50928
|
-
stdio: "inherit",
|
|
50929
|
-
encoding: "utf-8"
|
|
50930
|
-
});
|
|
50936
|
+
async archiveChange(name) {
|
|
50937
|
+
const result2 = runOpenspec(["archive", name, "-y", "--skip-specs"], { inherit: true });
|
|
50931
50938
|
if (result2.status !== 0) {
|
|
50932
|
-
throw new Error(
|
|
50939
|
+
throw new Error("openspec archive failed");
|
|
50933
50940
|
}
|
|
50934
|
-
return Promise.resolve();
|
|
50935
50941
|
}
|
|
50936
50942
|
}
|
|
50937
50943
|
// apps/cli/src/components/App.tsx
|
|
@@ -50969,7 +50975,7 @@ function App2({ args, statesDir, tasksDir }) {
|
|
|
50969
50975
|
}, undefined, false, undefined, this);
|
|
50970
50976
|
}
|
|
50971
50977
|
const stateDir = join10(statesDir, args.name);
|
|
50972
|
-
if (
|
|
50978
|
+
if (getStorage().read(join10(stateDir, ".ralph-state.json")) === null) {
|
|
50973
50979
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
|
|
50974
50980
|
message: `Error: change '${args.name}' not found`
|
|
50975
50981
|
}, undefined, false, undefined, this);
|
|
@@ -50995,8 +51001,6 @@ function App2({ args, statesDir, tasksDir }) {
|
|
|
50995
51001
|
message: "Error: --name is required for task mode"
|
|
50996
51002
|
}, undefined, false, undefined, this);
|
|
50997
51003
|
}
|
|
50998
|
-
mkdirSync2(join10(statesDir, args.name), { recursive: true });
|
|
50999
|
-
mkdirSync2(join10(tasksDir, args.name), { recursive: true });
|
|
51000
51004
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(TaskLoop, {
|
|
51001
51005
|
opts: {
|
|
51002
51006
|
name: args.name,
|
|
@@ -51020,27 +51024,47 @@ function App2({ args, statesDir, tasksDir }) {
|
|
|
51020
51024
|
}
|
|
51021
51025
|
|
|
51022
51026
|
// apps/cli/src/index.ts
|
|
51023
|
-
|
|
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() {
|
|
51024
51033
|
let dir = process.cwd();
|
|
51025
51034
|
while (dir !== "/") {
|
|
51026
|
-
if (
|
|
51035
|
+
if (await exists(join11(dir, "openspec")))
|
|
51027
51036
|
return dir;
|
|
51028
51037
|
dir = resolve(dir, "..");
|
|
51029
51038
|
}
|
|
51030
51039
|
return process.cwd();
|
|
51031
51040
|
}
|
|
51041
|
+
var args;
|
|
51042
|
+
try {
|
|
51043
|
+
args = await parseArgs(process.argv.slice(2));
|
|
51044
|
+
} catch (err) {
|
|
51045
|
+
process.stderr.write((err instanceof Error ? err.message : String(err)) + `
|
|
51046
|
+
|
|
51047
|
+
`);
|
|
51048
|
+
printHelp();
|
|
51049
|
+
process.exit(1);
|
|
51050
|
+
}
|
|
51032
51051
|
try {
|
|
51033
|
-
const
|
|
51034
|
-
const projectRoot = findProjectRoot();
|
|
51052
|
+
const projectRoot = await findProjectRoot();
|
|
51035
51053
|
const statesDir = join11(projectRoot, ".ralph", "tasks");
|
|
51036
51054
|
const tasksDir = join11(projectRoot, "openspec", "changes");
|
|
51037
51055
|
if (args.mode === "init") {
|
|
51038
|
-
|
|
51039
|
-
|
|
51040
|
-
|
|
51056
|
+
await mkdir2(statesDir, { recursive: true });
|
|
51057
|
+
const openspecBin = join11(dirname3(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
|
|
51058
|
+
Bun.spawnSync({
|
|
51059
|
+
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
51060
|
+
stdio: ["inherit", "inherit", "inherit"],
|
|
51041
51061
|
cwd: process.cwd()
|
|
51042
51062
|
});
|
|
51043
51063
|
}
|
|
51064
|
+
if (args.mode === "task" && args.name) {
|
|
51065
|
+
await mkdir2(join11(statesDir, args.name), { recursive: true });
|
|
51066
|
+
await mkdir2(join11(tasksDir, args.name), { recursive: true });
|
|
51067
|
+
}
|
|
51044
51068
|
runWithContext(createDefaultContext(), () => {
|
|
51045
51069
|
render_default(import_react58.createElement(App2, { args, statesDir, tasksDir }));
|
|
51046
51070
|
});
|
package/dist/mcp/index.js
CHANGED
|
@@ -6519,7 +6519,7 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
6519
6519
|
|
|
6520
6520
|
// apps/mcp/src/index.ts
|
|
6521
6521
|
import { resolve, join as join4 } from "path";
|
|
6522
|
-
import {
|
|
6522
|
+
import { exists } from "fs/promises";
|
|
6523
6523
|
|
|
6524
6524
|
// packages/context/src/context.ts
|
|
6525
6525
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -19946,7 +19946,6 @@ class StdioServerTransport {
|
|
|
19946
19946
|
|
|
19947
19947
|
// apps/mcp/src/tools.ts
|
|
19948
19948
|
import { join as join2 } from "path";
|
|
19949
|
-
import { spawn } from "child_process";
|
|
19950
19949
|
|
|
19951
19950
|
// node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
19952
19951
|
var exports_external = {};
|
|
@@ -23923,7 +23922,6 @@ var coerce = {
|
|
|
23923
23922
|
var NEVER2 = INVALID2;
|
|
23924
23923
|
// packages/core/src/state.ts
|
|
23925
23924
|
import { join } from "path";
|
|
23926
|
-
import { execSync } from "child_process";
|
|
23927
23925
|
|
|
23928
23926
|
// packages/types/src/types.ts
|
|
23929
23927
|
var IterationUsageSchema = exports_external.object({
|
|
@@ -24004,7 +24002,7 @@ function readState(changeDir) {
|
|
|
24004
24002
|
const filePath = join(changeDir, STATE_FILE);
|
|
24005
24003
|
const raw = getStorage().read(filePath);
|
|
24006
24004
|
if (raw === null)
|
|
24007
|
-
throw new Error(
|
|
24005
|
+
throw new Error(".ralph-state.json not found");
|
|
24008
24006
|
return StateSchema.parse(JSON.parse(raw));
|
|
24009
24007
|
}
|
|
24010
24008
|
function writeState(changeDir, state) {
|
|
@@ -24016,7 +24014,16 @@ function buildInitialState(options) {
|
|
|
24016
24014
|
const now = new Date().toISOString();
|
|
24017
24015
|
let branch = "main";
|
|
24018
24016
|
try {
|
|
24019
|
-
|
|
24017
|
+
const proc = Bun.spawnSync({
|
|
24018
|
+
cmd: ["git", "branch", "--show-current"],
|
|
24019
|
+
stdout: "pipe",
|
|
24020
|
+
stderr: "pipe"
|
|
24021
|
+
});
|
|
24022
|
+
if (proc.exitCode === 0) {
|
|
24023
|
+
const out = new TextDecoder().decode(proc.stdout).trim();
|
|
24024
|
+
if (out)
|
|
24025
|
+
branch = out;
|
|
24026
|
+
}
|
|
24020
24027
|
} catch {}
|
|
24021
24028
|
return StateSchema.parse({
|
|
24022
24029
|
version: "2",
|
|
@@ -24177,9 +24184,11 @@ function registerTools(server, changesDir, changeStore, taskFilesDir = changesDi
|
|
|
24177
24184
|
cliArgs.push("--" + engine);
|
|
24178
24185
|
if (model)
|
|
24179
24186
|
cliArgs.push("--model", model);
|
|
24180
|
-
const child = spawn(
|
|
24181
|
-
|
|
24182
|
-
|
|
24187
|
+
const child = Bun.spawn({
|
|
24188
|
+
cmd: ["bun", ...cliArgs],
|
|
24189
|
+
stdin: "ignore",
|
|
24190
|
+
stdout: "ignore",
|
|
24191
|
+
stderr: "ignore"
|
|
24183
24192
|
});
|
|
24184
24193
|
child.unref();
|
|
24185
24194
|
return {
|
|
@@ -24800,130 +24809,128 @@ function error2(msg) {
|
|
|
24800
24809
|
}
|
|
24801
24810
|
|
|
24802
24811
|
// packages/openspec/src/openspec-change-store.ts
|
|
24803
|
-
import { join as join3 } from "path";
|
|
24804
|
-
import {
|
|
24805
|
-
|
|
24806
|
-
|
|
24807
|
-
|
|
24808
|
-
|
|
24809
|
-
|
|
24810
|
-
|
|
24811
|
-
|
|
24812
|
-
|
|
24813
|
-
|
|
24814
|
-
|
|
24815
|
-
|
|
24816
|
-
|
|
24817
|
-
|
|
24818
|
-
|
|
24819
|
-
|
|
24820
|
-
|
|
24821
|
-
return spawnSync(runner, args, { ...options, encoding: "utf-8" });
|
|
24812
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
24813
|
+
import { readdir, mkdir } from "fs/promises";
|
|
24814
|
+
function resolveOpenspecBin() {
|
|
24815
|
+
const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
|
|
24816
|
+
return join3(dirname2(pkgJsonPath), "bin", "openspec.js");
|
|
24817
|
+
}
|
|
24818
|
+
function runOpenspec(args, options = {}) {
|
|
24819
|
+
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
24820
|
+
const proc = Bun.spawnSync({
|
|
24821
|
+
cmd: [process.execPath, resolveOpenspecBin(), ...args],
|
|
24822
|
+
stdio
|
|
24823
|
+
});
|
|
24824
|
+
const decoder = new TextDecoder;
|
|
24825
|
+
return {
|
|
24826
|
+
status: proc.exitCode,
|
|
24827
|
+
stdout: proc.stdout ? decoder.decode(proc.stdout) : "",
|
|
24828
|
+
stderr: proc.stderr ? decoder.decode(proc.stderr) : ""
|
|
24829
|
+
};
|
|
24822
24830
|
}
|
|
24823
24831
|
|
|
24824
24832
|
class OpenSpecChangeStore {
|
|
24825
|
-
createChange(name, description) {
|
|
24826
|
-
const result =
|
|
24827
|
-
|
|
24833
|
+
async createChange(name, description) {
|
|
24834
|
+
const result = runOpenspec(["new", "change", name, "--description", description], {
|
|
24835
|
+
inherit: true
|
|
24828
24836
|
});
|
|
24829
24837
|
if (result.status !== 0) {
|
|
24830
|
-
throw new Error(
|
|
24838
|
+
throw new Error("openspec new change failed");
|
|
24831
24839
|
}
|
|
24832
|
-
return Promise.resolve();
|
|
24833
24840
|
}
|
|
24834
24841
|
getChangeDirectory(name) {
|
|
24835
24842
|
return join3("openspec", "changes", name);
|
|
24836
24843
|
}
|
|
24837
|
-
listChanges() {
|
|
24838
|
-
const result =
|
|
24844
|
+
async listChanges() {
|
|
24845
|
+
const result = runOpenspec(["list", "--json"]);
|
|
24839
24846
|
if (result.stdout) {
|
|
24840
24847
|
try {
|
|
24841
24848
|
const parsed = JSON.parse(result.stdout);
|
|
24842
|
-
if (Array.isArray(parsed))
|
|
24843
|
-
return
|
|
24844
|
-
}
|
|
24849
|
+
if (Array.isArray(parsed))
|
|
24850
|
+
return parsed.map((item) => String(item));
|
|
24845
24851
|
if (parsed && typeof parsed === "object" && "changes" in parsed && parsed.changes) {
|
|
24846
|
-
return
|
|
24852
|
+
return parsed.changes.map((change) => change.name);
|
|
24847
24853
|
}
|
|
24848
24854
|
} catch {}
|
|
24849
24855
|
}
|
|
24850
24856
|
const changesDir = join3("openspec", "changes");
|
|
24851
|
-
if (!
|
|
24852
|
-
return
|
|
24853
|
-
|
|
24854
|
-
|
|
24857
|
+
if (!await Bun.file(changesDir).exists())
|
|
24858
|
+
return [];
|
|
24859
|
+
try {
|
|
24860
|
+
const entries = await readdir(changesDir, { withFileTypes: true });
|
|
24861
|
+
return entries.filter((entry) => entry.isDirectory() && entry.name !== "archive").map((entry) => entry.name);
|
|
24862
|
+
} catch {
|
|
24863
|
+
return [];
|
|
24864
|
+
}
|
|
24855
24865
|
}
|
|
24856
|
-
readTaskList(name) {
|
|
24857
|
-
const
|
|
24858
|
-
if (!
|
|
24859
|
-
return
|
|
24860
|
-
return
|
|
24866
|
+
async readTaskList(name) {
|
|
24867
|
+
const file = Bun.file(join3("openspec", "changes", name, "tasks.md"));
|
|
24868
|
+
if (!await file.exists())
|
|
24869
|
+
return "";
|
|
24870
|
+
return await file.text();
|
|
24861
24871
|
}
|
|
24862
|
-
writeTaskList(name, content) {
|
|
24872
|
+
async writeTaskList(name, content) {
|
|
24863
24873
|
const path = join3("openspec", "changes", name, "tasks.md");
|
|
24864
|
-
|
|
24865
|
-
|
|
24874
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
24875
|
+
await Bun.write(path, content);
|
|
24866
24876
|
}
|
|
24867
|
-
appendSteering(name, message) {
|
|
24877
|
+
async appendSteering(name, message) {
|
|
24868
24878
|
const path = join3("openspec", "changes", name, "steering.md");
|
|
24869
|
-
const
|
|
24879
|
+
const file = Bun.file(path);
|
|
24880
|
+
const existing = await file.exists() ? await file.text() : null;
|
|
24870
24881
|
const updated = existing ? `${message}
|
|
24871
24882
|
|
|
24872
24883
|
${existing.trimStart()}` : `${message}
|
|
24873
24884
|
`;
|
|
24874
|
-
|
|
24875
|
-
|
|
24876
|
-
}
|
|
24877
|
-
readSection(name, artifact, heading) {
|
|
24878
|
-
const
|
|
24879
|
-
if (!
|
|
24880
|
-
return
|
|
24881
|
-
const content =
|
|
24885
|
+
await mkdir(dirname2(path), { recursive: true });
|
|
24886
|
+
await Bun.write(path, updated);
|
|
24887
|
+
}
|
|
24888
|
+
async readSection(name, artifact, heading) {
|
|
24889
|
+
const file = Bun.file(join3("openspec", "changes", name, artifact));
|
|
24890
|
+
if (!await file.exists())
|
|
24891
|
+
return "";
|
|
24892
|
+
const content = await file.text();
|
|
24882
24893
|
const headingIndex = content.indexOf(heading);
|
|
24883
24894
|
if (headingIndex === -1)
|
|
24884
|
-
return
|
|
24895
|
+
return "";
|
|
24885
24896
|
const afterHeading = content.slice(headingIndex + heading.length);
|
|
24886
24897
|
const levelMatch = heading.match(/^(#+)/);
|
|
24887
24898
|
const level = levelMatch ? levelMatch[1].length : 2;
|
|
24888
24899
|
const nextHeadingPattern = new RegExp(`\\n#{1,${level}} `);
|
|
24889
24900
|
const nextMatch = afterHeading.match(nextHeadingPattern);
|
|
24890
24901
|
const sectionContent = nextMatch ? afterHeading.slice(0, nextMatch.index) : afterHeading;
|
|
24891
|
-
return
|
|
24902
|
+
return sectionContent.trim();
|
|
24892
24903
|
}
|
|
24893
|
-
validateChange(name) {
|
|
24894
|
-
const result =
|
|
24904
|
+
async validateChange(name) {
|
|
24905
|
+
const result = runOpenspec(["validate", name, "--json", "--no-interactive"]);
|
|
24895
24906
|
if (result.stdout) {
|
|
24896
24907
|
try {
|
|
24897
24908
|
const parsed = JSON.parse(result.stdout);
|
|
24898
|
-
return
|
|
24909
|
+
return {
|
|
24899
24910
|
valid: parsed.valid ?? result.status === 0,
|
|
24900
24911
|
warnings: parsed.warnings ?? [],
|
|
24901
24912
|
errors: parsed.errors ?? []
|
|
24902
|
-
}
|
|
24913
|
+
};
|
|
24903
24914
|
} catch {}
|
|
24904
24915
|
}
|
|
24905
|
-
return
|
|
24916
|
+
return {
|
|
24906
24917
|
valid: result.status === 0,
|
|
24907
24918
|
warnings: [],
|
|
24908
24919
|
errors: result.stderr ? [result.stderr] : []
|
|
24909
|
-
}
|
|
24920
|
+
};
|
|
24910
24921
|
}
|
|
24911
|
-
archiveChange(name) {
|
|
24912
|
-
const result =
|
|
24913
|
-
stdio: "inherit",
|
|
24914
|
-
encoding: "utf-8"
|
|
24915
|
-
});
|
|
24922
|
+
async archiveChange(name) {
|
|
24923
|
+
const result = runOpenspec(["archive", name, "-y", "--skip-specs"], { inherit: true });
|
|
24916
24924
|
if (result.status !== 0) {
|
|
24917
|
-
throw new Error(
|
|
24925
|
+
throw new Error("openspec archive failed");
|
|
24918
24926
|
}
|
|
24919
|
-
return Promise.resolve();
|
|
24920
24927
|
}
|
|
24921
24928
|
}
|
|
24922
24929
|
// apps/mcp/src/index.ts
|
|
24923
|
-
function findProjectRoot(startDir) {
|
|
24930
|
+
async function findProjectRoot(startDir) {
|
|
24924
24931
|
let dir = startDir;
|
|
24925
24932
|
while (dir !== "/") {
|
|
24926
|
-
if (
|
|
24933
|
+
if (await exists(join4(dir, "openspec")))
|
|
24927
24934
|
return dir;
|
|
24928
24935
|
dir = resolve(dir, "..");
|
|
24929
24936
|
}
|
|
@@ -24936,7 +24943,7 @@ async function main() {
|
|
|
24936
24943
|
if (dirIdx !== -1 && args[dirIdx + 1]) {
|
|
24937
24944
|
startDir = resolve(args[dirIdx + 1]);
|
|
24938
24945
|
}
|
|
24939
|
-
const projectRoot = findProjectRoot(startDir);
|
|
24946
|
+
const projectRoot = await findProjectRoot(startDir);
|
|
24940
24947
|
const changesDir = join4(projectRoot, ".ralph", "tasks");
|
|
24941
24948
|
const taskFilesDir = join4(projectRoot, "openspec", "changes");
|
|
24942
24949
|
const changeStore = new OpenSpecChangeStore;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neriros/ralphy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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",
|
|
@@ -41,12 +41,16 @@
|
|
|
41
41
|
"typecheck": "nx affected -t typecheck",
|
|
42
42
|
"check:deps": "depcruise packages/*/src apps/*/src --config .dependency-cruiser.cjs",
|
|
43
43
|
"check:unused": "knip",
|
|
44
|
-
"lint:ci": "nx affected -t lint",
|
|
45
|
-
"fmt:ci": "nx affected -t fmt:check",
|
|
46
|
-
"typecheck:ci": "nx affected -t typecheck --parallel=1",
|
|
47
|
-
"test:ci": "nx affected -t test",
|
|
48
|
-
"test:coverage:ci": "
|
|
44
|
+
"lint:ci": "nx affected -t lint --exclude=ui",
|
|
45
|
+
"fmt:ci": "nx affected -t fmt:check --exclude=ui",
|
|
46
|
+
"typecheck:ci": "nx affected -t typecheck --parallel=1 --exclude=ui",
|
|
47
|
+
"test:ci": "nx affected -t test --exclude=ui",
|
|
48
|
+
"test:affected-files:coverage:ci": "bun scripts/bun-test-affected-files.ts --coverage",
|
|
49
|
+
"test:coverage:ci": "nx affected -t test:coverage --exclude=ui",
|
|
50
|
+
"build:ci": "nx affected -t build --exclude=ui",
|
|
49
51
|
"check:circular:ci": "depcruise packages/*/src apps/*/src --config .dependency-cruiser.cjs",
|
|
52
|
+
"check:unused:ci": "knip",
|
|
53
|
+
"check:outdated:ci": "bun scripts/check-outdated.ts",
|
|
50
54
|
"prepare": "bunx husky",
|
|
51
55
|
"build:publish": "bunx nx run-many --target=build --projects=cli,mcp --output-style stream && bun run copy-assets",
|
|
52
56
|
"copy-assets": "bun scripts/copy-assets.ts",
|
|
@@ -57,7 +61,7 @@
|
|
|
57
61
|
"@commitlint/config-conventional": "^20.4.3",
|
|
58
62
|
"@fission-ai/openspec": "latest",
|
|
59
63
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
60
|
-
"@nx/devkit": "^22.5
|
|
64
|
+
"@nx/devkit": "^22.6.5",
|
|
61
65
|
"@nx/js": "^22.5.3",
|
|
62
66
|
"@secretlint/secretlint-rule-preset-recommend": "^11.3.1",
|
|
63
67
|
"@swc-node/register": "^1.11.1",
|
|
@@ -67,17 +71,20 @@
|
|
|
67
71
|
"bun-types": "^1.3.0",
|
|
68
72
|
"chalk": "^5.4.0",
|
|
69
73
|
"dependency-cruiser": "^17.3.8",
|
|
70
|
-
"front-matter": "^4.0.2",
|
|
71
74
|
"husky": "^9.1.7",
|
|
72
75
|
"knip": "^5.85.0",
|
|
73
76
|
"lint-staged": "^16.3.2",
|
|
74
77
|
"nx": "22.5.3",
|
|
78
|
+
"oxc-parser": "^0.126.0",
|
|
75
79
|
"oxfmt": "^0.36.0",
|
|
76
80
|
"oxlint": "^1.51.0",
|
|
77
81
|
"secretlint": "^11.3.1",
|
|
78
82
|
"typescript": "^5.8.0",
|
|
79
83
|
"zod": "^3.24.0"
|
|
80
84
|
},
|
|
85
|
+
"overrides": {
|
|
86
|
+
"minimatch": "^10.2.3"
|
|
87
|
+
},
|
|
81
88
|
"engines": {
|
|
82
89
|
"bun": ">=1.0.0"
|
|
83
90
|
}
|