@neriros/ralphy 2.7.1 → 2.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,6 +68,50 @@ ralph list # Table of all tasks
68
68
  ralph status --name fix-auth # Detailed view of one task
69
69
  ```
70
70
 
71
+ ### Agent Mode (Linear integration)
72
+
73
+ `ralph agent` polls Linear for open issues and runs up to N concurrent task loops, scaffolding an OpenSpec change per new issue. Requires `LINEAR_API_KEY` in the environment.
74
+
75
+ ```bash
76
+ export LINEAR_API_KEY=lin_api_xxx
77
+ ralph agent --linear-team ENG --linear-assignee me --concurrency 3 --poll-interval 60
78
+ ```
79
+
80
+ What it does on each tick:
81
+
82
+ 1. Polls Linear for open issues matching the filter (team / assignee / status / labels)
83
+ 2. Dedupes against `.ralph/agent-state.json` (already processed) plus any in-flight workers
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
+ 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` and/or applies `doneLabel`
87
+
88
+ Defaults are written to `ralphy.config.json` on first run; CLI flags override config values per invocation.
89
+
90
+ ```jsonc
91
+ {
92
+ "concurrency": 3,
93
+ "pollIntervalSeconds": 60,
94
+ "maxIterationsPerTask": 0,
95
+ "maxCostUsdPerTask": 0,
96
+ "engine": "claude",
97
+ "model": "opus",
98
+ "linear": {
99
+ "team": "ENG",
100
+ "assignee": "me",
101
+ "statuses": ["Todo", "In Progress"],
102
+ "labels": ["ralph", "automation"],
103
+ "inProgressStatus": "In Progress",
104
+ "doneStatus": "In Review",
105
+ "doneLabel": "ralphy-done",
106
+ "postComments": true,
107
+ },
108
+ }
109
+ ```
110
+
111
+ `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.
112
+
113
+ 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.
114
+
71
115
  ## CLI Options
72
116
 
73
117
  | Option | Description |
@@ -87,6 +131,17 @@ ralph status --name fix-auth # Detailed view of one task
87
131
  | `--log` | Log raw JSON stream output |
88
132
  | `--verbose` | Verbose output |
89
133
 
134
+ ### Agent mode flags
135
+
136
+ | Option | Description |
137
+ | ------------------------ | -------------------------------------------- |
138
+ | `--linear-team <key>` | Linear team key (e.g. `ENG`) |
139
+ | `--linear-assignee <id>` | Filter by assignee (user id, email, or `me`) |
140
+ | `--linear-status <name>` | Filter by status name (repeatable) |
141
+ | `--linear-label <name>` | Filter by label name (repeatable, any-of) |
142
+ | `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
143
+ | `--concurrency <n>` | Max concurrent task loops (default: 1) |
144
+
90
145
  ## OpenSpec Flow
91
146
 
92
147
  There are no phases. One loop, one prompt, one `tasks.md` checklist.
package/dist/cli/index.js CHANGED
@@ -69685,6 +69685,26 @@ async function updateIssueState(apiKey, issueId, stateId) {
69685
69685
  stateId
69686
69686
  });
69687
69687
  }
69688
+ async function fetchIssueLabels(apiKey, teamKey) {
69689
+ const query = `query Labels($team: String!) {
69690
+ issueLabels(filter: { team: { key: { eq: $team } } }, first: 250) {
69691
+ nodes { id name }
69692
+ }
69693
+ }`;
69694
+ const data = await linearRequest(apiKey, query, {
69695
+ team: teamKey
69696
+ });
69697
+ return data.issueLabels.nodes;
69698
+ }
69699
+ async function addLabelToIssue(apiKey, issueId, labelId) {
69700
+ const mutation = `mutation AddLabel($id: String!, $labelId: String!) {
69701
+ issueAddLabel(id: $id, labelId: $labelId) { success }
69702
+ }`;
69703
+ await linearRequest(apiKey, mutation, {
69704
+ id: issueId,
69705
+ labelId
69706
+ });
69707
+ }
69688
69708
 
69689
69709
  // apps/cli/src/agent/state.ts
69690
69710
  import { join as join10 } from "path";
