@neriros/ralphy 2.6.0 → 2.7.0

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +578 -43
  2. 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 join12, dirname as dirname4 } from "path";
50552
- import { exists, mkdir as mkdir2 } from "fs/promises";
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 import_react58 = __toESM(require_react(), 1);
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",
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 = 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 import_react57 = __toESM(require_react(), 1);
56378
- import { join as join11 } from "path";
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,465 @@ 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.label)
69599
+ where.labels = { some: { name: { eq: filter2.label } } };
69600
+ const query = `query Issues($filter: IssueFilter) {
69601
+ issues(filter: $filter, first: 50) {
69602
+ nodes {
69603
+ id identifier title description url
69604
+ state { name type }
69605
+ assignee { id email name }
69606
+ labels { nodes { name } }
69607
+ }
69608
+ }
69609
+ }`;
69610
+ const res = await fetch(LINEAR_API, {
69611
+ method: "POST",
69612
+ headers: {
69613
+ "Content-Type": "application/json",
69614
+ Authorization: apiKey
69615
+ },
69616
+ body: JSON.stringify({ query, variables: { filter: where } })
69617
+ });
69618
+ if (!res.ok) {
69619
+ const err = new Error("Linear API request failed");
69620
+ err.status = res.status;
69621
+ err.body = await res.text();
69622
+ throw err;
69623
+ }
69624
+ const json = await res.json();
69625
+ if (json.errors?.length) {
69626
+ const err = new Error("Linear API returned errors");
69627
+ err.messages = json.errors.map((e) => e.message);
69628
+ throw err;
69629
+ }
69630
+ if (!json.data)
69631
+ return [];
69632
+ return json.data.issues.nodes.map((n) => ({
69633
+ id: n.id,
69634
+ identifier: n.identifier,
69635
+ title: n.title,
69636
+ description: n.description,
69637
+ url: n.url,
69638
+ state: n.state,
69639
+ assignee: n.assignee,
69640
+ labels: n.labels.nodes.map((l) => l.name)
69641
+ }));
69642
+ }
69643
+
69644
+ // apps/cli/src/agent/state.ts
69645
+ import { join as join10 } from "path";
69646
+ var AgentStateSchema = exports_external.object({
69647
+ processedIssueIds: exports_external.array(exports_external.string()).default([]),
69648
+ lastPollAt: exports_external.string().nullable().default(null)
69649
+ });
69650
+ function statePath(projectRoot) {
69651
+ return join10(projectRoot, ".ralph", "agent-state.json");
69652
+ }
69653
+ async function readAgentState(projectRoot) {
69654
+ const file = Bun.file(statePath(projectRoot));
69655
+ if (!await file.exists()) {
69656
+ return AgentStateSchema.parse({});
69657
+ }
69658
+ return AgentStateSchema.parse(await file.json());
69659
+ }
69660
+ async function writeAgentState(projectRoot, state) {
69661
+ await Bun.write(statePath(projectRoot), JSON.stringify(state, null, 2) + `
69662
+ `);
69663
+ }
69664
+
69665
+ // apps/cli/src/agent/scaffold.ts
69666
+ import { join as join11 } from "path";
69667
+ import { mkdir } from "fs/promises";
69668
+ function changeNameForIssue(issue) {
69669
+ const slug = issue.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
69670
+ return slug ? `${issue.identifier.toLowerCase()}-${slug}` : issue.identifier.toLowerCase();
69671
+ }
69672
+ async function scaffoldChangeForIssue(tasksDir, statesDir, issue) {
69673
+ const name = changeNameForIssue(issue);
69674
+ const changeDir = join11(tasksDir, name);
69675
+ const stateDir = join11(statesDir, name);
69676
+ await mkdir(changeDir, { recursive: true });
69677
+ await mkdir(join11(changeDir, "specs"), { recursive: true });
69678
+ await mkdir(stateDir, { recursive: true });
69679
+ const proposal = [
69680
+ `# ${issue.identifier}: ${issue.title}`,
69681
+ "",
69682
+ `Source: [${issue.identifier}](${issue.url})`,
69683
+ `Status: ${issue.state.name}`,
69684
+ issue.assignee ? `Assignee: ${issue.assignee.name}` : "",
69685
+ issue.labels.length ? `Labels: ${issue.labels.join(", ")}` : "",
69686
+ "",
69687
+ "## Description",
69688
+ "",
69689
+ issue.description?.trim() || "_No description provided in Linear._",
69690
+ "",
69691
+ "## Steering",
69692
+ "",
69693
+ "_Add steering notes here as the loop runs._",
69694
+ ""
69695
+ ].filter((l) => l !== "").join(`
69696
+ `);
69697
+ const tasks = [
69698
+ `# Tasks for ${issue.identifier}`,
69699
+ "",
69700
+ `- [ ] Read the Linear issue at ${issue.url} and break it into concrete subtasks`,
69701
+ `- [ ] Implement the changes described in proposal.md`,
69702
+ `- [ ] Add or update tests covering the new behavior`,
69703
+ `- [ ] Run \`bun run lint\` and \`bun run test\` and fix any failures`,
69704
+ ""
69705
+ ].join(`
69706
+ `);
69707
+ const design = [
69708
+ `# Design for ${issue.identifier}`,
69709
+ "",
69710
+ "_Fill in the technical design as you work through the issue._",
69711
+ ""
69712
+ ].join(`
69713
+ `);
69714
+ await Bun.write(join11(changeDir, "proposal.md"), proposal);
69715
+ await Bun.write(join11(changeDir, "tasks.md"), tasks);
69716
+ await Bun.write(join11(changeDir, "design.md"), design);
69717
+ return name;
69718
+ }
69719
+
69720
+ // apps/cli/src/agent/config.ts
69721
+ import { join as join12 } from "path";
69722
+ var RalphyConfigSchema = exports_external.object({
69723
+ concurrency: exports_external.number().int().positive().default(1),
69724
+ pollIntervalSeconds: exports_external.number().int().positive().default(60),
69725
+ maxIterationsPerTask: exports_external.number().int().nonnegative().default(0),
69726
+ maxCostUsdPerTask: exports_external.number().nonnegative().default(0),
69727
+ engine: exports_external.enum(["claude", "codex"]).default("claude"),
69728
+ model: exports_external.enum(["haiku", "sonnet", "opus"]).default("opus"),
69729
+ linear: exports_external.object({
69730
+ team: exports_external.string().optional(),
69731
+ assignee: exports_external.string().optional(),
69732
+ statuses: exports_external.array(exports_external.string()).default([]),
69733
+ label: exports_external.string().optional()
69734
+ }).default({ statuses: [] })
69735
+ }).default({
69736
+ concurrency: 1,
69737
+ pollIntervalSeconds: 60,
69738
+ maxIterationsPerTask: 0,
69739
+ maxCostUsdPerTask: 0,
69740
+ engine: "claude",
69741
+ model: "opus",
69742
+ linear: { statuses: [] }
69743
+ });
69744
+ async function loadRalphyConfig(projectRoot) {
69745
+ const path = join12(projectRoot, "ralphy.config.json");
69746
+ const file = Bun.file(path);
69747
+ if (!await file.exists()) {
69748
+ return RalphyConfigSchema.parse({});
69749
+ }
69750
+ const raw = await file.json();
69751
+ return RalphyConfigSchema.parse(raw);
69752
+ }
69753
+ async function ensureRalphyConfig(projectRoot) {
69754
+ const path = join12(projectRoot, "ralphy.config.json");
69755
+ const file = Bun.file(path);
69756
+ if (await file.exists())
69757
+ return path;
69758
+ const defaults2 = RalphyConfigSchema.parse({});
69759
+ await Bun.write(path, JSON.stringify(defaults2, null, 2) + `
69760
+ `);
69761
+ return path;
69762
+ }
69763
+
69764
+ // apps/cli/src/agent/coordinator.ts
69765
+ class AgentCoordinator {
69766
+ deps;
69767
+ opts;
69768
+ workers = [];
69769
+ pendingIds = new Set;
69770
+ queue = [];
69771
+ state = null;
69772
+ stopped = false;
69773
+ constructor(deps, opts) {
69774
+ this.deps = deps;
69775
+ this.opts = opts;
69776
+ }
69777
+ get activeCount() {
69778
+ return this.workers.length;
69779
+ }
69780
+ get queuedCount() {
69781
+ return this.queue.length;
69782
+ }
69783
+ get activeWorkers() {
69784
+ return this.workers;
69785
+ }
69786
+ async init() {
69787
+ this.state = await this.deps.loadState();
69788
+ }
69789
+ async pollOnce() {
69790
+ if (this.stopped)
69791
+ return { found: 0, added: 0 };
69792
+ let issues;
69793
+ try {
69794
+ issues = await this.deps.fetchIssues(this.opts.filter);
69795
+ } catch (err) {
69796
+ this.deps.onLog(`! Linear poll failed: ${err.message}`, "red");
69797
+ return { found: 0, added: 0 };
69798
+ }
69799
+ const state = this.state;
69800
+ const seen = new Set(state.processedIssueIds);
69801
+ const queued = new Set(this.queue.map((i) => i.id));
69802
+ const active = new Set(this.workers.map((w) => w.issueId));
69803
+ let added = 0;
69804
+ for (const issue of issues) {
69805
+ if (seen.has(issue.id))
69806
+ continue;
69807
+ if (queued.has(issue.id))
69808
+ continue;
69809
+ if (active.has(issue.id))
69810
+ continue;
69811
+ if (this.pendingIds.has(issue.id))
69812
+ continue;
69813
+ this.queue.push(issue);
69814
+ added += 1;
69815
+ }
69816
+ state.lastPollAt = new Date().toISOString();
69817
+ await this.deps.saveState(state);
69818
+ this.spawnNext();
69819
+ return { found: issues.length, added };
69820
+ }
69821
+ spawnNext() {
69822
+ if (this.stopped || !this.state)
69823
+ return;
69824
+ while (this.workers.length + this.pendingIds.size < this.opts.concurrency && this.queue.length > 0) {
69825
+ const issue = this.queue.shift();
69826
+ this.pendingIds.add(issue.id);
69827
+ this.launchWorker(issue);
69828
+ }
69829
+ }
69830
+ async launchWorker(issue) {
69831
+ let changeName;
69832
+ try {
69833
+ changeName = await this.deps.scaffold(issue);
69834
+ } catch (err) {
69835
+ this.pendingIds.delete(issue.id);
69836
+ this.deps.onLog(`! scaffold failed for ${issue.identifier}: ${err.message}`, "red");
69837
+ this.spawnNext();
69838
+ return;
69839
+ }
69840
+ if (this.stopped) {
69841
+ this.pendingIds.delete(issue.id);
69842
+ return;
69843
+ }
69844
+ this.deps.onLog(`\u25B6 ${issue.identifier} \u2192 ${changeName} (worker started)`, "cyan");
69845
+ const handle = this.deps.spawnWorker(changeName, issue);
69846
+ const worker = {
69847
+ changeName,
69848
+ issueId: issue.id,
69849
+ issueIdentifier: issue.identifier,
69850
+ kill: handle.kill
69851
+ };
69852
+ this.workers.push(worker);
69853
+ this.pendingIds.delete(issue.id);
69854
+ this.deps.onWorkersChanged();
69855
+ handle.exited.then((code) => {
69856
+ const idx = this.workers.indexOf(worker);
69857
+ if (idx >= 0)
69858
+ this.workers.splice(idx, 1);
69859
+ const ok = code === 0;
69860
+ this.deps.onLog(`${ok ? "\u2713" : "\u2717"} ${issue.identifier} \u2192 ${changeName} exited (code ${code})`, ok ? "green" : "red");
69861
+ if (ok && this.state && !this.state.processedIssueIds.includes(issue.id)) {
69862
+ this.state.processedIssueIds.push(issue.id);
69863
+ this.deps.saveState(this.state);
69864
+ }
69865
+ this.deps.onWorkersChanged();
69866
+ this.spawnNext();
69867
+ });
69868
+ }
69869
+ stop() {
69870
+ this.stopped = true;
69871
+ for (const w of this.workers) {
69872
+ try {
69873
+ w.kill();
69874
+ } catch {}
69875
+ }
69876
+ }
69877
+ }
69878
+
69879
+ // apps/cli/src/components/AgentMode.tsx
69880
+ var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
69881
+ var lineCounter = 0;
69882
+ function nextId() {
69883
+ lineCounter += 1;
69884
+ return `${Date.now()}-${lineCounter}`;
69885
+ }
69886
+ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
69887
+ const { exit } = use_app_default();
69888
+ const [logs, setLogs] = import_react57.useState([]);
69889
+ const [, setTick] = import_react57.useState(0);
69890
+ const coordRef = import_react57.useRef(null);
69891
+ function appendLog(text, color) {
69892
+ setLogs((prev) => [...prev, { id: nextId(), text, color }]);
69893
+ }
69894
+ import_react57.useEffect(() => {
69895
+ let pollTimer = null;
69896
+ let cancelled = false;
69897
+ async function init2() {
69898
+ const cfgPath = await ensureRalphyConfig(projectRoot);
69899
+ const cfg = await loadRalphyConfig(projectRoot);
69900
+ appendLog(`agent mode \u2014 config: ${cfgPath}`, "gray");
69901
+ const concurrency = args.concurrency || cfg.concurrency;
69902
+ const pollInterval = args.pollInterval || cfg.pollIntervalSeconds;
69903
+ appendLog(`concurrency=${concurrency} pollInterval=${pollInterval}s`, "gray");
69904
+ const apiKey = process.env["LINEAR_API_KEY"];
69905
+ if (!apiKey) {
69906
+ appendLog("! LINEAR_API_KEY not set \u2014 cannot poll Linear", "red");
69907
+ exit();
69908
+ return;
69909
+ }
69910
+ const filter2 = {
69911
+ team: args.linearTeam || cfg.linear.team,
69912
+ assignee: args.linearAssignee || cfg.linear.assignee,
69913
+ statuses: args.linearStatus.length ? args.linearStatus : cfg.linear.statuses,
69914
+ label: args.linearLabel || cfg.linear.label
69915
+ };
69916
+ const coord2 = new AgentCoordinator({
69917
+ fetchIssues: (f2) => fetchOpenIssues(apiKey, f2),
69918
+ scaffold: (issue) => scaffoldChangeForIssue(tasksDir, statesDir, issue),
69919
+ spawnWorker: (changeName) => {
69920
+ const cmd = [
69921
+ process.execPath,
69922
+ process.argv[1] ?? "",
69923
+ "task",
69924
+ "--name",
69925
+ changeName,
69926
+ "--" + (args.engineSet ? args.engine : cfg.engine),
69927
+ args.engineSet ? args.model : cfg.model
69928
+ ];
69929
+ const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
69930
+ if (maxIter > 0)
69931
+ cmd.push("--max-iterations", String(maxIter));
69932
+ const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
69933
+ if (maxCost > 0)
69934
+ cmd.push("--max-cost", String(maxCost));
69935
+ const proc = Bun.spawn({
69936
+ cmd,
69937
+ cwd: projectRoot,
69938
+ stdout: "ignore",
69939
+ stderr: "ignore",
69940
+ stdin: "ignore"
69941
+ });
69942
+ return { exited: proc.exited, kill: () => proc.kill() };
69943
+ },
69944
+ loadState: () => readAgentState(projectRoot),
69945
+ saveState: (s) => writeAgentState(projectRoot, s),
69946
+ onLog: appendLog,
69947
+ onWorkersChanged: () => setTick((t) => t + 1)
69948
+ }, { concurrency, filter: filter2 });
69949
+ coordRef.current = coord2;
69950
+ await coord2.init();
69951
+ const tick = async () => {
69952
+ if (cancelled)
69953
+ return;
69954
+ const filterDesc = `team=${filter2.team ?? "*"}, assignee=${filter2.assignee ?? "*"}, statuses=${filter2.statuses?.length ? filter2.statuses.join(",") : "open"}${filter2.label ? `, label=${filter2.label}` : ""}`;
69955
+ appendLog(`\u2026 polling Linear (${filterDesc})`);
69956
+ const { found, added } = await coord2.pollOnce();
69957
+ appendLog(` found ${found} open, ${added} new (queue=${coord2.queuedCount})`);
69958
+ if (cancelled)
69959
+ return;
69960
+ pollTimer = setTimeout(tick, pollInterval * 1000);
69961
+ };
69962
+ tick();
69963
+ }
69964
+ init2();
69965
+ const onSig = () => {
69966
+ cancelled = true;
69967
+ appendLog("stopping agent \u2014 sending SIGTERM to workers", "yellow");
69968
+ coordRef.current?.stop();
69969
+ if (pollTimer)
69970
+ clearTimeout(pollTimer);
69971
+ exit();
69972
+ };
69973
+ process.on("SIGINT", onSig);
69974
+ process.on("SIGTERM", onSig);
69975
+ return () => {
69976
+ cancelled = true;
69977
+ if (pollTimer)
69978
+ clearTimeout(pollTimer);
69979
+ coordRef.current?.stop();
69980
+ process.off("SIGINT", onSig);
69981
+ process.off("SIGTERM", onSig);
69982
+ };
69983
+ }, []);
69984
+ const coord = coordRef.current;
69985
+ return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
69986
+ flexDirection: "column",
69987
+ children: [
69988
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Static, {
69989
+ items: logs,
69990
+ children: (line) => line.color ? /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
69991
+ color: line.color,
69992
+ children: line.text
69993
+ }, line.id, false, undefined, this) : /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
69994
+ children: line.text
69995
+ }, line.id, false, undefined, this)
69996
+ }, undefined, false, undefined, this),
69997
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Box_default, {
69998
+ marginTop: 1,
69999
+ flexDirection: "column",
70000
+ children: [
70001
+ /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70002
+ dimColor: true,
70003
+ children: [
70004
+ "workers active: ",
70005
+ coord?.activeCount ?? 0,
70006
+ " \xB7 queued: ",
70007
+ coord?.queuedCount ?? 0
70008
+ ]
70009
+ }, undefined, true, undefined, this),
70010
+ coord?.activeWorkers.map((w) => /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70011
+ color: "cyan",
70012
+ children: [
70013
+ " ",
70014
+ "\u25CF ",
70015
+ w.issueIdentifier,
70016
+ " (",
70017
+ w.changeName,
70018
+ ")"
70019
+ ]
70020
+ }, w.changeName, true, undefined, this))
70021
+ ]
70022
+ }, undefined, true, undefined, this)
70023
+ ]
70024
+ }, undefined, true, undefined, this);
70025
+ }
70026
+
69504
70027
  // packages/openspec/src/openspec-change-store.ts
