@neriros/ralphy 2.21.1 → 2.21.3
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/README.md +12 -3
- package/dist/cli/index.js +100 -36
- package/dist/mcp/index.js +80 -17
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -181,10 +181,16 @@ Example `ralphy.config.json`:
|
|
|
181
181
|
"codeReviewStaleHours": 24,
|
|
182
182
|
"indicators": {
|
|
183
183
|
"getTodo": { "filter": [{ "type": "status", "value": "Todo" }] },
|
|
184
|
-
"getInProgress": {
|
|
185
|
-
|
|
184
|
+
"getInProgress": {
|
|
185
|
+
"filter": [{ "type": "status", "value": "In Progress" }],
|
|
186
|
+
},
|
|
187
|
+
"getConflicted": {
|
|
188
|
+
"filter": [{ "type": "label", "value": "ralph:conflicted" }],
|
|
189
|
+
},
|
|
186
190
|
"getReview": { "filter": [{ "type": "label", "value": "ralph:review" }] },
|
|
187
|
-
"getAutoMerge": {
|
|
191
|
+
"getAutoMerge": {
|
|
192
|
+
"filter": [{ "type": "label", "value": "ralph:auto-merge" }],
|
|
193
|
+
},
|
|
188
194
|
"setInProgress": { "type": "status", "value": "In Progress" },
|
|
189
195
|
"setDone": {
|
|
190
196
|
"apply": [
|
|
@@ -243,6 +249,9 @@ With `useWorktree: true` (or `--worktree`) each task runs in an isolated worktre
|
|
|
243
249
|
|
|
244
250
|
- **`setupScript`** — `sh -c`-run inside the worktree right after scaffolding (e.g. `bun install`, `cp .env.example .env`).
|
|
245
251
|
- **`teardownScript`** — `sh -c`-run after the loop exits and (optional) worktree cleanup.
|
|
252
|
+
|
|
253
|
+
Both scripts receive `WORKSPACE_ROOT` in their environment — the absolute path to the origin repository (the parent of the worktree). Use it to reference project-root files from inside a worktree, e.g. `cp "$WORKSPACE_ROOT/.env.example" .env`.
|
|
254
|
+
|
|
246
255
|
- **`cleanupWorktreeOnSuccess`** — remove the worktree on clean exit. Failed workers always keep their worktree + branch for human inspection.
|
|
247
256
|
|
|
248
257
|
Both scripts log failures but never block the loop. **`appendPrompt`** (or `--prompt` in agent mode) is appended to every scaffolded `proposal.md` under `## Additional instructions` — use it for cross-cutting guidance every task should see.
|
package/dist/cli/index.js
CHANGED
|
@@ -35029,8 +35029,8 @@ import { readFileSync as readFileSync2 } from "fs";
|
|
|
35029
35029
|
import { resolve } from "path";
|
|
35030
35030
|
function getVersion() {
|
|
35031
35031
|
try {
|
|
35032
|
-
if ("2.21.
|
|
35033
|
-
return "2.21.
|
|
35032
|
+
if ("2.21.3")
|
|
35033
|
+
return "2.21.3";
|
|
35034
35034
|
} catch {}
|
|
35035
35035
|
const dirsToTry = [];
|
|
35036
35036
|
try {
|
|
@@ -61460,6 +61460,7 @@ function buildAgentCoordinator(input) {
|
|
|
61460
61460
|
const proc = Bun.spawn({
|
|
61461
61461
|
cmd: ["sh", "-c", cmd],
|
|
61462
61462
|
cwd: cwd2,
|
|
61463
|
+
env: { ...process.env, WORKSPACE_ROOT: projectRoot },
|
|
61463
61464
|
stdout: "ignore",
|
|
61464
61465
|
stderr: "pipe",
|
|
61465
61466
|
stdin: "ignore"
|
|
@@ -62245,7 +62246,7 @@ var exports_json_runner = {};
|
|
|
62245
62246
|
__export(exports_json_runner, {
|
|
62246
62247
|
runAgentJson: () => runAgentJson
|
|
62247
62248
|
});
|
|
62248
|
-
import { join as
|
|
62249
|
+
import { join as join22 } from "path";
|
|
62249
62250
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
62250
62251
|
import { homedir as homedir4 } from "os";
|
|
62251
62252
|
function cleanOutputLine2(raw) {
|
|
@@ -62270,7 +62271,7 @@ async function runAgentJson({
|
|
|
62270
62271
|
statesDir,
|
|
62271
62272
|
tasksDir
|
|
62272
62273
|
}) {
|
|
62273
|
-
await mkdir6(
|
|
62274
|
+
await mkdir6(join22(homedir4(), ".ralph"), { recursive: true }).catch(() => {
|
|
62274
62275
|
return;
|
|
62275
62276
|
});
|
|
62276
62277
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
@@ -62409,7 +62410,7 @@ var init_json_runner = __esm(() => {
|
|
|
62409
62410
|
});
|
|
62410
62411
|
|
|
62411
62412
|
// apps/cli/src/index.ts
|
|
62412
|
-
import { resolve as resolve2, join as
|
|
62413
|
+
import { resolve as resolve2, join as join23 } from "path";
|
|
62413
62414
|
import { exists as exists2, mkdir as mkdir7, rm } from "fs/promises";
|
|
62414
62415
|
|
|
62415
62416
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
@@ -67532,7 +67533,7 @@ function createDefaultContext() {
|
|
|
67532
67533
|
|
|
67533
67534
|
// apps/cli/src/components/App.tsx
|
|
67534
67535
|
var import_react58 = __toESM(require_react(), 1);
|
|
67535
|
-
import { join as
|
|
67536
|
+
import { join as join20 } from "path";
|
|
67536
67537
|
|
|
67537
67538
|
// packages/core/src/state.ts
|
|
67538
67539
|
init_types2();
|
|
@@ -74236,16 +74237,79 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
74236
74237
|
}
|
|
74237
74238
|
|
|
74238
74239
|
// packages/openspec/src/openspec-change-store.ts
|
|
74239
|
-
import {
|
|
74240
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
74240
74241
|
import { readdir, mkdir as mkdir5 } from "fs/promises";
|
|
74241
|
-
|
|
74242
|
-
|
|
74243
|
-
|
|
74242
|
+
|
|
74243
|
+
// packages/openspec/src/openspec-bin.ts
|
|
74244
|
+
import { dirname as dirname5, join as join18 } from "path";
|
|
74245
|
+
var bunInstallRunner = {
|
|
74246
|
+
spawnSync: (cmd, cwd2) => {
|
|
74247
|
+
const proc = Bun.spawnSync({
|
|
74248
|
+
cmd,
|
|
74249
|
+
cwd: cwd2,
|
|
74250
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
74251
|
+
});
|
|
74252
|
+
return { exitCode: proc.exitCode };
|
|
74253
|
+
},
|
|
74254
|
+
resolveSync: (specifier, fromDir) => Bun.resolveSync(specifier, fromDir),
|
|
74255
|
+
log: (text) => {
|
|
74256
|
+
process.stderr.write(text);
|
|
74257
|
+
}
|
|
74258
|
+
};
|
|
74259
|
+
function findPackageRoot(startDir) {
|
|
74260
|
+
let dir = startDir;
|
|
74261
|
+
for (let i = 0;i < 8; i++) {
|
|
74262
|
+
if (Bun.file(join18(dir, "package.json")).size >= 0) {
|
|
74263
|
+
try {
|
|
74264
|
+
if (Bun.file(join18(dir, "package.json")).size > 0)
|
|
74265
|
+
return dir;
|
|
74266
|
+
} catch {}
|
|
74267
|
+
}
|
|
74268
|
+
const parent = dirname5(dir);
|
|
74269
|
+
if (parent === dir)
|
|
74270
|
+
break;
|
|
74271
|
+
dir = parent;
|
|
74272
|
+
}
|
|
74273
|
+
return startDir;
|
|
74244
74274
|
}
|
|
74275
|
+
function ensureOpenspecInstalled(fromDir, runner) {
|
|
74276
|
+
const installDir = findPackageRoot(fromDir);
|
|
74277
|
+
runner.log(`[ralphy] @fission-ai/openspec not found in ${installDir} \u2014 installing automatically...
|
|
74278
|
+
`);
|
|
74279
|
+
const candidates = [
|
|
74280
|
+
["npm", "install", "--no-save", "--no-audit", "--no-fund", "@fission-ai/openspec@latest"],
|
|
74281
|
+
["bun", "add", "@fission-ai/openspec@latest"]
|
|
74282
|
+
];
|
|
74283
|
+
for (const cmd of candidates) {
|
|
74284
|
+
try {
|
|
74285
|
+
const result2 = runner.spawnSync(cmd, installDir);
|
|
74286
|
+
if (result2.exitCode === 0) {
|
|
74287
|
+
runner.log(`[ralphy] installed @fission-ai/openspec via ${cmd[0]}.
|
|
74288
|
+
`);
|
|
74289
|
+
return;
|
|
74290
|
+
}
|
|
74291
|
+
} catch {}
|
|
74292
|
+
}
|
|
74293
|
+
const err = new Error("openspec auto-install failed");
|
|
74294
|
+
err.installDir = installDir;
|
|
74295
|
+
throw err;
|
|
74296
|
+
}
|
|
74297
|
+
function resolveOpenspecBin(fromDir, runner = bunInstallRunner) {
|
|
74298
|
+
try {
|
|
74299
|
+
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
74300
|
+
return join18(dirname5(pkgJsonPath), "bin", "openspec.js");
|
|
74301
|
+
} catch {
|
|
74302
|
+
ensureOpenspecInstalled(fromDir, runner);
|
|
74303
|
+
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
74304
|
+
return join18(dirname5(pkgJsonPath), "bin", "openspec.js");
|
|
74305
|
+
}
|
|
74306
|
+
}
|
|
74307
|
+
|
|
74308
|
+
// packages/openspec/src/openspec-change-store.ts
|
|
74245
74309
|
function runOpenspec(args, options = {}) {
|
|
74246
74310
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
74247
74311
|
const proc = Bun.spawnSync({
|
|
74248
|
-
cmd: [process.execPath, resolveOpenspecBin(), ...args],
|
|
74312
|
+
cmd: [process.execPath, resolveOpenspecBin(import.meta.dir), ...args],
|
|
74249
74313
|
stdio
|
|
74250
74314
|
});
|
|
74251
74315
|
const decoder = new TextDecoder;
|
|
@@ -74266,7 +74330,7 @@ class OpenSpecChangeStore {
|
|
|
74266
74330
|
}
|
|
74267
74331
|
}
|
|
74268
74332
|
getChangeDirectory(name) {
|
|
74269
|
-
return
|
|
74333
|
+
return join19("openspec", "changes", name);
|
|
74270
74334
|
}
|
|
74271
74335
|
async listChanges() {
|
|
74272
74336
|
const result2 = runOpenspec(["list", "--json"]);
|
|
@@ -74280,7 +74344,7 @@ class OpenSpecChangeStore {
|
|
|
74280
74344
|
}
|
|
74281
74345
|
} catch {}
|
|
74282
74346
|
}
|
|
74283
|
-
const changesDir =
|
|
74347
|
+
const changesDir = join19("openspec", "changes");
|
|
74284
74348
|
if (!await Bun.file(changesDir).exists())
|
|
74285
74349
|
return [];
|
|
74286
74350
|
try {
|
|
@@ -74291,29 +74355,29 @@ class OpenSpecChangeStore {
|
|
|
74291
74355
|
}
|
|
74292
74356
|
}
|
|
74293
74357
|
async readTaskList(name) {
|
|
74294
|
-
const file = Bun.file(
|
|
74358
|
+
const file = Bun.file(join19("openspec", "changes", name, "tasks.md"));
|
|
74295
74359
|
if (!await file.exists())
|
|
74296
74360
|
return "";
|
|
74297
74361
|
return await file.text();
|
|
74298
74362
|
}
|
|
74299
74363
|
async writeTaskList(name, content) {
|
|
74300
|
-
const path =
|
|
74301
|
-
await mkdir5(
|
|
74364
|
+
const path = join19("openspec", "changes", name, "tasks.md");
|
|
74365
|
+
await mkdir5(dirname6(path), { recursive: true });
|
|
74302
74366
|
await Bun.write(path, content);
|
|
74303
74367
|
}
|
|
74304
74368
|
async appendSteering(name, message) {
|
|
74305
|
-
const path =
|
|
74369
|
+
const path = join19("openspec", "changes", name, "steering.md");
|
|
74306
74370
|
const file = Bun.file(path);
|
|
74307
74371
|
const existing = await file.exists() ? await file.text() : null;
|
|
74308
74372
|
const updated = existing ? `${message}
|
|
74309
74373
|
|
|
74310
74374
|
${existing.trimStart()}` : `${message}
|
|
74311
74375
|
`;
|
|
74312
|
-
await mkdir5(
|
|
74376
|
+
await mkdir5(dirname6(path), { recursive: true });
|
|
74313
74377
|
await Bun.write(path, updated);
|
|
74314
74378
|
}
|
|
74315
74379
|
async readSection(name, artifact, heading) {
|
|
74316
|
-
const file = Bun.file(
|
|
74380
|
+
const file = Bun.file(join19("openspec", "changes", name, artifact));
|
|
74317
74381
|
if (!await file.exists())
|
|
74318
74382
|
return "";
|
|
74319
74383
|
const content = await file.text();
|
|
@@ -74432,8 +74496,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
74432
74496
|
message: "Error: --name is required for status mode"
|
|
74433
74497
|
}, undefined, false, undefined, this);
|
|
74434
74498
|
}
|
|
74435
|
-
const stateDir =
|
|
74436
|
-
if (getStorage().read(
|
|
74499
|
+
const stateDir = join20(statesDir, args.name);
|
|
74500
|
+
if (getStorage().read(join20(stateDir, ".ralph-state.json")) === null) {
|
|
74437
74501
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
74438
74502
|
message: `Error: change '${args.name}' not found`
|
|
74439
74503
|
}, undefined, false, undefined, this);
|
|
@@ -74480,7 +74544,7 @@ init_worktree();
|
|
|
74480
74544
|
|
|
74481
74545
|
// apps/cli/src/debug.ts
|
|
74482
74546
|
init_log();
|
|
74483
|
-
import { join as
|
|
74547
|
+
import { join as join21 } from "path";
|
|
74484
74548
|
function fmtTs(d) {
|
|
74485
74549
|
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
74486
74550
|
}
|
|
@@ -74593,7 +74657,7 @@ function detectStuck(lines) {
|
|
|
74593
74657
|
};
|
|
74594
74658
|
}
|
|
74595
74659
|
async function inspectBinary(projectRoot) {
|
|
74596
|
-
const binPath =
|
|
74660
|
+
const binPath = join21(projectRoot, ".ralph", "bin", "cli.js");
|
|
74597
74661
|
const file = Bun.file(binPath);
|
|
74598
74662
|
if (!await file.exists())
|
|
74599
74663
|
return null;
|
|
@@ -74619,7 +74683,7 @@ var SPAWN_RE = /\u25B6 (\S+) \u2192 (\S+)/;
|
|
|
74619
74683
|
async function resolveDebugTarget(projectRoot, opts) {
|
|
74620
74684
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
74621
74685
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
74622
|
-
const jsonlLogFile = Bun.file(
|
|
74686
|
+
const jsonlLogFile = Bun.file(join21(projectRoot, ".ralph", "agent.log"));
|
|
74623
74687
|
const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
|
|
74624
74688
|
const allLines = [...textLines, ...jsonlLines];
|
|
74625
74689
|
if (opts.name && !opts.issue) {
|
|
@@ -74724,7 +74788,7 @@ async function runDebug(opts) {
|
|
|
74724
74788
|
`);
|
|
74725
74789
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
74726
74790
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
74727
|
-
const jsonlLogPath =
|
|
74791
|
+
const jsonlLogPath = join21(projectRoot, ".ralph", "agent.log");
|
|
74728
74792
|
const jsonlLogFile = Bun.file(jsonlLogPath);
|
|
74729
74793
|
const hasJsonlLog = await jsonlLogFile.exists();
|
|
74730
74794
|
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
|
|
@@ -74738,7 +74802,7 @@ async function runDebug(opts) {
|
|
|
74738
74802
|
}
|
|
74739
74803
|
const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
|
|
74740
74804
|
const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
74741
|
-
const workerLogFile = Bun.file(
|
|
74805
|
+
const workerLogFile = Bun.file(join21(projectRoot, ".ralph", "logs", `${changeName}.log`));
|
|
74742
74806
|
const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
|
|
74743
74807
|
const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
74744
74808
|
const seen = new Set;
|
|
@@ -74895,8 +74959,8 @@ async function runDebug(opts) {
|
|
|
74895
74959
|
out(" \u26A0 PR currently has merge conflicts");
|
|
74896
74960
|
if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
|
|
74897
74961
|
out(" \u26A0 PR has failing CI checks");
|
|
74898
|
-
const worktreePath =
|
|
74899
|
-
if (await Bun.file(
|
|
74962
|
+
const worktreePath = join21(projectRoot, ".ralph", "worktrees", changeName);
|
|
74963
|
+
if (await Bun.file(join21(worktreePath, ".git")).exists()) {
|
|
74900
74964
|
out(` Worktree : ${worktreePath}`);
|
|
74901
74965
|
}
|
|
74902
74966
|
if (!timeline.length)
|
|
@@ -74914,7 +74978,7 @@ if (typeof globalThis.Bun === "undefined") {
|
|
|
74914
74978
|
async function findProjectRoot() {
|
|
74915
74979
|
let dir = process.cwd();
|
|
74916
74980
|
while (dir !== "/") {
|
|
74917
|
-
if (await exists2(
|
|
74981
|
+
if (await exists2(join23(dir, "openspec")))
|
|
74918
74982
|
return dir;
|
|
74919
74983
|
dir = resolve2(dir, "..");
|
|
74920
74984
|
}
|
|
@@ -74955,7 +75019,7 @@ try {
|
|
|
74955
75019
|
const tasksDir = layout.tasksDir;
|
|
74956
75020
|
if (args.mode === "init") {
|
|
74957
75021
|
await mkdir7(statesDir, { recursive: true });
|
|
74958
|
-
const openspecBin =
|
|
75022
|
+
const openspecBin = resolveOpenspecBin(import.meta.dir);
|
|
74959
75023
|
Bun.spawnSync({
|
|
74960
75024
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
74961
75025
|
stdio: ["inherit", "inherit", "inherit"],
|
|
@@ -74978,9 +75042,9 @@ try {
|
|
|
74978
75042
|
`);
|
|
74979
75043
|
process.exit(1);
|
|
74980
75044
|
}
|
|
74981
|
-
const worktreeDir =
|
|
74982
|
-
const changeDir =
|
|
74983
|
-
const stateDir =
|
|
75045
|
+
const worktreeDir = join23(worktreesDir(projectRoot), args.name);
|
|
75046
|
+
const changeDir = join23(tasksDir, args.name);
|
|
75047
|
+
const stateDir = join23(statesDir, args.name);
|
|
74984
75048
|
const branch = `ralph/${args.name}`;
|
|
74985
75049
|
const removed = [];
|
|
74986
75050
|
if (await exists2(worktreeDir)) {
|
|
@@ -75032,13 +75096,13 @@ try {
|
|
|
75032
75096
|
process.exit(0);
|
|
75033
75097
|
}
|
|
75034
75098
|
if (args.mode === "task" && args.name) {
|
|
75035
|
-
await mkdir7(
|
|
75036
|
-
await mkdir7(
|
|
75099
|
+
await mkdir7(join23(statesDir, args.name), { recursive: true });
|
|
75100
|
+
await mkdir7(join23(tasksDir, args.name), { recursive: true });
|
|
75037
75101
|
}
|
|
75038
75102
|
if (args.mode === "agent") {
|
|
75039
75103
|
await mkdir7(statesDir, { recursive: true });
|
|
75040
75104
|
await mkdir7(tasksDir, { recursive: true });
|
|
75041
|
-
await mkdir7(
|
|
75105
|
+
await mkdir7(join23(projectRoot, ".ralph"), { recursive: true });
|
|
75042
75106
|
}
|
|
75043
75107
|
if (args.mode === "agent" && args.jsonOutput) {
|
|
75044
75108
|
const { runAgentJson: runAgentJson2 } = await Promise.resolve().then(() => (init_json_runner(), exports_json_runner));
|
package/dist/mcp/index.js
CHANGED
|
@@ -6605,7 +6605,7 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
6605
6605
|
});
|
|
6606
6606
|
|
|
6607
6607
|
// apps/mcp/src/index.ts
|
|
6608
|
-
import { resolve, join as
|
|
6608
|
+
import { resolve, join as join5 } from "path";
|
|
6609
6609
|
import { exists } from "fs/promises";
|
|
6610
6610
|
|
|
6611
6611
|
// packages/context/src/context.ts
|
|
@@ -24900,16 +24900,79 @@ function error2(msg) {
|
|
|
24900
24900
|
}
|
|
24901
24901
|
|
|
24902
24902
|
// packages/openspec/src/openspec-change-store.ts
|
|
24903
|
-
import {
|
|
24903
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
24904
24904
|
import { readdir, mkdir } from "fs/promises";
|
|
24905
|
-
|
|
24906
|
-
|
|
24907
|
-
|
|
24905
|
+
|
|
24906
|
+
// packages/openspec/src/openspec-bin.ts
|
|
24907
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
24908
|
+
var bunInstallRunner = {
|
|
24909
|
+
spawnSync: (cmd, cwd) => {
|
|
24910
|
+
const proc = Bun.spawnSync({
|
|
24911
|
+
cmd,
|
|
24912
|
+
cwd,
|
|
24913
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
24914
|
+
});
|
|
24915
|
+
return { exitCode: proc.exitCode };
|
|
24916
|
+
},
|
|
24917
|
+
resolveSync: (specifier, fromDir) => Bun.resolveSync(specifier, fromDir),
|
|
24918
|
+
log: (text) => {
|
|
24919
|
+
process.stderr.write(text);
|
|
24920
|
+
}
|
|
24921
|
+
};
|
|
24922
|
+
function findPackageRoot(startDir) {
|
|
24923
|
+
let dir = startDir;
|
|
24924
|
+
for (let i = 0;i < 8; i++) {
|
|
24925
|
+
if (Bun.file(join3(dir, "package.json")).size >= 0) {
|
|
24926
|
+
try {
|
|
24927
|
+
if (Bun.file(join3(dir, "package.json")).size > 0)
|
|
24928
|
+
return dir;
|
|
24929
|
+
} catch {}
|
|
24930
|
+
}
|
|
24931
|
+
const parent = dirname2(dir);
|
|
24932
|
+
if (parent === dir)
|
|
24933
|
+
break;
|
|
24934
|
+
dir = parent;
|
|
24935
|
+
}
|
|
24936
|
+
return startDir;
|
|
24937
|
+
}
|
|
24938
|
+
function ensureOpenspecInstalled(fromDir, runner) {
|
|
24939
|
+
const installDir = findPackageRoot(fromDir);
|
|
24940
|
+
runner.log(`[ralphy] @fission-ai/openspec not found in ${installDir} \u2014 installing automatically...
|
|
24941
|
+
`);
|
|
24942
|
+
const candidates = [
|
|
24943
|
+
["npm", "install", "--no-save", "--no-audit", "--no-fund", "@fission-ai/openspec@latest"],
|
|
24944
|
+
["bun", "add", "@fission-ai/openspec@latest"]
|
|
24945
|
+
];
|
|
24946
|
+
for (const cmd of candidates) {
|
|
24947
|
+
try {
|
|
24948
|
+
const result = runner.spawnSync(cmd, installDir);
|
|
24949
|
+
if (result.exitCode === 0) {
|
|
24950
|
+
runner.log(`[ralphy] installed @fission-ai/openspec via ${cmd[0]}.
|
|
24951
|
+
`);
|
|
24952
|
+
return;
|
|
24953
|
+
}
|
|
24954
|
+
} catch {}
|
|
24955
|
+
}
|
|
24956
|
+
const err = new Error("openspec auto-install failed");
|
|
24957
|
+
err.installDir = installDir;
|
|
24958
|
+
throw err;
|
|
24908
24959
|
}
|
|
24960
|
+
function resolveOpenspecBin(fromDir, runner = bunInstallRunner) {
|
|
24961
|
+
try {
|
|
24962
|
+
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
24963
|
+
return join3(dirname2(pkgJsonPath), "bin", "openspec.js");
|
|
24964
|
+
} catch {
|
|
24965
|
+
ensureOpenspecInstalled(fromDir, runner);
|
|
24966
|
+
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
24967
|
+
return join3(dirname2(pkgJsonPath), "bin", "openspec.js");
|
|
24968
|
+
}
|
|
24969
|
+
}
|
|
24970
|
+
|
|
24971
|
+
// packages/openspec/src/openspec-change-store.ts
|
|
24909
24972
|
function runOpenspec(args, options = {}) {
|
|
24910
24973
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
24911
24974
|
const proc = Bun.spawnSync({
|
|
24912
|
-
cmd: [process.execPath, resolveOpenspecBin(), ...args],
|
|
24975
|
+
cmd: [process.execPath, resolveOpenspecBin(import.meta.dir), ...args],
|
|
24913
24976
|
stdio
|
|
24914
24977
|
});
|
|
24915
24978
|
const decoder = new TextDecoder;
|
|
@@ -24930,7 +24993,7 @@ class OpenSpecChangeStore {
|
|
|
24930
24993
|
}
|
|
24931
24994
|
}
|
|
24932
24995
|
getChangeDirectory(name) {
|
|
24933
|
-
return
|
|
24996
|
+
return join4("openspec", "changes", name);
|
|
24934
24997
|
}
|
|
24935
24998
|
async listChanges() {
|
|
24936
24999
|
const result = runOpenspec(["list", "--json"]);
|
|
@@ -24944,7 +25007,7 @@ class OpenSpecChangeStore {
|
|
|
24944
25007
|
}
|
|
24945
25008
|
} catch {}
|
|
24946
25009
|
}
|
|
24947
|
-
const changesDir =
|
|
25010
|
+
const changesDir = join4("openspec", "changes");
|
|
24948
25011
|
if (!await Bun.file(changesDir).exists())
|
|
24949
25012
|
return [];
|
|
24950
25013
|
try {
|
|
@@ -24955,29 +25018,29 @@ class OpenSpecChangeStore {
|
|
|
24955
25018
|
}
|
|
24956
25019
|
}
|
|
24957
25020
|
async readTaskList(name) {
|
|
24958
|
-
const file = Bun.file(
|
|
25021
|
+
const file = Bun.file(join4("openspec", "changes", name, "tasks.md"));
|
|
24959
25022
|
if (!await file.exists())
|
|
24960
25023
|
return "";
|
|
24961
25024
|
return await file.text();
|
|
24962
25025
|
}
|
|
24963
25026
|
async writeTaskList(name, content) {
|
|
24964
|
-
const path =
|
|
24965
|
-
await mkdir(
|
|
25027
|
+
const path = join4("openspec", "changes", name, "tasks.md");
|
|
25028
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
24966
25029
|
await Bun.write(path, content);
|
|
24967
25030
|
}
|
|
24968
25031
|
async appendSteering(name, message) {
|
|
24969
|
-
const path =
|
|
25032
|
+
const path = join4("openspec", "changes", name, "steering.md");
|
|
24970
25033
|
const file = Bun.file(path);
|
|
24971
25034
|
const existing = await file.exists() ? await file.text() : null;
|
|
24972
25035
|
const updated = existing ? `${message}
|
|
24973
25036
|
|
|
24974
25037
|
${existing.trimStart()}` : `${message}
|
|
24975
25038
|
`;
|
|
24976
|
-
await mkdir(
|
|
25039
|
+
await mkdir(dirname3(path), { recursive: true });
|
|
24977
25040
|
await Bun.write(path, updated);
|
|
24978
25041
|
}
|
|
24979
25042
|
async readSection(name, artifact, heading) {
|
|
24980
|
-
const file = Bun.file(
|
|
25043
|
+
const file = Bun.file(join4("openspec", "changes", name, artifact));
|
|
24981
25044
|
if (!await file.exists())
|
|
24982
25045
|
return "";
|
|
24983
25046
|
const content = await file.text();
|
|
@@ -25021,7 +25084,7 @@ ${existing.trimStart()}` : `${message}
|
|
|
25021
25084
|
async function findProjectRoot(startDir) {
|
|
25022
25085
|
let dir = startDir;
|
|
25023
25086
|
while (dir !== "/") {
|
|
25024
|
-
if (await exists(
|
|
25087
|
+
if (await exists(join5(dir, "openspec")))
|
|
25025
25088
|
return dir;
|
|
25026
25089
|
dir = resolve(dir, "..");
|
|
25027
25090
|
}
|
|
@@ -25035,8 +25098,8 @@ async function main() {
|
|
|
25035
25098
|
startDir = resolve(args[dirIdx + 1]);
|
|
25036
25099
|
}
|
|
25037
25100
|
const projectRoot = await findProjectRoot(startDir);
|
|
25038
|
-
const changesDir =
|
|
25039
|
-
const taskFilesDir =
|
|
25101
|
+
const changesDir = join5(projectRoot, ".ralph", "tasks");
|
|
25102
|
+
const taskFilesDir = join5(projectRoot, "openspec", "changes");
|
|
25040
25103
|
const changeStore = new OpenSpecChangeStore;
|
|
25041
25104
|
const server = new McpServer({
|
|
25042
25105
|
name: "ralph",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neriros/ralphy",
|
|
3
|
-
"version": "2.21.
|
|
3
|
+
"version": "2.21.3",
|
|
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",
|
|
@@ -56,10 +56,12 @@
|
|
|
56
56
|
"copy-assets": "bun scripts/copy-assets.ts",
|
|
57
57
|
"prepublishOnly": "bun run build:publish"
|
|
58
58
|
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@fission-ai/openspec": "latest"
|
|
61
|
+
},
|
|
59
62
|
"devDependencies": {
|
|
60
63
|
"@commitlint/cli": "^20.5.3",
|
|
61
64
|
"@commitlint/config-conventional": "^20.5.3",
|
|
62
|
-
"@fission-ai/openspec": "latest",
|
|
63
65
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
64
66
|
"@nx/devkit": "^22.7.1",
|
|
65
67
|
"@nx/js": "^22.7.1",
|