@@ -69790,6 +69810,7 @@ var RalphyConfigSchema = exports_external.object({
69790
69810
  labels: exports_external.union([exports_external.array(exports_external.string()), exports_external.string()]).transform((v) => typeof v === "string" ? [v] : v).default([]),
69791
69811
  inProgressStatus: exports_external.string().optional(),
69792
69812
  doneStatus: exports_external.string().optional(),
69813
+ doneLabel: exports_external.string().optional(),
69793
69814
  postComments: exports_external.boolean().default(true)
69794
69815
  }).default({ statuses: [], labels: [], postComments: true })
69795
69816
  }).default({
@@ -69959,6 +69980,27 @@ class AgentCoordinator {
69959
69980
  if (ok && this.opts.doneStatus) {
69960
69981
  await this.moveIssue(issue, this.opts.doneStatus);
69961
69982
  }
69983
+ if (ok && this.opts.doneLabel) {
69984
+ await this.tagIssue(issue, this.opts.doneLabel);
69985
+ }
69986
+ }
69987
+ async tagIssue(issue, labelName) {
69988
+ const updater = this.deps.updater;
69989
+ if (!updater.resolveLabelId || !updater.addLabel) {
69990
+ this.deps.onLog(`! Linear updater does not support labels (cannot tag ${issue.identifier} with '${labelName}')`, "yellow");
69991
+ return;
69992
+ }
69993
+ try {
69994
+ const labelId = await updater.resolveLabelId(issue, labelName);
69995
+ if (!labelId) {
69996
+ this.deps.onLog(`! Linear label '${labelName}' not found for ${issue.identifier}`, "yellow");
69997
+ return;
69998
+ }
69999
+ await updater.addLabel(issue, labelId);
70000
+ this.deps.onLog(` \u2192 ${issue.identifier} tagged with '${labelName}'`, "gray");
70001
+ } catch (err) {
70002
+ this.deps.onLog(`! Linear label add failed for ${issue.identifier}: ${err.message}`, "red");
70003
+ }
69962
70004
  }
69963
70005
  async moveIssue(issue, stateName) {
69964
70006
  const updater = this.deps.updater;
@@ -70022,6 +70064,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70022
70064
  labels: args.linearLabel.length ? args.linearLabel : cfg.linear.labels
70023
70065
  };
70024
70066
  const stateCache = new Map;
70067
+ const labelCache = new Map;
70025
70068
  const teamKeyOf = (issue) => issue.identifier.split("-")[0];
70026
70069
  const coord2 = new AgentCoordinator({
70027
70070
  fetchIssues: (f2) => fetchOpenIssues(apiKey, f2),
@@ -70075,6 +70118,17 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70075
70118
  stateCache.set(team, map2);
70076
70119
  }
70077
70120
  return map2.get(stateName.toLowerCase()) ?? null;
70121
+ },
70122
+ addLabel: (issue, labelId) => addLabelToIssue(apiKey, issue.id, labelId),
70123
+ resolveLabelId: async (issue, labelName) => {
70124
+ const team = teamKeyOf(issue);
70125
+ let map2 = labelCache.get(team);
70126
+ if (!map2) {
70127
+ const labels = await fetchIssueLabels(apiKey, team);
70128
+ map2 = new Map(labels.map((l) => [l.name.toLowerCase(), l.id]));
70129
+ labelCache.set(team, map2);
70130
+ }
70131
+ return map2.get(labelName.toLowerCase()) ?? null;
70078
70132
  }
70079
70133
  }
70080
70134
  }, {
@@ -70082,6 +70136,7 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
70082
70136
  filter: filter2,
70083
70137
  inProgressStatus: cfg.linear.inProgressStatus,
70084
70138
  doneStatus: cfg.linear.doneStatus,
70139
+ doneLabel: cfg.linear.doneLabel,
70085
70140
  postComments: cfg.linear.postComments
70086
70141
  });
70087
70142
  coordRef.current = coord2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neriros/ralphy",
3
- "version": "2.7.1",
3
+ "version": "2.7.3",
4
4
  "description": "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
5
5
  "keywords": [
6
6
  "agent",