69505
- import { join as join10, dirname as dirname3 } from "path";
69506
- import { readdir, mkdir } from "fs/promises";
70028
+ import { join as join13, dirname as dirname3 } from "path";
70029
+ import { readdir, mkdir as mkdir2 } from "fs/promises";
69507
70030
  function resolveOpenspecBin() {
69508
70031
  const pkgJsonPath = Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir);
69509
- return join10(dirname3(pkgJsonPath), "bin", "openspec.js");
70032
+ return join13(dirname3(pkgJsonPath), "bin", "openspec.js");
69510
70033
  }
69511
70034
  function runOpenspec(args, options = {}) {
69512
70035
  const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
@@ -69532,7 +70055,7 @@ class OpenSpecChangeStore {
69532
70055
  }
69533
70056
  }
69534
70057
  getChangeDirectory(name) {
69535
- return join10("openspec", "changes", name);
70058
+ return join13("openspec", "changes", name);
69536
70059
  }
69537
70060
  async listChanges() {
69538
70061
  const result2 = runOpenspec(["list", "--json"]);
@@ -69546,7 +70069,7 @@ class OpenSpecChangeStore {
69546
70069
  }
69547
70070
  } catch {}
69548
70071
  }
69549
- const changesDir = join10("openspec", "changes");
70072
+ const changesDir = join13("openspec", "changes");
69550
70073
  if (!await Bun.file(changesDir).exists())
