@neriros/ralphy 2.7.2 → 2.7.4
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 +15 -1
- package/dist/cli/index.js +201 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ What it does on each tick:
|
|
|
83
83
|
2. Dedupes against `.ralph/agent-state.json` (already processed) plus any in-flight workers
|
|
84
84
|
3. For each new issue: fetches existing comments, scaffolds `openspec/changes/<id-slug>/{proposal.md,tasks.md,design.md}` (with the comments embedded so the worker sees prior discussion), then spawns `ralph task --name <id-slug>` up to the concurrency cap
|
|
85
85
|
4. Posts a "🤖 started" comment on the Linear issue and (optionally) moves it to `inProgressStatus`
|
|
86
|
-
5. On worker exit, posts a success/failure comment and (on success) moves the issue to `doneStatus`
|
|
86
|
+
5. On worker exit, posts a success/failure comment and (on success) moves the issue to `doneStatus` and/or applies `doneLabel`
|
|
87
87
|
|
|
88
88
|
Defaults are written to `ralphy.config.json` on first run; CLI flags override config values per invocation.
|
|
89
89
|
|
|
@@ -102,11 +102,24 @@ Defaults are written to `ralphy.config.json` on first run; CLI flags override co
|
|
|
102
102
|
"labels": ["ralph", "automation"],
|
|
103
103
|
"inProgressStatus": "In Progress",
|
|
104
104
|
"doneStatus": "In Review",
|
|
105
|
+
"doneLabel": "ralphy-done",
|
|
105
106
|
"postComments": true,
|
|
106
107
|
},
|
|
108
|
+
"useWorktree": true,
|
|
109
|
+
"cleanupWorktreeOnSuccess": false,
|
|
110
|
+
"setupScript": "bun install",
|
|
111
|
+
"teardownScript": "git status",
|
|
107
112
|
}
|
|
108
113
|
```
|
|
109
114
|
|
|
115
|
+
`doneStatus` and `doneLabel` are independent — set either, both, or neither. Use `doneLabel` if your team marks completion via a label rather than a workflow state.
|
|
116
|
+
|
|
117
|
+
#### Per-task git worktrees
|
|
118
|
+
|
|
119
|
+
With `--worktree` (or `useWorktree: true` in config) each task runs in an isolated worktree at `.ralph/worktrees/<change-name>` checked out onto a fresh `ralph/<change-name>` branch. The change is scaffolded _inside_ the worktree, and the loop's cwd is the worktree, so concurrent workers can't stomp on each other.
|
|
120
|
+
|
|
121
|
+
Use `setupScript` (run inside the worktree right after scaffolding) to install dependencies, copy `.env`, etc. Use `teardownScript` (run after the loop exits, before any worktree cleanup) to gather artifacts or roll back local mutations. Both run via `sh -c`; failures are logged but never block the loop. With `cleanupWorktreeOnSuccess: true` the worktree is removed when the worker exits 0 — failed workers always keep their worktree (and branch) for human inspection.
|
|
122
|
+
|
|
110
123
|
Failed workers (non-zero exit) are not marked processed, so they'll be retried on the next poll. SIGINT/SIGTERM cleanly stops polling and kills active workers. All Linear side effects are best-effort — failures log a warning but never block the task loop.
|
|
111
124
|
|
|
112
125
|
## CLI Options
|
|
@@ -138,6 +151,7 @@ Failed workers (non-zero exit) are not marked processed, so they'll be retried o
|
|
|
138
151
|
| `--linear-label <name>` | Filter by label name (repeatable, any-of) |
|
|
139
152
|
| `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
|
|
140
153
|
| `--concurrency <n>` | Max concurrent task loops (default: 1) |
|
|
154
|
+
| `--worktree` | Run each task in its own git worktree |
|
|
141
155
|
|
|
142
156
|
## OpenSpec Flow
|
|
143
157
|
|
package/dist/cli/index.js
CHANGED
|
@@ -49811,10 +49811,10 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49811
49811
|
} = utils$1;
|
|
49812
49812
|
var globalFetchAPI = (({
|
|
49813
49813
|
Request,
|
|
49814
|
-
Response
|
|
49814
|
+
Response: Response2
|
|
49815
49815
|
}) => ({
|
|
49816
49816
|
Request,
|
|
49817
|
-
Response
|
|
49817
|
+
Response: Response2
|
|
49818
49818
|
}))(utils$1.global);
|
|
49819
49819
|
var {
|
|
49820
49820
|
ReadableStream: ReadableStream$1,
|
|
@@ -49834,11 +49834,11 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49834
49834
|
const {
|
|
49835
49835
|
fetch: envFetch,
|
|
49836
49836
|
Request,
|
|
49837
|
-
Response
|
|
49837
|
+
Response: Response2
|
|
49838
49838
|
} = env3;
|
|
49839
49839
|
const isFetchSupported = envFetch ? isFunction2(envFetch) : typeof fetch === "function";
|
|
49840
49840
|
const isRequestSupported = isFunction2(Request);
|
|
49841
|
-
const isResponseSupported = isFunction2(
|
|
49841
|
+
const isResponseSupported = isFunction2(Response2);
|
|
49842
49842
|
if (!isFetchSupported) {
|
|
49843
49843
|
return false;
|
|
49844
49844
|
}
|
|
@@ -49860,7 +49860,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49860
49860
|
}
|
|
49861
49861
|
return duplexAccessed && !hasContentType;
|
|
49862
49862
|
});
|
|
49863
|
-
const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils$1.isReadableStream(new
|
|
49863
|
+
const supportsResponseStream = isResponseSupported && isReadableStreamSupported && test(() => utils$1.isReadableStream(new Response2("").body));
|
|
49864
49864
|
const resolvers = {
|
|
49865
49865
|
stream: supportsResponseStream && ((res) => res.body)
|
|
49866
49866
|
};
|
|
@@ -49971,7 +49971,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49971
49971
|
});
|
|
49972
49972
|
const responseContentLength = utils$1.toFiniteNumber(response.headers.get("content-length"));
|
|
49973
49973
|
const [onProgress, flush] = onDownloadProgress && progressEventDecorator(responseContentLength, progressEventReducer(asyncDecorator(onDownloadProgress), true)) || [];
|
|
49974
|
-
response = new
|
|
49974
|
+
response = new Response2(trackStream(response.body, DEFAULT_CHUNK_SIZE, onProgress, () => {
|
|
49975
49975
|
flush && flush();
|
|
49976
49976
|
unsubscribe && unsubscribe();
|
|
49977
49977
|
}), options);
|
|
@@ -50006,9 +50006,9 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50006
50006
|
const {
|
|
50007
50007
|
fetch: fetch2,
|
|
50008
50008
|
Request,
|
|
50009
|
-
Response
|
|
50009
|
+
Response: Response2
|
|
50010
50010
|
} = env3;
|
|
50011
|
-
const seeds = [Request,
|
|
50011
|
+
const seeds = [Request, Response2, fetch2];
|
|
50012
50012
|
let len = seeds.length, i = len, seed, target, map2 = seedCache;
|
|
50013
50013
|
while (i--) {
|
|
50014
50014
|
seed = seeds[i];
|
|
@@ -50548,7 +50548,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50548
50548
|
});
|
|
50549
50549
|
|
|
50550
50550
|
// apps/cli/src/index.ts
|
|
50551
|
-
import { resolve, join as
|
|
50551
|
+
import { resolve, join as join17, dirname as dirname4 } from "path";
|
|
50552
50552
|
import { exists, mkdir as mkdir3 } from "fs/promises";
|
|
50553
50553
|
|
|
50554
50554
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
@@ -56152,6 +56152,7 @@ var HELP_TEXT = [
|
|
|
56152
56152
|
" --linear-label <name> Filter by label name (repeatable, any-of)",
|
|
56153
56153
|
" --poll-interval <s> Seconds between Linear polls (default: 60)",
|
|
56154
56154
|
" --concurrency <n> Max concurrent task loops (default: 1)",
|
|
56155
|
+
" --worktree Run each task in its own git worktree (.ralph/worktrees/<name>)",
|
|
56155
56156
|
"",
|
|
56156
56157
|
" --help, -h Show this help message",
|
|
56157
56158
|
"",
|
|
@@ -56187,7 +56188,8 @@ async function parseArgs(argv) {
|
|
|
56187
56188
|
linearStatus: [],
|
|
56188
56189
|
linearLabel: [],
|
|
56189
56190
|
pollInterval: 60,
|
|
56190
|
-
concurrency: 1
|
|
56191
|
+
concurrency: 1,
|
|
56192
|
+
worktree: false
|
|
56191
56193
|
};
|
|
56192
56194
|
let expectModel = false;
|
|
56193
56195
|
let expectModelFlag = false;
|
|
@@ -56378,6 +56380,9 @@ async function parseArgs(argv) {
|
|
|
56378
56380
|
case "--concurrency":
|
|
56379
56381
|
expectConcurrency = true;
|
|
56380
56382
|
break;
|
|
56383
|
+
case "--worktree":
|
|
56384
|
+
result2.worktree = true;
|
|
56385
|
+
break;
|
|
56381
56386
|
default:
|
|
56382
56387
|
if (VALID_MODES.has(arg)) {
|
|
56383
56388
|
result2.mode = arg;
|
|
@@ -56445,7 +56450,7 @@ function createDefaultContext() {
|
|
|
56445
56450
|
|
|
56446
56451
|
// apps/cli/src/components/App.tsx
|
|
56447
56452
|
var import_react58 = __toESM(require_react(), 1);
|
|
56448
|
-
import { join as
|
|
56453
|
+
import { join as join16 } from "path";
|
|
56449
56454
|
|
|
56450
56455
|
// packages/core/src/state.ts
|
|
56451
56456
|
import { join as join2 } from "path";
|
|
@@ -69685,6 +69690,26 @@ async function updateIssueState(apiKey, issueId, stateId) {
|
|
|
69685
69690
|
stateId
|
|
69686
69691
|
});
|
|
69687
69692
|
}
|
|
69693
|
+
async function fetchIssueLabels(apiKey, teamKey) {
|
|
69694
|
+
const query = `query Labels($team: String!) {
|
|
69695
|
+
issueLabels(filter: { team: { key: { eq: $team } } }, first: 250) {
|
|
69696
|
+
nodes { id name }
|
|
69697
|
+
}
|
|
69698
|
+
}`;
|
|
69699
|
+
const data = await linearRequest(apiKey, query, {
|
|
69700
|
+
team: teamKey
|
|
69701
|
+
});
|
|
69702
|
+
return data.issueLabels.nodes;
|
|
69703
|
+
}
|
|
69704
|
+
async function addLabelToIssue(apiKey, issueId, labelId) {
|
|
69705
|
+
const mutation = `mutation AddLabel($id: String!, $labelId: String!) {
|
|
69706
|
+
issueAddLabel(id: $id, labelId: $labelId) { success }
|
|
69707
|
+
}`;
|
|
69708
|
+
await linearRequest(apiKey, mutation, {
|
|
69709
|
+
id: issueId,
|
|
69710
|
+
labelId
|
|
69711
|
+
});
|
|
69712
|
+
}
|
|
69688
69713
|
|
|
69689
69714
|
// apps/cli/src/agent/state.ts
|
|
69690
69715
|
import { join as join10 } from "path";
|
|
@@ -69781,6 +69806,10 @@ var RalphyConfigSchema = exports_external.object({
|
|
|
69781
69806
|
pollIntervalSeconds: exports_external.number().int().positive().default(60),
|
|
69782
69807
|
maxIterationsPerTask: exports_external.number().int().nonnegative().default(0),
|
|
69783
69808
|
maxCostUsdPerTask: exports_external.number().nonnegative().default(0),
|
|
69809
|
+
useWorktree: exports_external.boolean().default(false),
|
|
69810
|
+
cleanupWorktreeOnSuccess: exports_external.boolean().default(false),
|
|
69811
|
+
setupScript: exports_external.string().optional(),
|
|
69812
|
+
teardownScript: exports_external.string().optional(),
|
|
69784
69813
|
engine: exports_external.enum(["claude", "codex"]).default("claude"),
|
|
69785
69814
|
model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
|
|
69786
69815
|
linear: exports_external.object({
|
|
@@ -69790,6 +69819,7 @@ var RalphyConfigSchema = exports_external.object({
|
|
|
69790
69819
|
labels: exports_external.union([exports_external.array(exports_external.string()), exports_external.string()]).transform((v) => typeof v === "string" ? [v] : v).default([]),
|
|
69791
69820
|
inProgressStatus: exports_external.string().optional(),
|
|
69792
69821
|
doneStatus: exports_external.string().optional(),
|
|
69822
|
+
doneLabel: exports_external.string().optional(),
|
|
69793
69823
|
postComments: exports_external.boolean().default(true)
|
|
69794
69824
|
}).default({ statuses: [], labels: [], postComments: true })
|
|
69795
69825
|
}).default({
|
|
@@ -69959,6 +69989,27 @@ class AgentCoordinator {
|
|
|
69959
69989
|
if (ok && this.opts.doneStatus) {
|
|
69960
69990
|
await this.moveIssue(issue, this.opts.doneStatus);
|
|
69961
69991
|
}
|
|
69992
|
+
if (ok && this.opts.doneLabel) {
|
|
69993
|
+
await this.tagIssue(issue, this.opts.doneLabel);
|
|
69994
|
+
}
|
|
69995
|
+
}
|
|
69996
|
+
async tagIssue(issue, labelName) {
|
|
69997
|
+
const updater = this.deps.updater;
|
|
69998
|
+
if (!updater.resolveLabelId || !updater.addLabel) {
|
|
69999
|
+
this.deps.onLog(`! Linear updater does not support labels (cannot tag ${issue.identifier} with '${labelName}')`, "yellow");
|
|
70000
|
+
return;
|
|
70001
|
+
}
|
|
70002
|
+
try {
|
|
70003
|
+
const labelId = await updater.resolveLabelId(issue, labelName);
|
|
70004
|
+
if (!labelId) {
|
|
70005
|
+
this.deps.onLog(`! Linear label '${labelName}' not found for ${issue.identifier}`, "yellow");
|
|
70006
|
+
return;
|
|
70007
|
+
}
|
|
70008
|
+
await updater.addLabel(issue, labelId);
|
|
70009
|
+
this.deps.onLog(` \u2192 ${issue.identifier} tagged with '${labelName}'`, "gray");
|
|
70010
|
+
} catch (err) {
|
|
70011
|
+
this.deps.onLog(`! Linear label add failed for ${issue.identifier}: ${err.message}`, "red");
|
|
70012
|
+
}
|
|
69962
70013
|
}
|
|
69963
70014
|
async moveIssue(issue, stateName) {
|
|
69964
70015
|
const updater = this.deps.updater;
|
|
@@ -69984,8 +70035,55 @@ class AgentCoordinator {
|
|
|
69984
70035
|
}
|
|
69985
70036
|
}
|
|
69986
70037
|
|
|
70038
|
+
// apps/cli/src/agent/worktree.ts
|
|
70039
|
+
import { join as join13 } from "path";
|
|
70040
|
+
function worktreesDir(projectRoot) {
|
|
70041
|
+
return join13(projectRoot, ".ralph", "worktrees");
|
|
70042
|
+
}
|
|
70043
|
+
function branchForChange(changeName) {
|
|
70044
|
+
return `ralph/${changeName}`;
|
|
70045
|
+
}
|
|
70046
|
+
async function createWorktree(projectRoot, changeName, runner) {
|
|
70047
|
+
const dir = worktreesDir(projectRoot);
|
|
70048
|
+
const cwd2 = join13(dir, changeName);
|
|
70049
|
+
const branch = branchForChange(changeName);
|
|
70050
|
+
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
70051
|
+
if (list.stdout.includes(`worktree ${cwd2}
|
|
70052
|
+
`)) {
|
|
70053
|
+
return { cwd: cwd2, branch };
|
|
70054
|
+
}
|
|
70055
|
+
let branchExists = true;
|
|
70056
|
+
try {
|
|
70057
|
+
await runner.run(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], projectRoot);
|
|
70058
|
+
} catch {
|
|
70059
|
+
branchExists = false;
|
|
70060
|
+
}
|
|
70061
|
+
const cmd = branchExists ? ["worktree", "add", cwd2, branch] : ["worktree", "add", "-b", branch, cwd2];
|
|
70062
|
+
await runner.run(cmd, projectRoot);
|
|
70063
|
+
return { cwd: cwd2, branch };
|
|
70064
|
+
}
|
|
70065
|
+
async function removeWorktree(projectRoot, cwd2, runner) {
|
|
70066
|
+
await runner.run(["worktree", "remove", "--force", cwd2], projectRoot);
|
|
70067
|
+
}
|
|
70068
|
+
|
|
69987
70069
|
// apps/cli/src/components/AgentMode.tsx
|
|
69988
70070
|
var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
|
|
70071
|
+
import { join as join14 } from "path";
|
|
70072
|
+
var bunGitRunner = {
|
|
70073
|
+
run: async (args, cwd2) => {
|
|
70074
|
+
const proc = Bun.spawn({ cmd: ["git", ...args], cwd: cwd2, stdout: "pipe", stderr: "pipe" });
|
|
70075
|
+
const stdout = await new Response(proc.stdout).text();
|
|
70076
|
+
const stderr = await new Response(proc.stderr).text();
|
|
70077
|
+
const code = await proc.exited;
|
|
70078
|
+
if (code !== 0) {
|
|
70079
|
+
const err = new Error("git command failed");
|
|
70080
|
+
err.stderr = stderr;
|
|
70081
|
+
err.code = code;
|
|
70082
|
+
throw err;
|
|
70083
|
+
}
|
|
70084
|
+
return { stdout, stderr };
|
|
70085
|
+
}
|
|
70086
|
+
};
|
|
69989
70087
|
var lineCounter = 0;
|
|
69990
70088
|
function nextId() {
|
|
69991
70089
|
lineCounter += 1;
|
|
@@ -70022,7 +70120,26 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70022
70120
|
labels: args.linearLabel.length ? args.linearLabel : cfg.linear.labels
|
|
70023
70121
|
};
|
|
70024
70122
|
const stateCache = new Map;
|
|
70123
|
+
const labelCache = new Map;
|
|
70025
70124
|
const teamKeyOf = (issue) => issue.identifier.split("-")[0];
|
|
70125
|
+
const useWorktree = args.worktree || cfg.useWorktree;
|
|
70126
|
+
const cwdByChange = new Map;
|
|
70127
|
+
async function runScript(label, cmd, cwd2) {
|
|
70128
|
+
appendLog(` ${label}: ${cmd}`, "gray");
|
|
70129
|
+
const proc = Bun.spawn({
|
|
70130
|
+
cmd: ["sh", "-c", cmd],
|
|
70131
|
+
cwd: cwd2,
|
|
70132
|
+
stdout: "ignore",
|
|
70133
|
+
stderr: "pipe",
|
|
70134
|
+
stdin: "ignore"
|
|
70135
|
+
});
|
|
70136
|
+
const code = await proc.exited;
|
|
70137
|
+
if (code !== 0) {
|
|
70138
|
+
const stderr = await new Response(proc.stderr).text();
|
|
70139
|
+
appendLog(`! ${label} exited code ${code}${stderr ? `: ${stderr.trim().split(`
|
|
70140
|
+
`)[0]}` : ""}`, "yellow");
|
|
70141
|
+
}
|
|
70142
|
+
}
|
|
70026
70143
|
const coord2 = new AgentCoordinator({
|
|
70027
70144
|
fetchIssues: (f2) => fetchOpenIssues(apiKey, f2),
|
|
70028
70145
|
scaffold: async (issue) => {
|
|
@@ -70032,7 +70149,27 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70032
70149
|
} catch (err) {
|
|
70033
70150
|
appendLog(`! Linear comment fetch failed for ${issue.identifier}: ${err.message}`, "yellow");
|
|
70034
70151
|
}
|
|
70035
|
-
|
|
70152
|
+
let workerCwd = projectRoot;
|
|
70153
|
+
let scaffoldTasksDir = tasksDir;
|
|
70154
|
+
let scaffoldStatesDir = statesDir;
|
|
70155
|
+
const probeName = issue.identifier.toLowerCase();
|
|
70156
|
+
if (useWorktree) {
|
|
70157
|
+
try {
|
|
70158
|
+
const wt = await createWorktree(projectRoot, probeName, bunGitRunner);
|
|
70159
|
+
workerCwd = wt.cwd;
|
|
70160
|
+
scaffoldTasksDir = join14(wt.cwd, "openspec", "changes");
|
|
70161
|
+
scaffoldStatesDir = join14(wt.cwd, ".ralph", "tasks");
|
|
70162
|
+
appendLog(` ${issue.identifier} worktree: ${wt.cwd} (${wt.branch})`, "gray");
|
|
70163
|
+
} catch (err) {
|
|
70164
|
+
appendLog(`! worktree create failed for ${issue.identifier}: ${err.message} \u2014 falling back to project root`, "yellow");
|
|
70165
|
+
}
|
|
70166
|
+
}
|
|
70167
|
+
const changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue, comments);
|
|
70168
|
+
cwdByChange.set(changeName, workerCwd);
|
|
70169
|
+
if (cfg.setupScript) {
|
|
70170
|
+
await runScript("setup", cfg.setupScript, workerCwd);
|
|
70171
|
+
}
|
|
70172
|
+
return changeName;
|
|
70036
70173
|
},
|
|
70037
70174
|
spawnWorker: (changeName) => {
|
|
70038
70175
|
const cmd = [
|
|
@@ -70050,14 +70187,35 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70050
70187
|
const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
|
|
70051
70188
|
if (maxCost > 0)
|
|
70052
70189
|
cmd.push("--max-cost", String(maxCost));
|
|
70190
|
+
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
70053
70191
|
const proc = Bun.spawn({
|
|
70054
70192
|
cmd,
|
|
70055
|
-
cwd:
|
|
70193
|
+
cwd: cwd2,
|
|
70056
70194
|
stdout: "ignore",
|
|
70057
70195
|
stderr: "ignore",
|
|
70058
70196
|
stdin: "ignore"
|
|
70059
70197
|
});
|
|
70060
|
-
|
|
70198
|
+
const wrapped = proc.exited.then(async (code) => {
|
|
70199
|
+
if (cfg.teardownScript) {
|
|
70200
|
+
try {
|
|
70201
|
+
await runScript("teardown", cfg.teardownScript, cwd2);
|
|
70202
|
+
} catch {}
|
|
70203
|
+
}
|
|
70204
|
+
if (useWorktree && cwd2 !== projectRoot) {
|
|
70205
|
+
const ok = code === 0;
|
|
70206
|
+
if (ok && cfg.cleanupWorktreeOnSuccess) {
|
|
70207
|
+
try {
|
|
70208
|
+
await removeWorktree(projectRoot, cwd2, bunGitRunner);
|
|
70209
|
+
appendLog(` removed worktree ${cwd2}`, "gray");
|
|
70210
|
+
} catch (err) {
|
|
70211
|
+
appendLog(`! worktree remove failed for ${changeName}: ${err.message}`, "yellow");
|
|
70212
|
+
}
|
|
70213
|
+
}
|
|
70214
|
+
}
|
|
70215
|
+
cwdByChange.delete(changeName);
|
|
70216
|
+
return code;
|
|
70217
|
+
});
|
|
70218
|
+
return { exited: wrapped, kill: () => proc.kill() };
|
|
70061
70219
|
},
|
|
70062
70220
|
loadState: () => readAgentState(projectRoot),
|
|
70063
70221
|
saveState: (s) => writeAgentState(projectRoot, s),
|
|
@@ -70075,6 +70233,17 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70075
70233
|
stateCache.set(team, map2);
|
|
70076
70234
|
}
|
|
70077
70235
|
return map2.get(stateName.toLowerCase()) ?? null;
|
|
70236
|
+
},
|
|
70237
|
+
addLabel: (issue, labelId) => addLabelToIssue(apiKey, issue.id, labelId),
|
|
70238
|
+
resolveLabelId: async (issue, labelName) => {
|
|
70239
|
+
const team = teamKeyOf(issue);
|
|
70240
|
+
let map2 = labelCache.get(team);
|
|
70241
|
+
if (!map2) {
|
|
70242
|
+
const labels = await fetchIssueLabels(apiKey, team);
|
|
70243
|
+
map2 = new Map(labels.map((l) => [l.name.toLowerCase(), l.id]));
|
|
70244
|
+
labelCache.set(team, map2);
|
|
70245
|
+
}
|
|
70246
|
+
return map2.get(labelName.toLowerCase()) ?? null;
|
|
70078
70247
|
}
|
|
70079
70248
|
}
|
|
70080
70249
|
}, {
|
|
@@ -70082,6 +70251,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70082
70251
|
filter: filter2,
|
|
70083
70252
|
inProgressStatus: cfg.linear.inProgressStatus,
|
|
70084
70253
|
doneStatus: cfg.linear.doneStatus,
|
|
70254
|
+
doneLabel: cfg.linear.doneLabel,
|
|
70085
70255
|
postComments: cfg.linear.postComments
|
|
70086
70256
|
});
|
|
70087
70257
|
coordRef.current = coord2;
|
|
@@ -70163,11 +70333,11 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
70163
70333
|
}
|
|
70164
70334
|
|
|
70165
70335
|
// packages/openspec/src/openspec-change-store.ts
|
|
70166
|
-
import { join as
|
|
70336
|
+
import { join as join15, dirname as dirname3 } from "path";
|
|
70167
70337
|
import { readdir, mkdir as mkdir2 } from "fs/promises";
|
|
70168
70338
|
function resolveOpenspecBin() {
|
|
70169
70339
|
const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
|
|
70170
|
-
return
|
|
70340
|
+
return join15(dirname3(pkgJsonPath), "bin", "openspec.js");
|
|
70171
70341
|
}
|
|
70172
70342
|
function runOpenspec(args, options = {}) {
|
|
70173
70343
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
@@ -70193,7 +70363,7 @@ class OpenSpecChangeStore {
|
|
|
70193
70363
|
}
|
|
70194
70364
|
}
|
|
70195
70365
|
getChangeDirectory(name) {
|
|
70196
|
-
return
|
|
70366
|
+
return join15("openspec", "changes", name);
|
|
70197
70367
|
}
|
|
70198
70368
|
async listChanges() {
|
|
70199
70369
|
const result2 = runOpenspec(["list", "--json"]);
|
|
@@ -70207,7 +70377,7 @@ class OpenSpecChangeStore {
|
|
|
70207
70377
|
}
|
|
70208
70378
|
} catch {}
|
|
70209
70379
|
}
|
|
70210
|
-
const changesDir =
|
|
70380
|
+
const changesDir = join15("openspec", "changes");
|
|
70211
70381
|
if (!await Bun.file(changesDir).exists())
|
|
70212
70382
|
return [];
|
|
70213
70383
|
try {
|
|
@@ -70218,18 +70388,18 @@ class OpenSpecChangeStore {
|
|
|
70218
70388
|
}
|
|
70219
70389
|
}
|
|
70220
70390
|
async readTaskList(name) {
|
|
70221
|
-
const file = Bun.file(
|
|
70391
|
+
const file = Bun.file(join15("openspec", "changes", name, "tasks.md"));
|
|
70222
70392
|
if (!await file.exists())
|
|
70223
70393
|
return "";
|
|
70224
70394
|
return await file.text();
|
|
70225
70395
|
}
|
|
70226
70396
|
async writeTaskList(name, content) {
|
|
70227
|
-
const path =
|
|
70397
|
+
const path = join15("openspec", "changes", name, "tasks.md");
|
|
70228
70398
|
await mkdir2(dirname3(path), { recursive: true });
|
|
70229
70399
|
await Bun.write(path, content);
|
|
70230
70400
|
}
|
|
70231
70401
|
async appendSteering(name, message) {
|
|
70232
|
-
const path =
|
|
70402
|
+
const path = join15("openspec", "changes", name, "steering.md");
|
|
70233
70403
|
const file = Bun.file(path);
|
|
70234
70404
|
const existing = await file.exists() ? await file.text() : null;
|
|
70235
70405
|
const updated = existing ? `${message}
|
|
@@ -70240,7 +70410,7 @@ ${existing.trimStart()}` : `${message}
|
|
|
70240
70410
|
await Bun.write(path, updated);
|
|
70241
70411
|
}
|
|
70242
70412
|
async readSection(name, artifact, heading) {
|
|
70243
|
-
const file = Bun.file(
|
|
70413
|
+
const file = Bun.file(join15("openspec", "changes", name, artifact));
|
|
70244
70414
|
if (!await file.exists())
|
|
70245
70415
|
return "";
|
|
70246
70416
|
const content = await file.text();
|
|
@@ -70321,8 +70491,8 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
70321
70491
|
message: "Error: --name is required for status mode"
|
|
70322
70492
|
}, undefined, false, undefined, this);
|
|
70323
70493
|
}
|
|
70324
|
-
const stateDir =
|
|
70325
|
-
if (getStorage().read(
|
|
70494
|
+
const stateDir = join16(statesDir, args.name);
|
|
70495
|
+
if (getStorage().read(join16(stateDir, ".ralph-state.json")) === null) {
|
|
70326
70496
|
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
70327
70497
|
message: `Error: change '${args.name}' not found`
|
|
70328
70498
|
}, undefined, false, undefined, this);
|
|
@@ -70379,7 +70549,7 @@ if (typeof globalThis.Bun === "undefined") {
|
|
|
70379
70549
|
async function findProjectRoot() {
|
|
70380
70550
|
let dir = process.cwd();
|
|
70381
70551
|
while (dir !== "/") {
|
|
70382
|
-
if (await exists(
|
|
70552
|
+
if (await exists(join17(dir, "openspec")))
|
|
70383
70553
|
return dir;
|
|
70384
70554
|
dir = resolve(dir, "..");
|
|
70385
70555
|
}
|
|
@@ -70414,11 +70584,11 @@ try {
|
|
|
70414
70584
|
capture("command_run", { mode: args.mode, engine: args.engine, model: args.model });
|
|
70415
70585
|
try {
|
|
70416
70586
|
const projectRoot = await findProjectRoot();
|
|
70417
|
-
const statesDir =
|
|
70418
|
-
const tasksDir =
|
|
70587
|
+
const statesDir = join17(projectRoot, ".ralph", "tasks");
|
|
70588
|
+
const tasksDir = join17(projectRoot, "openspec", "changes");
|
|
70419
70589
|
if (args.mode === "init") {
|
|
70420
70590
|
await mkdir3(statesDir, { recursive: true });
|
|
70421
|
-
const openspecBin =
|
|
70591
|
+
const openspecBin = join17(dirname4(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
|
|
70422
70592
|
Bun.spawnSync({
|
|
70423
70593
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
70424
70594
|
stdio: ["inherit", "inherit", "inherit"],
|
|
@@ -70426,13 +70596,13 @@ try {
|
|
|
70426
70596
|
});
|
|
70427
70597
|
}
|
|
70428
70598
|
if (args.mode === "task" && args.name) {
|
|
70429
|
-
await mkdir3(
|
|
70430
|
-
await mkdir3(
|
|
70599
|
+
await mkdir3(join17(statesDir, args.name), { recursive: true });
|
|
70600
|
+
await mkdir3(join17(tasksDir, args.name), { recursive: true });
|
|
70431
70601
|
}
|
|
70432
70602
|
if (args.mode === "agent") {
|
|
70433
70603
|
await mkdir3(statesDir, { recursive: true });
|
|
70434
70604
|
await mkdir3(tasksDir, { recursive: true });
|
|
70435
|
-
await mkdir3(
|
|
70605
|
+
await mkdir3(join17(projectRoot, ".ralph"), { recursive: true });
|
|
70436
70606
|
}
|
|
70437
70607
|
await runWithContext(createDefaultContext(), async () => {
|
|
70438
70608
|
const { waitUntilExit } = render_default(import_react59.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
|