@neriros/ralphy 2.6.0 → 2.7.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 +716 -43
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -50548,8 +50548,8 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50548
50548
|
});
|
|
50549
50549
|
|
|
50550
50550
|
// apps/cli/src/index.ts
|
|
50551
|
-
import { resolve, join as
|
|
50552
|
-
import { exists, mkdir as
|
|
50551
|
+
import { resolve, join as join15, dirname as dirname4 } from "path";
|
|
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
|
|
50555
50555
|
import { Stream } from "stream";
|
|
@@ -56091,7 +56091,7 @@ var import_react20 = __toESM(require_react(), 1);
|
|
|
56091
56091
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/hooks/use-focus-manager.js
|
|
56092
56092
|
var import_react21 = __toESM(require_react(), 1);
|
|
56093
56093
|
// apps/cli/src/index.ts
|
|
56094
|
-
var
|
|
56094
|
+
var import_react59 = __toESM(require_react(), 1);
|
|
56095
56095
|
|
|
56096
56096
|
// packages/output/src/output.ts
|
|
56097
56097
|
var formatters = {
|
|
@@ -56117,7 +56117,7 @@ function log(msg) {
|
|
|
56117
56117
|
}
|
|
56118
56118
|
|
|
56119
56119
|
// apps/cli/src/cli.ts
|
|
56120
|
-
var VALID_MODES = new Set(["task", "list", "status", "init"]);
|
|
56120
|
+
var VALID_MODES = new Set(["task", "list", "status", "init", "agent"]);
|
|
56121
56121
|
var VALID_MODELS = new Set(["haiku", "sonnet", "opus"]);
|
|
56122
56122
|
var HELP_TEXT = [
|
|
56123
56123
|
"Usage: ralph <command> [options]",
|
|
@@ -56127,6 +56127,7 @@ var HELP_TEXT = [
|
|
|
56127
56127
|
" list List active changes",
|
|
56128
56128
|
" status Show detailed change status",
|
|
56129
56129
|
" init Initialize OpenSpec in current directory",
|
|
56130
|
+
" agent Poll Linear for new tasks and run loops concurrently",
|
|
56130
56131
|
"",
|
|
56131
56132
|
"Options:",
|
|
56132
56133
|
" --name <name> Change name (required for most commands)",
|
|
@@ -56143,6 +56144,15 @@ var HELP_TEXT = [
|
|
|
56143
56144
|
" --unlimited No iteration limit (default)",
|
|
56144
56145
|
" --log Log raw engine stream",
|
|
56145
56146
|
" --verbose Verbose output",
|
|
56147
|
+
"",
|
|
56148
|
+
"Agent mode options (require LINEAR_API_KEY env var):",
|
|
56149
|
+
" --linear-team <key> Linear team key (e.g. ENG)",
|
|
56150
|
+
" --linear-assignee <id> Filter by assignee (user id, email, or 'me')",
|
|
56151
|
+
" --linear-status <name> Filter by status name (repeatable, e.g. Todo, In Progress)",
|
|
56152
|
+
" --linear-label <name> Filter by label name (repeatable, any-of)",
|
|
56153
|
+
" --poll-interval <s> Seconds between Linear polls (default: 60)",
|
|
56154
|
+
" --concurrency <n> Max concurrent task loops (default: 1)",
|
|
56155
|
+
"",
|
|
56146
56156
|
" --help, -h Show this help message",
|
|
56147
56157
|
"",
|
|
56148
56158
|
"Examples:",
|
|
@@ -56171,7 +56181,13 @@ async function parseArgs(argv) {
|
|
|
56171
56181
|
maxConsecutiveFailures: 5,
|
|
56172
56182
|
delay: 0,
|
|
56173
56183
|
log: false,
|
|
56174
|
-
verbose: false
|
|
56184
|
+
verbose: false,
|
|
56185
|
+
linearTeam: "",
|
|
56186
|
+
linearAssignee: "",
|
|
56187
|
+
linearStatus: [],
|
|
56188
|
+
linearLabel: [],
|
|
56189
|
+
pollInterval: 60,
|
|
56190
|
+
concurrency: 1
|
|
56175
56191
|
};
|
|
56176
56192
|
let expectModel = false;
|
|
56177
56193
|
let expectModelFlag = false;
|
|
@@ -56185,6 +56201,12 @@ async function parseArgs(argv) {
|
|
|
56185
56201
|
let expectMaxIterations = false;
|
|
56186
56202
|
let expectTimeout = false;
|
|
56187
56203
|
let expectPushInterval = false;
|
|
56204
|
+
let expectLinearTeam = false;
|
|
56205
|
+
let expectLinearAssignee = false;
|
|
56206
|
+
let expectLinearStatus = false;
|
|
56207
|
+
let expectLinearLabel = false;
|
|
56208
|
+
let expectPollInterval = false;
|
|
56209
|
+
let expectConcurrency = false;
|
|
56188
56210
|
for (const arg of argv) {
|
|
56189
56211
|
if (expectModel) {
|
|
56190
56212
|
if (VALID_MODELS.has(arg)) {
|
|
@@ -56250,6 +56272,36 @@ async function parseArgs(argv) {
|
|
|
56250
56272
|
expectPushInterval = false;
|
|
56251
56273
|
continue;
|
|
56252
56274
|
}
|
|
56275
|
+
if (expectLinearTeam) {
|
|
56276
|
+
result2.linearTeam = arg;
|
|
56277
|
+
expectLinearTeam = false;
|
|
56278
|
+
continue;
|
|
56279
|
+
}
|
|
56280
|
+
if (expectLinearAssignee) {
|
|
56281
|
+
result2.linearAssignee = arg;
|
|
56282
|
+
expectLinearAssignee = false;
|
|
56283
|
+
continue;
|
|
56284
|
+
}
|
|
56285
|
+
if (expectLinearStatus) {
|
|
56286
|
+
result2.linearStatus.push(arg);
|
|
56287
|
+
expectLinearStatus = false;
|
|
56288
|
+
continue;
|
|
56289
|
+
}
|
|
56290
|
+
if (expectLinearLabel) {
|
|
56291
|
+
result2.linearLabel.push(arg);
|
|
56292
|
+
expectLinearLabel = false;
|
|
56293
|
+
continue;
|
|
56294
|
+
}
|
|
56295
|
+
if (expectPollInterval) {
|
|
56296
|
+
result2.pollInterval = parseInt(arg, 10);
|
|
56297
|
+
expectPollInterval = false;
|
|
56298
|
+
continue;
|
|
56299
|
+
}
|
|
56300
|
+
if (expectConcurrency) {
|
|
56301
|
+
result2.concurrency = parseInt(arg, 10);
|
|
56302
|
+
expectConcurrency = false;
|
|
56303
|
+
continue;
|
|
56304
|
+
}
|
|
56253
56305
|
switch (arg) {
|
|
56254
56306
|
case "--claude":
|
|
56255
56307
|
if (result2.engineSet && result2.engine !== "claude") {
|
|
@@ -56308,6 +56360,24 @@ async function parseArgs(argv) {
|
|
|
56308
56360
|
case "--verbose":
|
|
56309
56361
|
result2.verbose = true;
|
|
56310
56362
|
break;
|
|
56363
|
+
case "--linear-team":
|
|
56364
|
+
expectLinearTeam = true;
|
|
56365
|
+
break;
|
|
56366
|
+
case "--linear-assignee":
|
|
56367
|
+
expectLinearAssignee = true;
|
|
56368
|
+
break;
|
|
56369
|
+
case "--linear-status":
|
|
56370
|
+
expectLinearStatus = true;
|
|
56371
|
+
break;
|
|
56372
|
+
case "--linear-label":
|
|
56373
|
+
expectLinearLabel = true;
|
|
56374
|
+
break;
|
|
56375
|
+
case "--poll-interval":
|
|
56376
|
+
expectPollInterval = true;
|
|
56377
|
+
break;
|
|
56378
|
+
case "--concurrency":
|
|
56379
|
+
expectConcurrency = true;
|
|
56380
|
+
break;
|
|
56311
56381
|
default:
|
|
56312
56382
|
if (VALID_MODES.has(arg)) {
|
|
56313
56383
|
result2.mode = arg;
|
|
@@ -56374,8 +56444,8 @@ function createDefaultContext() {
|
|
|
56374
56444
|
}
|
|
56375
56445
|
|
|
56376
56446
|
// apps/cli/src/components/App.tsx
|
|
56377
|
-
var
|
|
56378
|
-
import { join as
|
|
56447
|
+
var import_react58 = __toESM(require_react(), 1);
|
|
56448
|
+
import { join as join14 } from "path";
|
|
56379
56449
|
|
|
56380
56450
|
// packages/core/src/state.ts
|
|
56381
56451
|
import { join as join2 } from "path";
|
|
@@ -69501,12 +69571,603 @@ function TaskLoop({ opts }) {
|
|
|
69501
69571
|
}, undefined, true, undefined, this);
|
|
69502
69572
|
}
|
|
69503
69573
|
|
|
69574
|
+
// apps/cli/src/components/AgentMode.tsx
|
|
69575
|
+
var import_react57 = __toESM(require_react(), 1);
|
|
69576
|
+
|
|
69577
|
+
// apps/cli/src/agent/linear.ts
|
|
69578
|
+
var OPEN_STATE_TYPES = ["unstarted", "started", "backlog"];
|
|
69579
|
+
var LINEAR_API = "https://api.linear.app/graphql";
|
|
69580
|
+
async function fetchOpenIssues(apiKey, filter2) {
|
|
69581
|
+
const where = {};
|
|
69582
|
+
if (filter2.team)
|
|
69583
|
+
where.team = { key: { eq: filter2.team } };
|
|
69584
|
+
if (filter2.assignee) {
|
|
69585
|
+
if (filter2.assignee === "me") {
|
|
69586
|
+
where.assignee = { isMe: { eq: true } };
|
|
69587
|
+
} else if (filter2.assignee.includes("@")) {
|
|
69588
|
+
where.assignee = { email: { eq: filter2.assignee } };
|
|
69589
|
+
} else {
|
|
69590
|
+
where.assignee = { id: { eq: filter2.assignee } };
|
|
69591
|
+
}
|
|
69592
|
+
}
|
|
69593
|
+
if (filter2.statuses && filter2.statuses.length > 0) {
|
|
69594
|
+
where.state = { name: { in: filter2.statuses } };
|
|
69595
|
+
} else {
|
|
69596
|
+
where.state = { type: { in: [...OPEN_STATE_TYPES] } };
|
|
69597
|
+
}
|
|
69598
|
+
if (filter2.labels && filter2.labels.length > 0) {
|
|
69599
|
+
where.labels = { some: { name: { in: filter2.labels } } };
|
|
69600
|
+
}
|
|
69601
|
+
const query = `query Issues($filter: IssueFilter) {
|
|
69602
|
+
issues(filter: $filter, first: 50) {
|
|
69603
|
+
nodes {
|
|
69604
|
+
id identifier title description url
|
|
69605
|
+
state { name type }
|
|
69606
|
+
assignee { id email name }
|
|
69607
|
+
labels { nodes { name } }
|
|
69608
|
+
}
|
|
69609
|
+
}
|
|
69610
|
+
}`;
|
|
69611
|
+
const data = await linearRequest(apiKey, query, {
|
|
69612
|
+
filter: where
|
|
69613
|
+
});
|
|
69614
|
+
return data.issues.nodes.map((n) => ({
|
|
69615
|
+
id: n.id,
|
|
69616
|
+
identifier: n.identifier,
|
|
69617
|
+
title: n.title,
|
|
69618
|
+
description: n.description,
|
|
69619
|
+
url: n.url,
|
|
69620
|
+
state: n.state,
|
|
69621
|
+
assignee: n.assignee,
|
|
69622
|
+
labels: n.labels.nodes.map((l) => l.name)
|
|
69623
|
+
}));
|
|
69624
|
+
}
|
|
69625
|
+
async function linearRequest(apiKey, query, variables) {
|
|
69626
|
+
const res = await fetch(LINEAR_API, {
|
|
69627
|
+
method: "POST",
|
|
69628
|
+
headers: { "Content-Type": "application/json", Authorization: apiKey },
|
|
69629
|
+
body: JSON.stringify({ query, variables })
|
|
69630
|
+
});
|
|
69631
|
+
if (!res.ok) {
|
|
69632
|
+
const err = new Error("Linear API request failed");
|
|
69633
|
+
err.status = res.status;
|
|
69634
|
+
err.body = await res.text();
|
|
69635
|
+
throw err;
|
|
69636
|
+
}
|
|
69637
|
+
const json = await res.json();
|
|
69638
|
+
if (json.errors?.length) {
|
|
69639
|
+
const err = new Error("Linear API returned errors");
|
|
69640
|
+
err.messages = json.errors.map((e) => e.message);
|
|
69641
|
+
throw err;
|
|
69642
|
+
}
|
|
69643
|
+
if (!json.data) {
|
|
69644
|
+
throw new Error("Linear API returned no data");
|
|
69645
|
+
}
|
|
69646
|
+
return json.data;
|
|
69647
|
+
}
|
|
69648
|
+
async function addIssueComment(apiKey, issueId, body) {
|
|
69649
|
+
const mutation = `mutation Comment($issueId: String!, $body: String!) {
|
|
69650
|
+
commentCreate(input: { issueId: $issueId, body: $body }) { success }
|
|
69651
|
+
}`;
|
|
69652
|
+
await linearRequest(apiKey, mutation, {
|
|
69653
|
+
issueId,
|
|
69654
|
+
body
|
|
69655
|
+
});
|
|
69656
|
+
}
|
|
69657
|
+
async function fetchIssueComments(apiKey, issueId) {
|
|
69658
|
+
const query = `query Comments($id: String!) {
|
|
69659
|
+
issue(id: $id) {
|
|
69660
|
+
comments(first: 50) {
|
|
69661
|
+
nodes { id body createdAt user { name email } }
|
|
69662
|
+
}
|
|
69663
|
+
}
|
|
69664
|
+
}`;
|
|
69665
|
+
const data = await linearRequest(apiKey, query, { id: issueId });
|
|
69666
|
+
return data.issue?.comments.nodes ?? [];
|
|
69667
|
+
}
|
|
69668
|
+
async function fetchWorkflowStates(apiKey, teamKey) {
|
|
69669
|
+
const query = `query States($team: String!) {
|
|
69670
|
+
workflowStates(filter: { team: { key: { eq: $team } } }, first: 50) {
|
|
69671
|
+
nodes { id name type }
|
|
69672
|
+
}
|
|
69673
|
+
}`;
|
|
69674
|
+
const data = await linearRequest(apiKey, query, {
|
|
69675
|
+
team: teamKey
|
|
69676
|
+
});
|
|
69677
|
+
return data.workflowStates.nodes;
|
|
69678
|
+
}
|
|
69679
|
+
async function updateIssueState(apiKey, issueId, stateId) {
|
|
69680
|
+
const mutation = `mutation Update($id: String!, $stateId: String!) {
|
|
69681
|
+
issueUpdate(id: $id, input: { stateId: $stateId }) { success }
|
|
69682
|
+
}`;
|
|
69683
|
+
await linearRequest(apiKey, mutation, {
|
|
69684
|
+
id: issueId,
|
|
69685
|
+
stateId
|
|
69686
|
+
});
|
|
69687
|
+
}
|
|
69688
|
+
|
|
69689
|
+
// apps/cli/src/agent/state.ts
|
|
69690
|
+
import { join as join10 } from "path";
|
|
69691
|
+
var AgentStateSchema = exports_external.object({
|
|
69692
|
+
processedIssueIds: exports_external.array(exports_external.string()).default([]),
|
|
69693
|
+
lastPollAt: exports_external.string().nullable().default(null)
|
|
69694
|
+
});
|
|
69695
|
+
function statePath(projectRoot) {
|
|
69696
|
+
return join10(projectRoot, ".ralph", "agent-state.json");
|
|
69697
|
+
}
|
|
69698
|
+
async function readAgentState(projectRoot) {
|
|
69699
|
+
const file = Bun.file(statePath(projectRoot));
|
|
69700
|
+
if (!await file.exists()) {
|
|
69701
|
+
return AgentStateSchema.parse({});
|
|
69702
|
+
}
|
|
69703
|
+
return AgentStateSchema.parse(await file.json());
|
|
69704
|
+
}
|
|
69705
|
+
async function writeAgentState(projectRoot, state) {
|
|
69706
|
+
await Bun.write(statePath(projectRoot), JSON.stringify(state, null, 2) + `
|
|
69707
|
+
`);
|
|
69708
|
+
}
|
|
69709
|
+
|
|
69710
|
+
// apps/cli/src/agent/scaffold.ts
|
|
69711
|
+
import { join as join11 } from "path";
|
|
69712
|
+
import { mkdir } from "fs/promises";
|
|
69713
|
+
function changeNameForIssue(issue) {
|
|
69714
|
+
const slug = issue.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
69715
|
+
return slug ? `${issue.identifier.toLowerCase()}-${slug}` : issue.identifier.toLowerCase();
|
|
69716
|
+
}
|
|
69717
|
+
async function scaffoldChangeForIssue(tasksDir, statesDir, issue, comments = []) {
|
|
69718
|
+
const name = changeNameForIssue(issue);
|
|
69719
|
+
const changeDir = join11(tasksDir, name);
|
|
69720
|
+
const stateDir = join11(statesDir, name);
|
|
69721
|
+
await mkdir(changeDir, { recursive: true });
|
|
69722
|
+
await mkdir(join11(changeDir, "specs"), { recursive: true });
|
|
69723
|
+
await mkdir(stateDir, { recursive: true });
|
|
69724
|
+
const commentsBlock = comments.length > 0 ? [
|
|
69725
|
+
"",
|
|
69726
|
+
"## Linear comments",
|
|
69727
|
+
"",
|
|
69728
|
+
...comments.flatMap((c) => [
|
|
69729
|
+
`**${c.user?.name ?? "unknown"}** \u2014 ${c.createdAt}`,
|
|
69730
|
+
"",
|
|
69731
|
+
c.body.trim(),
|
|
69732
|
+
""
|
|
69733
|
+
])
|
|
69734
|
+
] : [];
|
|
69735
|
+
const proposal = [
|
|
69736
|
+
`# ${issue.identifier}: ${issue.title}`,
|
|
69737
|
+
"",
|
|
69738
|
+
`Source: [${issue.identifier}](${issue.url})`,
|
|
69739
|
+
`Status: ${issue.state.name}`,
|
|
69740
|
+
issue.assignee ? `Assignee: ${issue.assignee.name}` : "",
|
|
69741
|
+
issue.labels.length ? `Labels: ${issue.labels.join(", ")}` : "",
|
|
69742
|
+
"",
|
|
69743
|
+
"## Description",
|
|
69744
|
+
"",
|
|
69745
|
+
issue.description?.trim() || "_No description provided in Linear._",
|
|
69746
|
+
...commentsBlock,
|
|
69747
|
+
"",
|
|
69748
|
+
"## Steering",
|
|
69749
|
+
"",
|
|
69750
|
+
"_Add steering notes here as the loop runs._",
|
|
69751
|
+
""
|
|
69752
|
+
].filter((l) => l !== "").join(`
|
|
69753
|
+
`);
|
|
69754
|
+
const tasks = [
|
|
69755
|
+
`# Tasks for ${issue.identifier}`,
|
|
69756
|
+
"",
|
|
69757
|
+
`- [ ] Read the Linear issue at ${issue.url} and break it into concrete subtasks`,
|
|
69758
|
+
`- [ ] Implement the changes described in proposal.md`,
|
|
69759
|
+
`- [ ] Add or update tests covering the new behavior`,
|
|
69760
|
+
`- [ ] Run \`bun run lint\` and \`bun run test\` and fix any failures`,
|
|
69761
|
+
""
|
|
69762
|
+
].join(`
|
|
69763
|
+
`);
|
|
69764
|
+
const design = [
|
|
69765
|
+
`# Design for ${issue.identifier}`,
|
|
69766
|
+
"",
|
|
69767
|
+
"_Fill in the technical design as you work through the issue._",
|
|
69768
|
+
""
|
|
69769
|
+
].join(`
|
|
69770
|
+
`);
|
|
69771
|
+
await Bun.write(join11(changeDir, "proposal.md"), proposal);
|
|
69772
|
+
await Bun.write(join11(changeDir, "tasks.md"), tasks);
|
|
69773
|
+
await Bun.write(join11(changeDir, "design.md"), design);
|
|
69774
|
+
return name;
|
|
69775
|
+
}
|
|
69776
|
+
|
|
69777
|
+
// apps/cli/src/agent/config.ts
|
|
69778
|
+
import { join as join12 } from "path";
|
|
69779
|
+
var RalphyConfigSchema = exports_external.object({
|
|
69780
|
+
concurrency: exports_external.number().int().positive().default(1),
|
|
69781
|
+
pollIntervalSeconds: exports_external.number().int().positive().default(60),
|
|
69782
|
+
maxIterationsPerTask: exports_external.number().int().nonnegative().default(0),
|
|
69783
|
+
maxCostUsdPerTask: exports_external.number().nonnegative().default(0),
|
|
69784
|
+
engine: exports_external.enum(["claude", "codex"]).default("claude"),
|
|
69785
|
+
model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
|
|
69786
|
+
linear: exports_external.object({
|
|
69787
|
+
team: exports_external.string().optional(),
|
|
69788
|
+
assignee: exports_external.string().optional(),
|
|
69789
|
+
statuses: exports_external.array(exports_external.string()).default([]),
|
|
69790
|
+
labels: exports_external.union([exports_external.array(exports_external.string()), exports_external.string()]).transform((v) => typeof v === "string" ? [v] : v).default([]),
|
|
69791
|
+
inProgressStatus: exports_external.string().optional(),
|
|
69792
|
+
doneStatus: exports_external.string().optional(),
|
|
69793
|
+
postComments: exports_external.boolean().default(true)
|
|
69794
|
+
}).default({ statuses: [], labels: [], postComments: true })
|
|
69795
|
+
}).default({
|
|
69796
|
+
concurrency: 1,
|
|
69797
|
+
pollIntervalSeconds: 60,
|
|
69798
|
+
maxIterationsPerTask: 0,
|
|
69799
|
+
maxCostUsdPerTask: 0,
|
|
69800
|
+
engine: "claude",
|
|
69801
|
+
model: "opus",
|
|
69802
|
+
linear: { statuses: [], labels: [], postComments: true }
|
|
69803
|
+
});
|
|
69804
|
+
async function loadRalphyConfig(projectRoot) {
|
|
69805
|
+
const path = join12(projectRoot, "ralphy.config.json");
|
|
69806
|
+
const file = Bun.file(path);
|
|
69807
|
+
if (!await file.exists()) {
|
|
69808
|
+
return RalphyConfigSchema.parse({});
|
|
69809
|
+
}
|
|
69810
|
+
const raw = await file.json();
|
|
69811
|
+
return RalphyConfigSchema.parse(raw);
|
|
69812
|
+
}
|
|
69813
|
+
async function ensureRalphyConfig(projectRoot) {
|
|
69814
|
+
const path = join12(projectRoot, "ralphy.config.json");
|
|
69815
|
+
const file = Bun.file(path);
|
|
69816
|
+
if (await file.exists())
|
|
69817
|
+
return path;
|
|
69818
|
+
const defaults2 = RalphyConfigSchema.parse({});
|
|
69819
|
+
await Bun.write(path, JSON.stringify(defaults2, null, 2) + `
|
|
69820
|
+
`);
|
|
69821
|
+
return path;
|
|
69822
|
+
}
|
|
69823
|
+
|
|
69824
|
+
// apps/cli/src/agent/coordinator.ts
|
|
69825
|
+
class AgentCoordinator {
|
|
69826
|
+
deps;
|
|
69827
|
+
opts;
|
|
69828
|
+
workers = [];
|
|
69829
|
+
pendingIds = new Set;
|
|
69830
|
+
queue = [];
|
|
69831
|
+
state = null;
|
|
69832
|
+
stopped = false;
|
|
69833
|
+
constructor(deps, opts) {
|
|
69834
|
+
this.deps = deps;
|
|
69835
|
+
this.opts = opts;
|
|
69836
|
+
}
|
|
69837
|
+
get activeCount() {
|
|
69838
|
+
return this.workers.length;
|
|
69839
|
+
}
|
|
69840
|
+
get queuedCount() {
|
|
69841
|
+
return this.queue.length;
|
|
69842
|
+
}
|
|
69843
|
+
get activeWorkers() {
|
|
69844
|
+
return this.workers;
|
|
69845
|
+
}
|
|
69846
|
+
async init() {
|
|
69847
|
+
this.state = await this.deps.loadState();
|
|
69848
|
+
}
|
|
69849
|
+
async pollOnce() {
|
|
69850
|
+
if (this.stopped)
|
|
69851
|
+
return { found: 0, added: 0 };
|
|
69852
|
+
let issues;
|
|
69853
|
+
try {
|
|
69854
|
+
issues = await this.deps.fetchIssues(this.opts.filter);
|
|
69855
|
+
} catch (err) {
|
|
69856
|
+
this.deps.onLog(`! Linear poll failed: ${err.message}`, "red");
|
|
69857
|
+
return { found: 0, added: 0 };
|
|
69858
|
+
}
|
|
69859
|
+
const state = this.state;
|
|
69860
|
+
const seen = new Set(state.processedIssueIds);
|
|
69861
|
+
const queued = new Set(this.queue.map((i) => i.id));
|
|
69862
|
+
const active = new Set(this.workers.map((w) => w.issueId));
|
|
69863
|
+
let added = 0;
|
|
69864
|
+
for (const issue of issues) {
|
|
69865
|
+
if (seen.has(issue.id))
|
|
69866
|
+
continue;
|
|
69867
|
+
if (queued.has(issue.id))
|
|
69868
|
+
continue;
|
|
69869
|
+
if (active.has(issue.id))
|
|
69870
|
+
continue;
|
|
69871
|
+
if (this.pendingIds.has(issue.id))
|
|
69872
|
+
continue;
|
|
69873
|
+
this.queue.push(issue);
|
|
69874
|
+
added += 1;
|
|
69875
|
+
}
|
|
69876
|
+
state.lastPollAt = new Date().toISOString();
|
|
69877
|
+
await this.deps.saveState(state);
|
|
69878
|
+
this.spawnNext();
|
|
69879
|
+
return { found: issues.length, added };
|
|
69880
|
+
}
|
|
69881
|
+
spawnNext() {
|
|
69882
|
+
if (this.stopped || !this.state)
|
|
69883
|
+
return;
|
|
69884
|
+
while (this.workers.length + this.pendingIds.size < this.opts.concurrency && this.queue.length > 0) {
|
|
69885
|
+
const issue = this.queue.shift();
|
|
69886
|
+
this.pendingIds.add(issue.id);
|
|
69887
|
+
this.launchWorker(issue);
|
|
69888
|
+
}
|
|
69889
|
+
}
|
|
69890
|
+
async launchWorker(issue) {
|
|
69891
|
+
let changeName;
|
|
69892
|
+
try {
|
|
69893
|
+
changeName = await this.deps.scaffold(issue);
|
|
69894
|
+
} catch (err) {
|
|
69895
|
+
this.pendingIds.delete(issue.id);
|
|
69896
|
+
this.deps.onLog(`! scaffold failed for ${issue.identifier}: ${err.message}`, "red");
|
|
69897
|
+
this.spawnNext();
|
|
69898
|
+
return;
|
|
69899
|
+
}
|
|
69900
|
+
if (this.stopped) {
|
|
69901
|
+
this.pendingIds.delete(issue.id);
|
|
69902
|
+
return;
|
|
69903
|
+
}
|
|
69904
|
+
this.deps.onLog(`\u25B6 ${issue.identifier} \u2192 ${changeName} (worker started)`, "cyan");
|
|
69905
|
+
const handle = this.deps.spawnWorker(changeName, issue);
|
|
69906
|
+
const worker = {
|
|
69907
|
+
changeName,
|
|
69908
|
+
issueId: issue.id,
|
|
69909
|
+
issueIdentifier: issue.identifier,
|
|
69910
|
+
kill: handle.kill
|
|
69911
|
+
};
|
|
69912
|
+
this.workers.push(worker);
|
|
69913
|
+
this.pendingIds.delete(issue.id);
|
|
69914
|
+
this.deps.onWorkersChanged();
|
|
69915
|
+
this.notifyStarted(issue, changeName);
|
|
69916
|
+
handle.exited.then((code) => {
|
|
69917
|
+
const idx = this.workers.indexOf(worker);
|
|
69918
|
+
if (idx >= 0)
|
|
69919
|
+
this.workers.splice(idx, 1);
|
|
69920
|
+
const ok = code === 0;
|
|
69921
|
+
this.deps.onLog(`${ok ? "\u2713" : "\u2717"} ${issue.identifier} \u2192 ${changeName} exited (code ${code})`, ok ? "green" : "red");
|
|
69922
|
+
if (ok && this.state && !this.state.processedIssueIds.includes(issue.id)) {
|
|
69923
|
+
this.state.processedIssueIds.push(issue.id);
|
|
69924
|
+
this.deps.saveState(this.state);
|
|
69925
|
+
}
|
|
69926
|
+
this.notifyExited(issue, changeName, code);
|
|
69927
|
+
this.deps.onWorkersChanged();
|
|
69928
|
+
this.spawnNext();
|
|
69929
|
+
});
|
|
69930
|
+
}
|
|
69931
|
+
async notifyStarted(issue, changeName) {
|
|
69932
|
+
const updater = this.deps.updater;
|
|
69933
|
+
if (!updater)
|
|
69934
|
+
return;
|
|
69935
|
+
if (this.opts.postComments !== false) {
|
|
69936
|
+
try {
|
|
69937
|
+
await updater.postComment(issue, `\uD83E\uDD16 Ralph started working on this issue. Tracking change: \`${changeName}\``);
|
|
69938
|
+
} catch (err) {
|
|
69939
|
+
this.deps.onLog(`! Linear comment failed for ${issue.identifier}: ${err.message}`, "red");
|
|
69940
|
+
}
|
|
69941
|
+
}
|
|
69942
|
+
if (this.opts.inProgressStatus) {
|
|
69943
|
+
await this.moveIssue(issue, this.opts.inProgressStatus);
|
|
69944
|
+
}
|
|
69945
|
+
}
|
|
69946
|
+
async notifyExited(issue, changeName, code) {
|
|
69947
|
+
const updater = this.deps.updater;
|
|
69948
|
+
if (!updater)
|
|
69949
|
+
return;
|
|
69950
|
+
const ok = code === 0;
|
|
69951
|
+
if (this.opts.postComments !== false) {
|
|
69952
|
+
const body = ok ? `\u2705 Ralph completed work on this issue. Change: \`${changeName}\`` : `\u2717 Ralph exited with code ${code} on this issue. Change: \`${changeName}\``;
|
|
69953
|
+
try {
|
|
69954
|
+
await updater.postComment(issue, body);
|
|
69955
|
+
} catch (err) {
|
|
69956
|
+
this.deps.onLog(`! Linear comment failed for ${issue.identifier}: ${err.message}`, "red");
|
|
69957
|
+
}
|
|
69958
|
+
}
|
|
69959
|
+
if (ok && this.opts.doneStatus) {
|
|
69960
|
+
await this.moveIssue(issue, this.opts.doneStatus);
|
|
69961
|
+
}
|
|
69962
|
+
}
|
|
69963
|
+
async moveIssue(issue, stateName) {
|
|
69964
|
+
const updater = this.deps.updater;
|
|
69965
|
+
try {
|
|
69966
|
+
const stateId = await updater.resolveStateId(issue, stateName);
|
|
69967
|
+
if (!stateId) {
|
|
69968
|
+
this.deps.onLog(`! Linear state '${stateName}' not found for ${issue.identifier}`, "yellow");
|
|
69969
|
+
return;
|
|
69970
|
+
}
|
|
69971
|
+
await updater.setState(issue, stateId);
|
|
69972
|
+
this.deps.onLog(` \u2192 ${issue.identifier} moved to '${stateName}'`, "gray");
|
|
69973
|
+
} catch (err) {
|
|
69974
|
+
this.deps.onLog(`! Linear state move failed for ${issue.identifier}: ${err.message}`, "red");
|
|
69975
|
+
}
|
|
69976
|
+
}
|
|
69977
|
+
stop() {
|
|
69978
|
+
this.stopped = true;
|
|
69979
|
+
for (const w of this.workers) {
|
|
69980
|
+
try {
|
|
69981
|
+
w.kill();
|
|
69982
|
+
} catch {}
|
|
69983
|
+
}
|
|
69984
|
+
}
|
|
69985
|
+
}
|
|
69986
|
+
|
|
69987
|
+
// apps/cli/src/components/AgentMode.tsx
|
|
69988
|
+
var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
|
|
69989
|
+
var lineCounter = 0;
|
|
69990
|
+
function nextId() {
|
|
69991
|
+
lineCounter += 1;
|
|
69992
|
+
return `${Date.now()}-${lineCounter}`;
|
|
69993
|
+
}
|
|
69994
|
+
function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
69995
|
+
const { exit } = use_app_default();
|
|
69996
|
+
const [logs, setLogs] = import_react57.useState([]);
|
|
69997
|
+
const [, setTick] = import_react57.useState(0);
|
|
69998
|
+
const coordRef = import_react57.useRef(null);
|
|
69999
|
+
function appendLog(text, color) {
|
|
70000
|
+
setLogs((prev) => [...prev, { id: nextId(), text, color }]);
|
|
70001
|
+
}
|
|
70002
|
+
import_react57.useEffect(() => {
|
|
70003
|
+
let pollTimer = null;
|
|
70004
|
+
let cancelled = false;
|
|
70005
|
+
async function init2() {
|
|
70006
|
+
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
70007
|
+
const cfg = await loadRalphyConfig(projectRoot);
|
|
70008
|
+
appendLog(`agent mode \u2014 config: ${cfgPath}`, "gray");
|
|
70009
|
+
const concurrency = args.concurrency || cfg.concurrency;
|
|
70010
|
+
const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
|
|
70011
|
+
appendLog(`concurrency=${concurrency} pollInterval=${pollInterval}s`, "gray");
|
|
70012
|
+
const apiKey = process.env["LINEAR_API_KEY"];
|
|
70013
|
+
if (!apiKey) {
|
|
70014
|
+
appendLog("! LINEAR_API_KEY not set \u2014 cannot poll Linear", "red");
|
|
70015
|
+
exit();
|
|
70016
|
+
return;
|
|
70017
|
+
}
|
|
70018
|
+
const filter2 = {
|
|
70019
|
+
team: args.linearTeam || cfg.linear.team,
|
|
70020
|
+
assignee: args.linearAssignee || cfg.linear.assignee,
|
|
70021
|
+
statuses: args.linearStatus.length ? args.linearStatus : cfg.linear.statuses,
|
|
70022
|
+
labels: args.linearLabel.length ? args.linearLabel : cfg.linear.labels
|
|
70023
|
+
};
|
|
70024
|
+
const stateCache = new Map;
|
|
70025
|
+
const teamKeyOf = (issue) => issue.identifier.split("-")[0];
|
|
70026
|
+
const coord2 = new AgentCoordinator({
|
|
70027
|
+
fetchIssues: (f2) => fetchOpenIssues(apiKey, f2),
|
|
70028
|
+
scaffold: async (issue) => {
|
|
70029
|
+
let comments = [];
|
|
70030
|
+
try {
|
|
70031
|
+
comments = await fetchIssueComments(apiKey, issue.id);
|
|
70032
|
+
} catch (err) {
|
|
70033
|
+
appendLog(`! Linear comment fetch failed for ${issue.identifier}: ${err.message}`, "yellow");
|
|
70034
|
+
}
|
|
70035
|
+
return scaffoldChangeForIssue(tasksDir, statesDir, issue, comments);
|
|
70036
|
+
},
|
|
70037
|
+
spawnWorker: (changeName) => {
|
|
70038
|
+
const cmd = [
|
|
70039
|
+
process.execPath,
|
|
70040
|
+
process.argv[1] ?? "",
|
|
70041
|
+
"task",
|
|
70042
|
+
"--name",
|
|
70043
|
+
changeName,
|
|
70044
|
+
"--" + (args.engineSet ? args.engine : cfg.engine),
|
|
70045
|
+
args.engineSet ? args.model : cfg.model
|
|
70046
|
+
];
|
|
70047
|
+
const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
|
|
70048
|
+
if (maxIter > 0)
|
|
70049
|
+
cmd.push("--max-iterations", String(maxIter));
|
|
70050
|
+
const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
|
|
70051
|
+
if (maxCost > 0)
|
|
70052
|
+
cmd.push("--max-cost", String(maxCost));
|
|
70053
|
+
const proc = Bun.spawn({
|
|
70054
|
+
cmd,
|
|
70055
|
+
cwd: projectRoot,
|
|
70056
|
+
stdout: "ignore",
|
|
70057
|
+
stderr: "ignore",
|
|
70058
|
+
stdin: "ignore"
|
|
70059
|
+
});
|
|
70060
|
+
return { exited: proc.exited, kill: () => proc.kill() };
|
|
70061
|
+
},
|
|
70062
|
+
loadState: () => readAgentState(projectRoot),
|
|
70063
|
+
saveState: (s) => writeAgentState(projectRoot, s),
|
|
70064
|
+
onLog: appendLog,
|
|
70065
|
+
onWorkersChanged: () => setTick((t) => t + 1),
|
|
70066
|
+
updater: {
|
|
70067
|
+
postComment: (issue, body) => addIssueComment(apiKey, issue.id, body),
|
|
70068
|
+
setState: (issue, stateId) => updateIssueState(apiKey, issue.id, stateId),
|
|
70069
|
+
resolveStateId: async (issue, stateName) => {
|
|
70070
|
+
const team = teamKeyOf(issue);
|
|
70071
|
+
let map2 = stateCache.get(team);
|
|
70072
|
+
if (!map2) {
|
|
70073
|
+
const states = await fetchWorkflowStates(apiKey, team);
|
|
70074
|
+
map2 = new Map(states.map((s) => [s.name.toLowerCase(), s.id]));
|
|
70075
|
+
stateCache.set(team, map2);
|
|
70076
|
+
}
|
|
70077
|
+
return map2.get(stateName.toLowerCase()) ?? null;
|
|
70078
|
+
}
|
|
70079
|
+
}
|
|
70080
|
+
}, {
|
|
70081
|
+
concurrency,
|
|
70082
|
+
filter: filter2,
|
|
70083
|
+
inProgressStatus: cfg.linear.inProgressStatus,
|
|
70084
|
+
doneStatus: cfg.linear.doneStatus,
|
|
70085
|
+
postComments: cfg.linear.postComments
|
|
70086
|
+
});
|
|
70087
|
+
coordRef.current = coord2;
|
|
70088
|
+
await coord2.init();
|
|
70089
|
+
const tick = async () => {
|
|
70090
|
+
if (cancelled)
|
|
70091
|
+
return;
|
|
70092
|
+
const filterDesc = `team=${filter2.team ?? "*"}, assignee=${filter2.assignee ?? "*"}, statuses=${filter2.statuses?.length ? filter2.statuses.join(",") : "open"}${filter2.labels?.length ? `, labels=${filter2.labels.join(",")}` : ""}`;
|
|
70093
|
+
appendLog(`\u2026 polling Linear (${filterDesc})`);
|
|
70094
|
+
const { found, added } = await coord2.pollOnce();
|
|
70095
|
+
appendLog(` found ${found} open, ${added} new (queue=${coord2.queuedCount})`);
|
|
70096
|
+
if (cancelled)
|
|
70097
|
+
return;
|
|
70098
|
+
pollTimer = setTimeout(tick, pollInterval * 1000);
|
|
70099
|
+
};
|
|
70100
|
+
tick();
|
|
70101
|
+
}
|
|
70102
|
+
init2();
|
|
70103
|
+
const onSig = () => {
|
|
70104
|
+
cancelled = true;
|
|
70105
|
+
appendLog("stopping agent \u2014 sending SIGTERM to workers", "yellow");
|
|
70106
|
+
coordRef.current?.stop();
|
|
70107
|
+
if (pollTimer)
|
|
70108
|
+
clearTimeout(pollTimer);
|
|
70109
|
+
exit();
|
|
70110
|
+
};
|
|
70111
|
+
process.on("SIGINT", onSig);
|
|
70112
|
+
process.on("SIGTERM", onSig);
|
|
70113
|
+
return () => {
|
|
70114
|
+
cancelled = true;
|
|
70115
|
+
if (pollTimer)
|
|
70116
|
+
clearTimeout(pollTimer);
|
|
70117
|
+
coordRef.current?.stop();
|
|
70118
|
+
process.off("SIGINT", onSig);
|
|
70119
|
+
process.off("SIGTERM", onSig);
|
|
70120
|
+
};
|
|
70121
|
+
}, []);
|
|
70122
|
+
const coord = coordRef.current;
|
|
70123
|
+
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
70124
|
+
flexDirection: "column",
|
|
70125
|
+
children: [
|
|
70126
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Static, {
|
|
70127
|
+
items: logs,
|
|
70128
|
+
children: (line) => line.color ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70129
|
+
color: line.color,
|
|
70130
|
+
children: line.text
|
|
70131
|
+
}, line.id, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70132
|
+
children: line.text
|
|
70133
|
+
}, line.id, false, undefined, this)
|
|
70134
|
+
}, undefined, false, undefined, this),
|
|
70135
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
|
|
70136
|
+
marginTop: 1,
|
|
70137
|
+
flexDirection: "column",
|
|
70138
|
+
children: [
|
|
70139
|
+
/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70140
|
+
dimColor: true,
|
|
70141
|
+
children: [
|
|
70142
|
+
"workers active: ",
|
|
70143
|
+
coord?.activeCount ?? 0,
|
|
70144
|
+
" \xB7 queued: ",
|
|
70145
|
+
coord?.queuedCount ?? 0
|
|
70146
|
+
]
|
|
70147
|
+
}, undefined, true, undefined, this),
|
|
70148
|
+
coord?.activeWorkers.map((w) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
70149
|
+
color: "cyan",
|
|
70150
|
+
children: [
|
|
70151
|
+
" ",
|
|
70152
|
+
"\u25CF ",
|
|
70153
|
+
w.issueIdentifier,
|
|
70154
|
+
" (",
|
|
70155
|
+
w.changeName,
|
|
70156
|
+
")"
|
|
70157
|
+
]
|
|
70158
|
+
}, w.changeName, true, undefined, this))
|
|
70159
|
+
]
|
|
70160
|
+
}, undefined, true, undefined, this)
|
|
70161
|
+
]
|
|
70162
|
+
}, undefined, true, undefined, this);
|
|
70163
|
+
}
|
|
70164
|
+
|
|
69504
70165
|
// packages/openspec/src/openspec-change-store.ts
|
|
69505
|
-
import { join as
|
|
69506
|
-
import { readdir, mkdir } from "fs/promises";
|
|
70166
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
70167
|
+
import { readdir, mkdir as mkdir2 } from "fs/promises";
|
|
69507
70168
|
function resolveOpenspecBin() {
|
|
69508
70169
|
const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
|
|
69509
|
-
return
|
|
70170
|
+
return join13(dirname3(pkgJsonPath), "bin", "openspec.js");
|
|
69510
70171
|
}
|
|
69511
70172
|
function runOpenspec(args, options = {}) {
|
|
69512
70173
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
@@ -69532,7 +70193,7 @@ class OpenSpecChangeStore {
|
|
|
69532
70193
|
}
|
|
69533
70194
|
}
|
|
69534
70195
|
getChangeDirectory(name) {
|
|
69535
|
-
return
|
|
70196
|
+
return join13("openspec", "changes", name);
|
|
69536
70197
|
}
|
|
69537
70198
|
async listChanges() {
|
|
69538
70199
|
const result2 = runOpenspec(["list", "--json"]);
|
|
@@ -69546,7 +70207,7 @@ class OpenSpecChangeStore {
|
|
|
69546
70207
|
}
|
|
69547
70208
|
} catch {}
|
|
69548
70209
|
}
|
|
69549
|
-
const changesDir =
|
|
70210
|
+
const changesDir = join13("openspec", "changes");
|
|
69550
70211
|
if (!await Bun.file(changesDir).exists())
|
|
69551
70212
|
return [];
|
|
69552
70213
|
try {
|
|
@@ -69557,29 +70218,29 @@ class OpenSpecChangeStore {
|
|
|
69557
70218
|
}
|
|
69558
70219
|
}
|
|
69559
70220
|
async readTaskList(name) {
|
|
69560
|
-
const file = Bun.file(
|
|
70221
|
+
const file = Bun.file(join13("openspec", "changes", name, "tasks.md"));
|
|
69561
70222
|
if (!await file.exists())
|
|
69562
70223
|
return "";
|
|
69563
70224
|
return await file.text();
|
|
69564
70225
|
}
|
|
69565
70226
|
async writeTaskList(name, content) {
|
|
69566
|
-
const path =
|
|
69567
|
-
await
|
|
70227
|
+
const path = join13("openspec", "changes", name, "tasks.md");
|
|
70228
|
+
await mkdir2(dirname3(path), { recursive: true });
|
|
69568
70229
|
await Bun.write(path, content);
|
|
69569
70230
|
}
|
|
69570
70231
|
async appendSteering(name, message) {
|
|
69571
|
-
const path =
|
|
70232
|
+
const path = join13("openspec", "changes", name, "steering.md");
|
|
69572
70233
|
const file = Bun.file(path);
|
|
69573
70234
|
const existing = await file.exists() ? await file.text() : null;
|
|
69574
70235
|
const updated = existing ? `${message}
|
|
69575
70236
|
|
|
69576
70237
|
${existing.trimStart()}` : `${message}
|
|
69577
70238
|
`;
|
|
69578
|
-
await
|
|
70239
|
+
await mkdir2(dirname3(path), { recursive: true });
|
|
69579
70240
|
await Bun.write(path, updated);
|
|
69580
70241
|
}
|
|
69581
70242
|
async readSection(name, artifact, heading) {
|
|
69582
|
-
const file = Bun.file(
|
|
70243
|
+
const file = Bun.file(join13("openspec", "changes", name, artifact));
|
|
69583
70244
|
if (!await file.exists())
|
|
69584
70245
|
return "";
|
|
69585
70246
|
const content = await file.text();
|
|
@@ -69620,67 +70281,74 @@ ${existing.trimStart()}` : `${message}
|
|
|
69620
70281
|
}
|
|
69621
70282
|
}
|
|
69622
70283
|
// apps/cli/src/components/App.tsx
|
|
69623
|
-
var
|
|
70284
|
+
var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
|
|
69624
70285
|
function ExitAfterRender({ children }) {
|
|
69625
70286
|
const { exit } = use_app_default();
|
|
69626
|
-
|
|
70287
|
+
import_react58.useEffect(() => {
|
|
69627
70288
|
exit();
|
|
69628
70289
|
}, [exit]);
|
|
69629
|
-
return /* @__PURE__ */
|
|
70290
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
|
|
69630
70291
|
children
|
|
69631
70292
|
}, undefined, false, undefined, this);
|
|
69632
70293
|
}
|
|
69633
70294
|
function ErrorMessage({ message }) {
|
|
69634
70295
|
const { exit } = use_app_default();
|
|
69635
|
-
|
|
70296
|
+
import_react58.useEffect(() => {
|
|
69636
70297
|
process.exitCode = 1;
|
|
69637
70298
|
exit();
|
|
69638
70299
|
}, [exit]);
|
|
69639
|
-
return /* @__PURE__ */
|
|
70300
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
69640
70301
|
color: "red",
|
|
69641
70302
|
children: message
|
|
69642
70303
|
}, undefined, false, undefined, this);
|
|
69643
70304
|
}
|
|
69644
|
-
function App2({ args, statesDir, tasksDir }) {
|
|
70305
|
+
function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
69645
70306
|
switch (args.mode) {
|
|
69646
70307
|
case "list":
|
|
69647
|
-
return /* @__PURE__ */
|
|
70308
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskList, {
|
|
69648
70309
|
statesDir
|
|
69649
70310
|
}, undefined, false, undefined, this);
|
|
70311
|
+
case "agent":
|
|
70312
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AgentMode, {
|
|
70313
|
+
args,
|
|
70314
|
+
projectRoot,
|
|
70315
|
+
statesDir,
|
|
70316
|
+
tasksDir
|
|
70317
|
+
}, undefined, false, undefined, this);
|
|
69650
70318
|
case "status": {
|
|
69651
70319
|
if (!args.name) {
|
|
69652
|
-
return /* @__PURE__ */
|
|
70320
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
69653
70321
|
message: "Error: --name is required for status mode"
|
|
69654
70322
|
}, undefined, false, undefined, this);
|
|
69655
70323
|
}
|
|
69656
|
-
const stateDir =
|
|
69657
|
-
if (getStorage().read(
|
|
69658
|
-
return /* @__PURE__ */
|
|
70324
|
+
const stateDir = join14(statesDir, args.name);
|
|
70325
|
+
if (getStorage().read(join14(stateDir, ".ralph-state.json")) === null) {
|
|
70326
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
69659
70327
|
message: `Error: change '${args.name}' not found`
|
|
69660
70328
|
}, undefined, false, undefined, this);
|
|
69661
70329
|
}
|
|
69662
70330
|
const state = readState(stateDir);
|
|
69663
|
-
return /* @__PURE__ */
|
|
69664
|
-
children: /* @__PURE__ */
|
|
70331
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
|
|
70332
|
+
children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskStatus, {
|
|
69665
70333
|
state,
|
|
69666
70334
|
stateDir
|
|
69667
70335
|
}, undefined, false, undefined, this)
|
|
69668
70336
|
}, undefined, false, undefined, this);
|
|
69669
70337
|
}
|
|
69670
70338
|
case "init":
|
|
69671
|
-
return /* @__PURE__ */
|
|
69672
|
-
children: /* @__PURE__ */
|
|
70339
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
|
|
70340
|
+
children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
|
|
69673
70341
|
color: "green",
|
|
69674
70342
|
children: "Initialized openspec directory"
|
|
69675
70343
|
}, undefined, false, undefined, this)
|
|
69676
70344
|
}, undefined, false, undefined, this);
|
|
69677
70345
|
case "task": {
|
|
69678
70346
|
if (!args.name) {
|
|
69679
|
-
return /* @__PURE__ */
|
|
70347
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
69680
70348
|
message: "Error: --name is required for task mode"
|
|
69681
70349
|
}, undefined, false, undefined, this);
|
|
69682
70350
|
}
|
|
69683
|
-
return /* @__PURE__ */
|
|
70351
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskLoop, {
|
|
69684
70352
|
opts: {
|
|
69685
70353
|
name: args.name,
|
|
69686
70354
|
prompt: args.prompt,
|
|
@@ -69711,7 +70379,7 @@ if (typeof globalThis.Bun === "undefined") {
|
|
|
69711
70379
|
async function findProjectRoot() {
|
|
69712
70380
|
let dir = process.cwd();
|
|
69713
70381
|
while (dir !== "/") {
|
|
69714
|
-
if (await exists(
|
|
70382
|
+
if (await exists(join15(dir, "openspec")))
|
|
69715
70383
|
return dir;
|
|
69716
70384
|
dir = resolve(dir, "..");
|
|
69717
70385
|
}
|
|
@@ -69746,11 +70414,11 @@ try {
|
|
|
69746
70414
|
capture("command_run", { mode: args.mode, engine: args.engine, model: args.model });
|
|
69747
70415
|
try {
|
|
69748
70416
|
const projectRoot = await findProjectRoot();
|
|
69749
|
-
const statesDir =
|
|
69750
|
-
const tasksDir =
|
|
70417
|
+
const statesDir = join15(projectRoot, ".ralph", "tasks");
|
|
70418
|
+
const tasksDir = join15(projectRoot, "openspec", "changes");
|
|
69751
70419
|
if (args.mode === "init") {
|
|
69752
|
-
await
|
|
69753
|
-
const openspecBin =
|
|
70420
|
+
await mkdir3(statesDir, { recursive: true });
|
|
70421
|
+
const openspecBin = join15(dirname4(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
|
|
69754
70422
|
Bun.spawnSync({
|
|
69755
70423
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
69756
70424
|
stdio: ["inherit", "inherit", "inherit"],
|
|
@@ -69758,11 +70426,16 @@ try {
|
|
|
69758
70426
|
});
|
|
69759
70427
|
}
|
|
69760
70428
|
if (args.mode === "task" && args.name) {
|
|
69761
|
-
await
|
|
69762
|
-
await
|
|
70429
|
+
await mkdir3(join15(statesDir, args.name), { recursive: true });
|
|
70430
|
+
await mkdir3(join15(tasksDir, args.name), { recursive: true });
|
|
70431
|
+
}
|
|
70432
|
+
if (args.mode === "agent") {
|
|
70433
|
+
await mkdir3(statesDir, { recursive: true });
|
|
70434
|
+
await mkdir3(tasksDir, { recursive: true });
|
|
70435
|
+
await mkdir3(join15(projectRoot, ".ralph"), { recursive: true });
|
|
69763
70436
|
}
|
|
69764
70437
|
await runWithContext(createDefaultContext(), async () => {
|
|
69765
|
-
const { waitUntilExit } = render_default(
|
|
70438
|
+
const { waitUntilExit } = render_default(import_react59.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
|
|
69766
70439
|
await waitUntilExit();
|
|
69767
70440
|
});
|
|
69768
70441
|
await shutdown();
|