69551
70074
  return [];
69552
70075
  try {
@@ -69557,29 +70080,29 @@ class OpenSpecChangeStore {
69557
70080
  }
69558
70081
  }
69559
70082
  async readTaskList(name) {
69560
- const file = Bun.file(join10("openspec", "changes", name, "tasks.md"));
70083
+ const file = Bun.file(join13("openspec", "changes", name, "tasks.md"));
69561
70084
  if (!await file.exists())
69562
70085
  return "";
69563
70086
  return await file.text();
69564
70087
  }
69565
70088
  async writeTaskList(name, content) {
69566
- const path = join10("openspec", "changes", name, "tasks.md");
69567
- await mkdir(dirname3(path), { recursive: true });
70089
+ const path = join13("openspec", "changes", name, "tasks.md");
70090
+ await mkdir2(dirname3(path), { recursive: true });
69568
70091
  await Bun.write(path, content);
69569
70092
  }
69570
70093
  async appendSteering(name, message) {
69571
- const path = join10("openspec", "changes", name, "steering.md");
70094
+ const path = join13("openspec", "changes", name, "steering.md");
69572
70095
  const file = Bun.file(path);
69573
70096
  const existing = await file.exists() ? await file.text() : null;
69574
70097
  const updated = existing ? `${message}
69575
70098
 
69576
70099
  ${existing.trimStart()}` : `${message}
69577
70100
  `;
69578
- await mkdir(dirname3(path), { recursive: true });
70101
+ await mkdir2(dirname3(path), { recursive: true });
69579
70102
  await Bun.write(path, updated);
69580
70103
  }
