@neriros/ralphy 2.21.0 → 2.21.2
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 +11 -7
- package/dist/cli/index.js +99 -36
- package/dist/mcp/index.js +80 -17
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -148,6 +148,7 @@ Linear is the source of truth for which issues Ralph has touched. The `linear.in
|
|
|
148
148
|
| `getInProgress` | `{filter: Marker[]}` | Issues to resume after restart |
|
|
149
149
|
| `getConflicted` | `{filter: Marker[]}` | Issues whose PR is conflicted (re-fix run) |
|
|
150
150
|
| `getReview` | `{filter: Marker[]}` | Done issues flagged for review follow-up |
|
|
151
|
+
| `getAutoMerge` | `{filter: Marker[]}` | Issues whose PR should be auto-merged once required checks pass |
|
|
151
152
|
| `setInProgress` | `Marker` or `{apply: Marker[]}` | Applied when a worker spawns (any non-resume mode) |
|
|
152
153
|
| `setDone` | `Marker` or `{apply: Marker[]}` | Applied on clean exit |
|
|
153
154
|
| `setError` | `Marker` or `{apply: Marker[]}` | Applied on non-zero exit (quarantine signal — issue is _not_ auto-resumed) |
|
|
@@ -167,6 +168,7 @@ Example `ralphy.config.json`:
|
|
|
167
168
|
"model": "opus",
|
|
168
169
|
"useWorktree": true,
|
|
169
170
|
"createPrOnSuccess": true,
|
|
171
|
+
"autoMergeStrategy": "squash",
|
|
170
172
|
"fixCiOnFailure": true,
|
|
171
173
|
"linear": {
|
|
172
174
|
"team": "ENG",
|
|
@@ -182,6 +184,7 @@ Example `ralphy.config.json`:
|
|
|
182
184
|
"getInProgress": { "filter": [{ "type": "status", "value": "In Progress" }] },
|
|
183
185
|
"getConflicted": { "filter": [{ "type": "label", "value": "ralph:conflicted" }] },
|
|
184
186
|
"getReview": { "filter": [{ "type": "label", "value": "ralph:review" }] },
|
|
187
|
+
"getAutoMerge": { "filter": [{ "type": "label", "value": "ralph:auto-merge" }] },
|
|
185
188
|
"setInProgress": { "type": "status", "value": "In Progress" },
|
|
186
189
|
"setDone": {
|
|
187
190
|
"apply": [
|
|
@@ -225,13 +228,14 @@ Done issues whose PR `gh pr view --json mergeable` reports as `CONFLICTING` get
|
|
|
225
228
|
|
|
226
229
|
### PR + CI integration
|
|
227
230
|
|
|
228
|
-
| Flag / config | Behavior
|
|
229
|
-
| ------------------------------------- |
|
|
230
|
-
| `createPrOnSuccess` / `--create-pr` | After a clean exit, push the worker's branch and `gh pr create`. Title: `<ID>: <title>`. Idempotent — surfaces the existing URL if the PR is already open. Requires `--worktree` and `gh` authenticated. `prBaseBranch` defaults to `main
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
231
|
+
| Flag / config | Behavior |
|
|
232
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
233
|
+
| `createPrOnSuccess` / `--create-pr` | After a clean exit, push the worker's branch and `gh pr create`. Title: `<ID>: <title>`. Idempotent — surfaces the existing URL if the PR is already open. Requires `--worktree` and `gh` authenticated. `prBaseBranch` defaults to `main`; override per-issue by labelling the Linear issue with `ralph:branch:<branch-name>`. |
|
|
234
|
+
| `getAutoMerge` indicator | Opt an issue in for GitHub auto-merge (any-of label/status filter, same shape as `getReview`). When matched, Ralph runs `gh pr merge <url> --auto --<strategy>` right after opening the PR so GitHub merges as soon as required checks pass. Strategy comes from `autoMergeStrategy` (`squash` \| `merge` \| `rebase`, default `squash`). Failures are logged but non-fatal — the CI/conflict watch loop continues. |
|
|
235
|
+
| `fixCiOnFailure` / `--fix-ci` | After the PR opens, poll `gh pr checks`. On failure, pull failed logs via `gh run view --log-failed`, append them to `## Steering`, re-spawn the worker, and push the new commits — repeat until green or `maxCiFixAttempts` (default `5`) is hit. While this loop runs, `setDone` is **not** applied; if CI is never green the worker is treated as failed. |
|
|
236
|
+
| `ciPollIntervalSeconds` | Seconds between CI status polls (default `30`). |
|
|
237
|
+
| `ignoreCiChecks` | Array of check names to ignore when computing pass/fail. |
|
|
238
|
+
| `codeReviewTrigger` / `--code-review` | See [Code-review iteration](#code-review-iteration). |
|
|
235
239
|
|
|
236
240
|
### Worktrees, setup, teardown
|
|
237
241
|
|
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.2")
|
|
35033
|
+
return "2.21.2";
|
|
35034
35034
|
} catch {}
|
|
35035
35035
|
const dirsToTry = [];
|
|
35036
35036
|
try {
|
|
@@ -62245,7 +62245,7 @@ var exports_json_runner = {};
|
|
|
62245
62245
|
__export(exports_json_runner, {
|
|
62246
62246
|
runAgentJson: () => runAgentJson
|
|
62247
62247
|
});
|
|
62248
|
-
import { join as
|
|
62248
|
+
import { join as join22 } from "path";
|
|
62249
62249
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
62250
62250
|
import { homedir as homedir4 } from "os";
|
|
62251
62251
|
function cleanOutputLine2(raw) {
|
|
@@ -62270,7 +62270,7 @@ async function runAgentJson({
|
|
|
62270
62270
|
statesDir,
|
|
62271
62271
|
tasksDir
|
|
62272
62272
|
}) {
|
|
62273
|
-
await mkdir6(
|
|
62273
|
+
await mkdir6(join22(homedir4(), ".ralph"), { recursive: true }).catch(() => {
|
|
62274
62274
|
return;
|
|
62275
62275
|
});
|
|
62276
62276
|
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
@@ -62409,7 +62409,7 @@ var init_json_runner = __esm(() => {
|
|
|
62409
62409
|
});
|
|
62410
62410
|
|
|
62411
62411
|
// apps/cli/src/index.ts
|
|
62412
|
-
import { resolve as resolve2, join as
|
|
62412
|
+
import { resolve as resolve2, join as join23 } from "path";
|
|
62413
62413
|
import { exists as exists2, mkdir as mkdir7, rm } from "fs/promises";
|
|
62414
62414
|
|
|
62415
62415
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
@@ -67532,7 +67532,7 @@ function createDefaultContext() {
|
|
|
67532
67532
|
|
|
67533
67533
|
// apps/cli/src/components/App.tsx
|
|
67534
67534
|
var import_react58 = __toESM(require_react(), 1);
|
|
67535
|
-
import { join as
|
|
67535
|
+
import { join as join20 } from "path";
|
|
67536
67536
|
|
|
67537
67537
|
// packages/core/src/state.ts
|
|
67538
67538
|
init_types2();
|
|
@@ -74236,16 +74236,79 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
74236
74236
|
}
|
|
74237
74237
|
|
|
74238
74238
|
// packages/openspec/src/openspec-change-store.ts
|
|
74239
|
-
import {
|
|
74239
|
+
import { dirname as dirname6, join as join19 } from "path";
|
|
74240
74240
|
import { readdir, mkdir as mkdir5 } from "fs/promises";
|
|
74241
|
-
|
|
74242
|
-
|
|
74243
|
-
|
|
74241
|
+
|
|
74242
|
+
// packages/openspec/src/openspec-bin.ts
|
|
74243
|
+
import { dirname as dirname5, join as join18 } from "path";
|
|
74244
|
+
var bunInstallRunner = {
|
|
74245
|
+
spawnSync: (cmd, cwd2) => {
|
|
74246
|
+
const proc = Bun.spawnSync({
|
|
74247
|
+
cmd,
|
|
74248
|
+
cwd: cwd2,
|
|
74249
|
+
stdio: ["ignore", "inherit", "inherit"]
|
|
74250
|
+
});
|
|
74251
|
+
return { exitCode: proc.exitCode };
|
|
74252
|
+
},
|
|
74253
|
+
resolveSync: (specifier, fromDir) => Bun.resolveSync(specifier, fromDir),
|
|
74254
|
+
log: (text) => {
|
|
74255
|
+
process.stderr.write(text);
|
|
74256
|
+
}
|
|
74257
|
+
};
|
|
74258
|
+
function findPackageRoot(startDir) {
|
|
74259
|
+
let dir = startDir;
|
|
74260
|
+
for (let i = 0;i < 8; i++) {
|
|
74261
|
+
if (Bun.file(join18(dir, "package.json")).size >= 0) {
|
|
74262
|
+
try {
|
|
74263
|
+
if (Bun.file(join18(dir, "package.json")).size > 0)
|
|
74264
|
+
return dir;
|
|
74265
|
+
} catch {}
|
|
74266
|
+
}
|
|
74267
|
+
const parent = dirname5(dir);
|
|
74268
|
+
if (parent === dir)
|
|
74269
|
+
break;
|
|
74270
|
+
dir = parent;
|
|
74271
|
+
}
|
|
74272
|
+
return startDir;
|
|
74244
74273
|
}
|
|
74274
|
+
function ensureOpenspecInstalled(fromDir, runner) {
|
|
74275
|
+
const installDir = findPackageRoot(fromDir);
|
|
74276
|
+
runner.log(`[ralphy] @fission-ai/openspec not found in ${installDir} \u2014 installing automatically...
|
|
74277
|
+
`);
|
|
74278
|
+
const candidates = [
|
|
74279
|
+
["npm", "install", "--no-save", "--no-audit", "--no-fund", "@fission-ai/openspec@latest"],
|
|
74280
|
+
["bun", "add", "@fission-ai/openspec@latest"]
|
|
74281
|
+
];
|
|
74282
|
+
for (const cmd of candidates) {
|
|
74283
|
+
try {
|
|
74284
|
+
const result2 = runner.spawnSync(cmd, installDir);
|
|
74285
|
+
if (result2.exitCode === 0) {
|
|
74286
|
+
runner.log(`[ralphy] installed @fission-ai/openspec via ${cmd[0]}.
|
|
74287
|
+
`);
|
|
74288
|
+
return;
|
|
74289
|
+
}
|
|
74290
|
+
} catch {}
|
|
74291
|
+
}
|
|
74292
|
+
const err = new Error("openspec auto-install failed");
|
|
74293
|
+
err.installDir = installDir;
|
|
74294
|
+
throw err;
|
|
74295
|
+
}
|
|
74296
|
+
function resolveOpenspecBin(fromDir, runner = bunInstallRunner) {
|
|
74297
|
+
try {
|
|
74298
|
+
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
74299
|
+
return join18(dirname5(pkgJsonPath), "bin", "openspec.js");
|
|
74300
|
+
} catch {
|
|
74301
|
+
ensureOpenspecInstalled(fromDir, runner);
|
|
74302
|
+
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
74303
|
+
return join18(dirname5(pkgJsonPath), "bin", "openspec.js");
|
|
74304
|
+
}
|
|
74305
|
+
}
|
|
74306
|
+
|
|
74307
|
+
// packages/openspec/src/openspec-change-store.ts
|
|
74245
74308
|
function runOpenspec(args, options = {}) {
|
|
74246
74309
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
74247
74310
|
const proc = Bun.spawnSync({
|
|
74248
|
-
cmd: [process.execPath, resolveOpenspecBin(), ...args],
|
|
74311
|
+
cmd: [process.execPath, resolveOpenspecBin(import.meta.dir), ...args],
|
|
74249
74312
|
stdio
|
|
74250
74313
|
});
|
|
74251
74314
|
const decoder = new TextDecoder;
|
|
@@ -74266,7 +74329,7 @@ class OpenSpecChangeStore {
|
|
|
74266
74329
|
}
|
|
74267
74330
|
}
|
|
74268
74331
|
getChangeDirectory(name) {
|
|
74269
|
-
return
|
|
74332
|
+
return join19("openspec", "changes", name);
|
|
74270
74333
|
}
|
|
74271
74334
|
async listChanges() {
|
|
74272
74335
|
const result2 = runOpenspec(["list", "--json"]);
|
|
@@ -74280,7 +74343,7 @@ class OpenSpecChangeStore {
|
|
|
74280
74343
|
}
|
|
74281
74344
|
} catch {}
|
|
74282
74345
|
}
|
|
74283
|
-
const changesDir =
|
|
74346
|
+
const changesDir = join19("openspec", "changes");
|
|
74284
74347
|
if (!await Bun.file(changesDir).exists())
|
|
74285
74348
|
return [];
|
|
74286
74349
|
try {
|
|
@@ -74291,29 +74354,29 @@ class OpenSpecChangeStore {
|
|
|
74291
74354
|
}
|
|
74292
74355
|
}
|
|
74293
74356
|
async readTaskList(name) {
|
|
74294
|
-
const file = Bun.file(
|
|
74357
|
+
const file = Bun.file(join19("openspec", "changes", name, "tasks.md"));
|
|
74295
74358
|
if (!await file.exists())
|
|
74296
74359
|
return "";
|
|
74297
74360
|
return await file.text();
|
|
74298
74361
|
}
|
|
74299
74362
|
async writeTaskList(name, content) {
|
|
74300
|
-
const path =
|
|
74301
|
-
await mkdir5(
|
|
74363
|
+
const path = join19("openspec", "changes", name, "tasks.md");
|
|
74364
|
+
await mkdir5(dirname6(path), { recursive: true });
|
|
74302
74365
|
await Bun.write(path, content);
|
|
74303
74366
|
}
|
|
74304
74367
|
async appendSteering(name, message) {
|
|
74305
|
-
const path =
|
|
74368
|
+
const path = join19("openspec", "changes", name, "steering.md");
|
|
74306
74369
|
const file = Bun.file(path);
|
|
74307
74370
|
const existing = await file.exists() ? await file.text() : null;
|
|
74308
74371
|
const updated = existing ? `${message}
|
|
74309
74372
|
|
|
74310
74373
|
${existing.trimStart()}` : `${message}
|
|
74311
74374
|
`;
|
|
74312
|
-
await mkdir5(
|
|
74375
|
+
await mkdir5(dirname6(path), { recursive: true });
|
|
74313
74376
|
await Bun.write(path, updated);
|
|
74314
74377
|
}
|
|
74315
74378
|
async readSection(name, artifact, heading) {
|
|
74316
|
-
const file = Bun.file(
|
|
74379
|
+
const file = Bun.file(join19("openspec", "changes", name, artifact));
|
|
74317
74380
|
if (!await file.exists())
|
|
74318
74381
|
return "";
|
|
74319
74382
|
const content = await file.text();
|
|
@@ -74432,8 +74495,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
74432
74495
|
message: "Error: --name is required for status mode"
|
|
74433
74496
|
}, undefined, false, undefined, this);
|
|
74434
74497
|
}
|
|
74435
|
-
const stateDir =
|
|
74436
|
-
if (getStorage().read(
|
|
74498
|
+
const stateDir = join20(statesDir, args.name);
|
|
74499
|
+
if (getStorage().read(join20(stateDir, ".ralph-state.json")) === null) {
|
|
74437
74500
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
74438
74501
|
message: `Error: change '${args.name}' not found`
|
|
74439
74502
|
}, undefined, false, undefined, this);
|
|
@@ -74480,7 +74543,7 @@ init_worktree();
|
|
|
74480
74543
|
|
|
74481
74544
|
// apps/cli/src/debug.ts
|
|
74482
74545
|
init_log();
|
|
74483
|
-
import { join as
|
|
74546
|
+
import { join as join21 } from "path";
|
|
74484
74547
|
function fmtTs(d) {
|
|
74485
74548
|
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
74486
74549
|
}
|
|
@@ -74593,7 +74656,7 @@ function detectStuck(lines) {
|
|
|
74593
74656
|
};
|
|
74594
74657
|
}
|
|
74595
74658
|
async function inspectBinary(projectRoot) {
|
|
74596
|
-
const binPath =
|
|
74659
|
+
const binPath = join21(projectRoot, ".ralph", "bin", "cli.js");
|
|
74597
74660
|
const file = Bun.file(binPath);
|
|
74598
74661
|
if (!await file.exists())
|
|
74599
74662
|
return null;
|
|
@@ -74619,7 +74682,7 @@ var SPAWN_RE = /\u25B6 (\S+) \u2192 (\S+)/;
|
|
|
74619
74682
|
async function resolveDebugTarget(projectRoot, opts) {
|
|
74620
74683
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
74621
74684
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
74622
|
-
const jsonlLogFile = Bun.file(
|
|
74685
|
+
const jsonlLogFile = Bun.file(join21(projectRoot, ".ralph", "agent.log"));
|
|
74623
74686
|
const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
|
|
74624
74687
|
const allLines = [...textLines, ...jsonlLines];
|
|
74625
74688
|
if (opts.name && !opts.issue) {
|
|
@@ -74724,7 +74787,7 @@ async function runDebug(opts) {
|
|
|
74724
74787
|
`);
|
|
74725
74788
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
74726
74789
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
74727
|
-
const jsonlLogPath =
|
|
74790
|
+
const jsonlLogPath = join21(projectRoot, ".ralph", "agent.log");
|
|
74728
74791
|
const jsonlLogFile = Bun.file(jsonlLogPath);
|
|
74729
74792
|
const hasJsonlLog = await jsonlLogFile.exists();
|
|
74730
74793
|
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
|
|
@@ -74738,7 +74801,7 @@ async function runDebug(opts) {
|
|
|
74738
74801
|
}
|
|
74739
74802
|
const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
|
|
74740
74803
|
const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
74741
|
-
const workerLogFile = Bun.file(
|
|
74804
|
+
const workerLogFile = Bun.file(join21(projectRoot, ".ralph", "logs", `${changeName}.log`));
|
|
74742
74805
|
const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
|
|
74743
74806
|
const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
74744
74807
|
const seen = new Set;
|
|
@@ -74895,8 +74958,8 @@ async function runDebug(opts) {
|
|
|
74895
74958
|
out(" \u26A0 PR currently has merge conflicts");
|
|
74896
74959
|
if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
|
|
74897
74960
|
out(" \u26A0 PR has failing CI checks");
|
|
74898
|
-
const worktreePath =
|
|
74899
|
-
if (await Bun.file(
|
|
74961
|
+
const worktreePath = join21(projectRoot, ".ralph", "worktrees", changeName);
|
|
74962
|
+
if (await Bun.file(join21(worktreePath, ".git")).exists()) {
|
|
74900
74963
|
out(` Worktree : ${worktreePath}`);
|
|
74901
74964
|
}
|
|
74902
74965
|
if (!timeline.length)
|
|
@@ -74914,7 +74977,7 @@ if (typeof globalThis.Bun === "undefined") {
|
|
|
74914
74977
|
async function findProjectRoot() {
|
|
74915
74978
|
let dir = process.cwd();
|
|
74916
74979
|
while (dir !== "/") {
|
|
74917
|
-
if (await exists2(
|
|
74980
|
+
if (await exists2(join23(dir, "openspec")))
|
|
74918
74981
|
return dir;
|
|
74919
74982
|
dir = resolve2(dir, "..");
|
|
74920
74983
|
}
|
|
@@ -74955,7 +75018,7 @@ try {
|
|
|
74955
75018
|
const tasksDir = layout.tasksDir;
|
|
74956
75019
|
if (args.mode === "init") {
|
|
74957
75020
|
await mkdir7(statesDir, { recursive: true });
|
|
74958
|
-
const openspecBin =
|
|
75021
|
+
const openspecBin = resolveOpenspecBin(import.meta.dir);
|
|
74959
75022
|
Bun.spawnSync({
|
|
74960
75023
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
74961
75024
|
stdio: ["inherit", "inherit", "inherit"],
|
|
@@ -74978,9 +75041,9 @@ try {
|
|
|
74978
75041
|
`);
|
|
74979
75042
|
process.exit(1);
|
|
74980
75043
|
}
|
|
74981
|
-
const worktreeDir =
|
|
74982
|
-
const changeDir =
|
|
74983
|
-
const stateDir =
|
|
75044
|
+
const worktreeDir = join23(worktreesDir(projectRoot), args.name);
|
|
75045
|
+
const changeDir = join23(tasksDir, args.name);
|
|
75046
|
+
const stateDir = join23(statesDir, args.name);
|
|
74984
75047
|
const branch = `ralph/${args.name}`;
|
|
74985
75048
|
const removed = [];
|
|
74986
75049
|
if (await exists2(worktreeDir)) {
|
|
@@ -75032,13 +75095,13 @@ try {
|
|
|
75032
75095
|
process.exit(0);
|
|
75033
75096
|
}
|
|
75034
75097
|
if (args.mode === "task" && args.name) {
|
|
75035
|
-
await mkdir7(
|
|
75036
|
-
await mkdir7(
|
|
75098
|
+
await mkdir7(join23(statesDir, args.name), { recursive: true });
|
|
75099
|
+
await mkdir7(join23(tasksDir, args.name), { recursive: true });
|
|
75037
75100
|
}
|
|
75038
75101
|
if (args.mode === "agent") {
|
|
75039
75102
|
await mkdir7(statesDir, { recursive: true });
|
|
75040
75103
|
await mkdir7(tasksDir, { recursive: true });
|
|
75041
|
-
await mkdir7(
|
|
75104
|
+
await mkdir7(join23(projectRoot, ".ralph"), { recursive: true });
|
|
75042
75105
|
}
|
|
75043
75106
|
if (args.mode === "agent" && args.jsonOutput) {
|
|
75044
75107
|
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.2",
|
|
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",
|