69581
70104
  async readSection(name, artifact, heading) {
69582
- const file = Bun.file(join10("openspec", "changes", name, artifact));
70105
+ const file = Bun.file(join13("openspec", "changes", name, artifact));
69583
70106
  if (!await file.exists())
69584
70107
  return "";
69585
70108
  const content = await file.text();
@@ -69620,67 +70143,74 @@ ${existing.trimStart()}` : `${message}
69620
70143
  }
69621
70144
  }
69622
70145
  // apps/cli/src/components/App.tsx
69623
- var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
70146
+ var jsx_dev_runtime10 = __toESM(require_jsx_dev_runtime(), 1);
69624
70147
  function ExitAfterRender({ children }) {
69625
70148
  const { exit } = use_app_default();
69626
- import_react57.useEffect(() => {
70149
+ import_react58.useEffect(() => {
69627
70150
  exit();
69628
70151
  }, [exit]);
69629
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(jsx_dev_runtime9.Fragment, {
70152
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(jsx_dev_runtime10.Fragment, {
69630
70153
  children
69631
70154
  }, undefined, false, undefined, this);
69632
70155
  }
69633
70156
  function ErrorMessage({ message }) {
69634
70157
  const { exit } = use_app_default();
69635
- import_react57.useEffect(() => {
70158
+ import_react58.useEffect(() => {
69636
70159
  process.exitCode = 1;
69637
70160
  exit();
69638
70161
  }, [exit]);
69639
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70162
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69640
70163
  color: "red",
69641
70164
  children: message
69642
70165
  }, undefined, false, undefined, this);
69643
70166
  }
69644
- function App2({ args, statesDir, tasksDir }) {
70167
+ function App2({ args, statesDir, tasksDir, projectRoot }) {
69645
70168
  switch (args.mode) {
69646
70169
  case "list":
69647
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(TaskList, {
70170
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskList, {
69648
70171
  statesDir
69649
70172
  }, undefined, false, undefined, this);
70173
+ case "agent":
70174
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(AgentMode, {
70175
+ args,
70176
+ projectRoot,
70177
+ statesDir,
70178
+ tasksDir
70179
+ }, undefined, false, undefined, this);
69650
70180
  case "status": {
69651
70181
  if (!args.name) {
69652
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
70182
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
69653
70183
  message: "Error: --name is required for status mode"
69654
70184
  }, undefined, false, undefined, this);
69655
70185
  }
69656
- const stateDir = join11(statesDir, args.name);
69657
- if (getStorage().read(join11(stateDir, ".ralph-state.json")) === null) {
69658
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
70186
+ const stateDir = join14(statesDir, args.name);
70187
+ if (getStorage().read(join14(stateDir, ".ralph-state.json")) === null) {
70188
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
69659
70189
  message: `Error: change '${args.name}' not found`
69660
70190
  }, undefined, false, undefined, this);
69661
70191
  }
69662
70192
  const state = readState(stateDir);
69663
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ExitAfterRender, {
69664
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(TaskStatus, {
70193
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
70194
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskStatus, {
69665
70195
  state,
69666
70196
  stateDir
69667
70197
  }, undefined, false, undefined, this)
69668
70198
  }, undefined, false, undefined, this);
69669
70199
  }
69670
70200
  case "init":
69671
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ExitAfterRender, {
69672
- children: /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
70201
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ExitAfterRender, {
70202
+ children: /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {
69673
70203
  color: "green",
69674
70204
  children: "Initialized openspec directory"
69675
70205
  }, undefined, false, undefined, this)
69676
70206
  }, undefined, false, undefined, this);
69677
70207
  case "task": {
69678
70208
  if (!args.name) {
69679
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
70209
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
69680
70210
  message: "Error: --name is required for task mode"
69681
70211
  }, undefined, false, undefined, this);
69682
70212
  }
69683
- return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(TaskLoop, {
70213
+ return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskLoop, {
69684
70214
  opts: {
69685
70215
  name: args.name,
69686
70216
  prompt: args.prompt,
@@ -69711,7 +70241,7 @@ if (typeof globalThis.Bun === "undefined") {
69711
70241
  async function findProjectRoot() {
69712
70242
  let dir = process.cwd();
69713
70243
  while (dir !== "/") {
69714
- if (await exists(join12(dir, "openspec")))
70244
+ if (await exists(join15(dir, "openspec")))
69715
70245
  return dir;
69716
70246
  dir = resolve(dir, "..");
69717
70247
  }
@@ -69746,11 +70276,11 @@ try {
69746
70276
  capture("command_run", { mode: args.mode, engine: args.engine, model: args.model });
69747
70277
  try {
69748
70278
  const projectRoot = await findProjectRoot();
69749
- const statesDir = join12(projectRoot, ".ralph", "tasks");
69750
- const tasksDir = join12(projectRoot, "openspec", "changes");
70279
+ const statesDir = join15(projectRoot, ".ralph", "tasks");
70280
+ const tasksDir = join15(projectRoot, "openspec", "changes");
69751
70281
  if (args.mode === "init") {
69752
- await mkdir2(statesDir, { recursive: true });
69753
- const openspecBin = join12(dirname4(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
70282
+ await mkdir3(statesDir, { recursive: true });
70283
+ const openspecBin = join15(dirname4(Bun.resolveSync("@fission-ai/openspec/package.json", import.meta.dir)), "bin", "openspec.js");
69754
70284
  Bun.spawnSync({
69755
70285
  cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
69756
70286
  stdio: ["inherit", "inherit", "inherit"],
@@ -69758,11 +70288,16 @@ try {
69758
70288
  });
69759
70289
  }
69760
70290
  if (args.mode === "task" && args.name) {
69761
- await mkdir2(join12(statesDir, args.name), { recursive: true });
69762
- await mkdir2(join12(tasksDir, args.name), { recursive: true });
70291
+ await mkdir3(join15(statesDir, args.name), { recursive: true });
70292
+ await mkdir3(join15(tasksDir, args.name), { recursive: true });
70293
+ }
70294
+ if (args.mode === "agent") {
70295
+ await mkdir3(statesDir, { recursive: true });
70296
+ await mkdir3(tasksDir, { recursive: true });
70297
+ await mkdir3(join15(projectRoot, ".ralph"), { recursive: true });
69763
70298
  }
69764
70299
  await runWithContext(createDefaultContext(), async () => {
69765
- const { waitUntilExit } = render_default(import_react58.createElement(App2, { args, statesDir, tasksDir }));
70300
+ const { waitUntilExit } = render_default(import_react59.createElement(App2, { args, statesDir, tasksDir, projectRoot }));
69766
70301
  await waitUntilExit();
69767
70302
  });
69768
70303
  await shutdown();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
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",