@neriros/ralphy 2.13.7 โ 2.13.10
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 +34 -1
- package/dist/cli/index.js +185 -149
- package/dist/mcp/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,12 +82,15 @@ ralph status --name fix-auth # Detailed view of one task
|
|
|
82
82
|
```bash
|
|
83
83
|
export LINEAR_API_KEY=lin_api_xxx
|
|
84
84
|
ralph agent --linear-team ENG --linear-assignee me --concurrency 3 --poll-interval 60
|
|
85
|
+
|
|
86
|
+
# Limit the number of tickets processed in this run
|
|
87
|
+
ralph agent --max-tickets 5 --linear-team ENG --linear-assignee me
|
|
85
88
|
```
|
|
86
89
|
|
|
87
90
|
What it does on each tick:
|
|
88
91
|
|
|
89
92
|
1. Polls Linear for open issues matching the filter (team / assignee / status / labels)
|
|
90
|
-
2. Dedupes against
|
|
93
|
+
2. Dedupes against in-flight workers and any already-active issues
|
|
91
94
|
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
|
|
92
95
|
4. Posts a "๐ค started" comment on the Linear issue and applies the `setInProgress` indicator (if configured)
|
|
93
96
|
5. On worker exit, posts a success/failure comment and applies the `setDone` indicator on success or `setError` on failure (if configured)
|
|
@@ -196,11 +199,41 @@ Failed workers (non-zero exit) are not marked processed, so they'll be retried o
|
|
|
196
199
|
| `--linear-assignee <id>` | Filter by assignee (user id, email, or `me`) |
|
|
197
200
|
| `--poll-interval <s>` | Seconds between Linear polls (default: 60) |
|
|
198
201
|
| `--concurrency <n>` | Max concurrent task loops (default: 1) |
|
|
202
|
+
| `--max-tickets <n>` | Stop picking up new issues after N have been started this run (0 = unlimited) |
|
|
199
203
|
| `--worktree` | Run each task in its own git worktree |
|
|
200
204
|
| `--indicator <k>:<t>:<v>` | Override a `linear.indicators` entry; repeatable (e.g. `setDone:status:Done`) |
|
|
201
205
|
| `--create-pr` | Push worker branch + open a GitHub PR on success (needs `--worktree`) |
|
|
202
206
|
| `--fix-ci` | After PR opens, re-run task on CI failures until green (needs `--create-pr`) |
|
|
203
207
|
|
|
208
|
+
#### `--max-tickets`
|
|
209
|
+
|
|
210
|
+
Use `--max-tickets N` to cap how many issues ralph picks up in a single agent run. Once N issues have been started (across fresh, resume, and conflict-fix modes), the coordinator stops enqueuing new work. In-flight workers continue to completion. The limit resets each time you restart `ralph agent`.
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Process at most 3 issues this session, then idle
|
|
214
|
+
ralph agent --max-tickets 3 --linear-team ENG
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
When the limit is reached, ralph logs a yellow notice and the dashboard header shows `โ tickets โคN`. Polling continues (to handle conflict re-fixes on already-started issues), but no new issues are queued.
|
|
218
|
+
|
|
219
|
+
#### Dashboard
|
|
220
|
+
|
|
221
|
+
The `ralph agent` terminal dashboard shows a full-terminal layout with three always-visible panels:
|
|
222
|
+
|
|
223
|
+
- **RALPH AGENT** (blue box): engine/model, concurrency, poll interval, active limits (`iter โคN`, `cost โค$N`, `tickets โคN`), feature flags (โ PR โ fixCI โ worktree), and the Linear filter on the second line
|
|
224
|
+
- **POLL STATUS + WORKERS** (side-by-side): last-poll counts and next-poll countdown; active/queued worker totals with colored counts
|
|
225
|
+
- **TASKS tab bar** (when multiple workers run): numbered worker tabs with priority glyph and phase โ Tab/โ โ to switch, 1-9 to jump
|
|
226
|
+
|
|
227
|
+
Each worker card shows:
|
|
228
|
+
|
|
229
|
+
- Priority badge (`โฒ URGENT` / `โ HIGH` / `ยท MED` / `โ LOW`) + issue identifier + title + mode badge (`[NEW]` / `[RESUME]` / `[FIX]`)
|
|
230
|
+
- `โ LINEAR ISSUE-ID` and `โ PR #N` (short labels, not full URLs)
|
|
231
|
+
- `โถ TASK` โ first unchecked task from `tasks.md`, updated every second
|
|
232
|
+
- `PHASE` with color (cyan = working, yellow = git ops, blue = CI, green = done, red = gave-up) + time in phase
|
|
233
|
+
- `โต CMD` when a shell command is in flight (shows the command and how long it's been running)
|
|
234
|
+
- `LOG` โ path to the worker's log file for `tail -f`
|
|
235
|
+
- `โ OUTPUT โ` section with live stdout/stderr (scales to fill remaining terminal height for the focused worker)
|
|
236
|
+
|
|
204
237
|
## OpenSpec Flow
|
|
205
238
|
|
|
206
239
|
There are no phases. One loop, one prompt, one `tasks.md` checklist.
|
package/dist/cli/index.js
CHANGED
|
@@ -48157,10 +48157,10 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
48157
48157
|
this.__CANCEL__ = true;
|
|
48158
48158
|
}
|
|
48159
48159
|
}
|
|
48160
|
-
function settle(
|
|
48160
|
+
function settle(resolve2, reject2, response) {
|
|
48161
48161
|
const validateStatus = response.config.validateStatus;
|
|
48162
48162
|
if (!response.status || !validateStatus || validateStatus(response.status)) {
|
|
48163
|
-
|
|
48163
|
+
resolve2(response);
|
|
48164
48164
|
} else {
|
|
48165
48165
|
reject2(new AxiosError("Request failed with status code " + response.status, response.status >= 400 && response.status < 500 ? AxiosError.ERR_BAD_REQUEST : AxiosError.ERR_BAD_RESPONSE, response.config, response.request, response));
|
|
48166
48166
|
}
|
|
@@ -49013,7 +49013,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49013
49013
|
}
|
|
49014
49014
|
var isHttpAdapterSupported = typeof process !== "undefined" && utils$1.kindOf(process) === "process";
|
|
49015
49015
|
var wrapAsync = (asyncExecutor) => {
|
|
49016
|
-
return new Promise((
|
|
49016
|
+
return new Promise((resolve2, reject2) => {
|
|
49017
49017
|
let onDone;
|
|
49018
49018
|
let isDone;
|
|
49019
49019
|
const done = (value, isRejected) => {
|
|
@@ -49024,7 +49024,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49024
49024
|
};
|
|
49025
49025
|
const _resolve = (value) => {
|
|
49026
49026
|
done(value);
|
|
49027
|
-
|
|
49027
|
+
resolve2(value);
|
|
49028
49028
|
};
|
|
49029
49029
|
const _reject = (reason) => {
|
|
49030
49030
|
done(reason, true);
|
|
@@ -49085,7 +49085,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49085
49085
|
}
|
|
49086
49086
|
};
|
|
49087
49087
|
var httpAdapter = isHttpAdapterSupported && function httpAdapter2(config) {
|
|
49088
|
-
return wrapAsync(async function dispatchHttpRequest(
|
|
49088
|
+
return wrapAsync(async function dispatchHttpRequest(resolve2, reject2, onDone) {
|
|
49089
49089
|
const own2 = (key) => utils$1.hasOwnProp(config, key) ? config[key] : undefined;
|
|
49090
49090
|
let data = own2("data");
|
|
49091
49091
|
let lookup = own2("lookup");
|
|
@@ -49193,7 +49193,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49193
49193
|
}
|
|
49194
49194
|
let convertedData;
|
|
49195
49195
|
if (method2 !== "GET") {
|
|
49196
|
-
return settle(
|
|
49196
|
+
return settle(resolve2, reject2, {
|
|
49197
49197
|
status: 405,
|
|
49198
49198
|
statusText: "method not allowed",
|
|
49199
49199
|
headers: {},
|
|
@@ -49215,7 +49215,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49215
49215
|
} else if (responseType === "stream") {
|
|
49216
49216
|
convertedData = stream.Readable.from(convertedData);
|
|
49217
49217
|
}
|
|
49218
|
-
return settle(
|
|
49218
|
+
return settle(resolve2, reject2, {
|
|
49219
49219
|
data: convertedData,
|
|
49220
49220
|
status: 200,
|
|
49221
49221
|
statusText: "OK",
|
|
@@ -49442,7 +49442,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49442
49442
|
});
|
|
49443
49443
|
}
|
|
49444
49444
|
response.data = responseStream;
|
|
49445
|
-
settle(
|
|
49445
|
+
settle(resolve2, reject2, response);
|
|
49446
49446
|
} else {
|
|
49447
49447
|
const responseBuffer = [];
|
|
49448
49448
|
let totalResponseBytes = 0;
|
|
@@ -49481,7 +49481,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49481
49481
|
} catch (err) {
|
|
49482
49482
|
return reject2(AxiosError.from(err, null, config, response.request, response));
|
|
49483
49483
|
}
|
|
49484
|
-
settle(
|
|
49484
|
+
settle(resolve2, reject2, response);
|
|
49485
49485
|
});
|
|
49486
49486
|
}
|
|
49487
49487
|
abortEmitter.once("abort", (err) => {
|
|
@@ -49782,7 +49782,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49782
49782
|
};
|
|
49783
49783
|
var isXHRAdapterSupported = typeof XMLHttpRequest !== "undefined";
|
|
49784
49784
|
var xhrAdapter = isXHRAdapterSupported && function(config) {
|
|
49785
|
-
return new Promise(function dispatchXhrRequest(
|
|
49785
|
+
return new Promise(function dispatchXhrRequest(resolve2, reject2) {
|
|
49786
49786
|
const _config = resolveConfig(config);
|
|
49787
49787
|
let requestData = _config.data;
|
|
49788
49788
|
const requestHeaders = AxiosHeaders.from(_config.headers).normalize();
|
|
@@ -49818,7 +49818,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
49818
49818
|
request
|
|
49819
49819
|
};
|
|
49820
49820
|
settle(function _resolve(value) {
|
|
49821
|
-
|
|
49821
|
+
resolve2(value);
|
|
49822
49822
|
done();
|
|
49823
49823
|
}, function _reject(err) {
|
|
49824
49824
|
reject2(err);
|
|
@@ -50247,8 +50247,8 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50247
50247
|
}
|
|
50248
50248
|
}
|
|
50249
50249
|
!isStreamResponse && unsubscribe && unsubscribe();
|
|
50250
|
-
return await new Promise((
|
|
50251
|
-
settle(
|
|
50250
|
+
return await new Promise((resolve2, reject2) => {
|
|
50251
|
+
settle(resolve2, reject2, {
|
|
50252
50252
|
data: responseData,
|
|
50253
50253
|
headers: AxiosHeaders.from(response.headers),
|
|
50254
50254
|
status: response.status,
|
|
@@ -50640,8 +50640,8 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50640
50640
|
throw new TypeError("executor must be a function.");
|
|
50641
50641
|
}
|
|
50642
50642
|
let resolvePromise;
|
|
50643
|
-
this.promise = new Promise(function promiseExecutor(
|
|
50644
|
-
resolvePromise =
|
|
50643
|
+
this.promise = new Promise(function promiseExecutor(resolve2) {
|
|
50644
|
+
resolvePromise = resolve2;
|
|
50645
50645
|
});
|
|
50646
50646
|
const token = this;
|
|
50647
50647
|
this.promise.then((cancel) => {
|
|
@@ -50655,9 +50655,9 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50655
50655
|
});
|
|
50656
50656
|
this.promise.then = (onfulfilled) => {
|
|
50657
50657
|
let _resolve;
|
|
50658
|
-
const promise = new Promise((
|
|
50659
|
-
token.subscribe(
|
|
50660
|
-
_resolve =
|
|
50658
|
+
const promise = new Promise((resolve2) => {
|
|
50659
|
+
token.subscribe(resolve2);
|
|
50660
|
+
_resolve = resolve2;
|
|
50661
50661
|
}).then(onfulfilled);
|
|
50662
50662
|
promise.cancel = function reject2() {
|
|
50663
50663
|
token.unsubscribe(_resolve);
|
|
@@ -50837,7 +50837,7 @@ var require_axios = __commonJS((exports, module) => {
|
|
|
50837
50837
|
});
|
|
50838
50838
|
|
|
50839
50839
|
// apps/cli/src/index.ts
|
|
50840
|
-
import { resolve, join as join19, dirname as dirname5 } from "path";
|
|
50840
|
+
import { resolve as resolve2, join as join19, dirname as dirname5 } from "path";
|
|
50841
50841
|
import { exists as exists2, mkdir as mkdir5, rm } from "fs/promises";
|
|
50842
50842
|
|
|
50843
50843
|
// node_modules/.bun/ink@5.2.1+1f88f629f0141b18/node_modules/ink/build/render.js
|
|
@@ -56406,102 +56406,36 @@ function styled(text, style) {
|
|
|
56406
56406
|
function log(msg) {
|
|
56407
56407
|
console.log(typeof msg === "string" ? msg : format(msg));
|
|
56408
56408
|
}
|
|
56409
|
-
// package.json
|
|
56410
|
-
var package_default = {
|
|
56411
|
-
name: "@neriros/ralphy",
|
|
56412
|
-
version: "2.13.7",
|
|
56413
|
-
description: "An iterative AI task execution framework. Orchestrates multi-phase autonomous work using Claude or Codex engines.",
|
|
56414
|
-
keywords: [
|
|
56415
|
-
"agent",
|
|
56416
|
-
"ai",
|
|
56417
|
-
"claude",
|
|
56418
|
-
"cli",
|
|
56419
|
-
"loop",
|
|
56420
|
-
"mcp",
|
|
56421
|
-
"ralph",
|
|
56422
|
-
"task-runner"
|
|
56423
|
-
],
|
|
56424
|
-
license: "MIT",
|
|
56425
|
-
repository: {
|
|
56426
|
-
type: "git",
|
|
56427
|
-
url: "https://github.com/NeriRos/ralphy.git"
|
|
56428
|
-
},
|
|
56429
|
-
bin: {
|
|
56430
|
-
ralphy: "./dist/cli/index.js",
|
|
56431
|
-
"ralphy-mcp": "./dist/mcp/index.js"
|
|
56432
|
-
},
|
|
56433
|
-
workspaces: [
|
|
56434
|
-
"packages/*",
|
|
56435
|
-
"apps/*",
|
|
56436
|
-
"tools/generators/*"
|
|
56437
|
-
],
|
|
56438
|
-
files: [
|
|
56439
|
-
"dist/cli/index.js",
|
|
56440
|
-
"dist/mcp/index.js",
|
|
56441
|
-
"README.md"
|
|
56442
|
-
],
|
|
56443
|
-
type: "module",
|
|
56444
|
-
scripts: {
|
|
56445
|
-
ralph: "bun .ralph/bin/cli.js",
|
|
56446
|
-
lint: "oxlint --config .oxlintrc.json .",
|
|
56447
|
-
"lint:fix": "oxlint --config .oxlintrc.json --fix .",
|
|
56448
|
-
fmt: "oxfmt --write .",
|
|
56449
|
-
"fmt:check": "oxfmt --check .",
|
|
56450
|
-
typecheck: "nx affected -t typecheck",
|
|
56451
|
-
"check:deps": "depcruise packages/*/src apps/*/src --config .dependency-cruiser.cjs",
|
|
56452
|
-
"check:unused": "knip",
|
|
56453
|
-
"lint:ci": "nx affected -t lint --exclude=ui",
|
|
56454
|
-
"fmt:ci": "nx affected -t fmt:check --exclude=ui",
|
|
56455
|
-
"typecheck:ci": "nx affected -t typecheck --parallel=1 --exclude=ui",
|
|
56456
|
-
"test:ci": "nx affected -t test --exclude=ui",
|
|
56457
|
-
"test:affected-files:coverage:ci": "bun scripts/bun-test-affected-files.ts --coverage",
|
|
56458
|
-
"test:coverage:ci": "nx affected -t test:coverage --exclude=ui",
|
|
56459
|
-
"build:ci": "nx affected -t build --exclude=ui",
|
|
56460
|
-
"check:circular:ci": "depcruise packages/*/src apps/*/src --config .dependency-cruiser.cjs",
|
|
56461
|
-
"check:unused:ci": "knip",
|
|
56462
|
-
"check:outdated:ci": "bun scripts/check-outdated.ts",
|
|
56463
|
-
prepare: "bunx husky",
|
|
56464
|
-
"build:publish": "bunx nx run-many --target=build --projects=cli,mcp --output-style stream && bun run copy-assets",
|
|
56465
|
-
"copy-assets": "bun scripts/copy-assets.ts",
|
|
56466
|
-
prepublishOnly: "bun run build:publish"
|
|
56467
|
-
},
|
|
56468
|
-
devDependencies: {
|
|
56469
|
-
"@commitlint/cli": "^20.4.3",
|
|
56470
|
-
"@commitlint/config-conventional": "^20.4.3",
|
|
56471
|
-
"@fission-ai/openspec": "latest",
|
|
56472
|
-
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
56473
|
-
"@nx/devkit": "^22.7.1",
|
|
56474
|
-
"@nx/js": "^22.7.1",
|
|
56475
|
-
"@secretlint/secretlint-rule-preset-recommend": "^11.3.1",
|
|
56476
|
-
"@swc-node/register": "^1.11.1",
|
|
56477
|
-
"@swc/core": "^1.15.18",
|
|
56478
|
-
"@total-typescript/ts-reset": "^0.6.1",
|
|
56479
|
-
"@types/node": "^22.0.0",
|
|
56480
|
-
"bun-types": "^1.3.0",
|
|
56481
|
-
chalk: "^5.4.0",
|
|
56482
|
-
"dependency-cruiser": "^17.3.8",
|
|
56483
|
-
husky: "^9.1.7",
|
|
56484
|
-
knip: "^5.85.0",
|
|
56485
|
-
"lint-staged": "^16.3.2",
|
|
56486
|
-
nx: "22.5.3",
|
|
56487
|
-
"oxc-parser": "^0.126.0",
|
|
56488
|
-
oxfmt: "^0.36.0",
|
|
56489
|
-
oxlint: "^1.51.0",
|
|
56490
|
-
secretlint: "^11.3.1",
|
|
56491
|
-
typescript: "^5.8.0",
|
|
56492
|
-
zod: "^3.24.0"
|
|
56493
|
-
},
|
|
56494
|
-
overrides: {
|
|
56495
|
-
axios: "^1.15.1",
|
|
56496
|
-
minimatch: "^10.2.3"
|
|
56497
|
-
},
|
|
56498
|
-
engines: {
|
|
56499
|
-
bun: ">=1.0.0"
|
|
56500
|
-
}
|
|
56501
|
-
};
|
|
56502
56409
|
|
|
56503
56410
|
// apps/cli/src/cli.ts
|
|
56504
|
-
|
|
56411
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
56412
|
+
import { resolve } from "path";
|
|
56413
|
+
function getVersion() {
|
|
56414
|
+
const dirsToTry = [];
|
|
56415
|
+
try {
|
|
56416
|
+
const cliDir = import.meta.dir;
|
|
56417
|
+
dirsToTry.push(cliDir);
|
|
56418
|
+
} catch {}
|
|
56419
|
+
dirsToTry.push(process.cwd());
|
|
56420
|
+
for (const startDir of dirsToTry) {
|
|
56421
|
+
let current = startDir;
|
|
56422
|
+
for (let i = 0;i < 10; i++) {
|
|
56423
|
+
const pkgPath = resolve(current, "package.json");
|
|
56424
|
+
try {
|
|
56425
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
56426
|
+
if (pkg.workspaces && pkg.version && pkg.version !== "0.0.0") {
|
|
56427
|
+
return pkg.version;
|
|
56428
|
+
}
|
|
56429
|
+
} catch {}
|
|
56430
|
+
const parent = resolve(current, "..");
|
|
56431
|
+
if (parent === current)
|
|
56432
|
+
break;
|
|
56433
|
+
current = parent;
|
|
56434
|
+
}
|
|
56435
|
+
}
|
|
56436
|
+
return "unknown";
|
|
56437
|
+
}
|
|
56438
|
+
var VERSION = getVersion();
|
|
56505
56439
|
var VALID_MODES = new Set(["task", "list", "status", "init", "agent", "clean"]);
|
|
56506
56440
|
var VALID_MODELS = new Set(["haiku", "sonnet", "opus"]);
|
|
56507
56441
|
var INDICATOR_KEYS = new Set([
|
|
@@ -56541,6 +56475,7 @@ var HELP_TEXT = [
|
|
|
56541
56475
|
" --max-runtime <n> Stop after N minutes of wall-clock time (0 = no limit)",
|
|
56542
56476
|
" --max-failures <n> Stop after N consecutive failures (default: 5, 0 = disable)",
|
|
56543
56477
|
" --unlimited No iteration limit (default)",
|
|
56478
|
+
" --manual-test Enable manual testing phase (create test tasks in tasks.md)",
|
|
56544
56479
|
" --log Log raw engine stream",
|
|
56545
56480
|
" --verbose Verbose output",
|
|
56546
56481
|
"",
|
|
@@ -56559,6 +56494,7 @@ var HELP_TEXT = [
|
|
|
56559
56494
|
" Types: label, status",
|
|
56560
56495
|
" --create-pr Push the worker branch and open a GitHub PR on success (needs --worktree)",
|
|
56561
56496
|
" --fix-ci After opening the PR, re-run on CI failures until green (needs --create-pr)",
|
|
56497
|
+
" --max-tickets <n> Stop picking up new issues after N have been started (0 = unlimited)",
|
|
56562
56498
|
"",
|
|
56563
56499
|
" --help, -h Show this help message",
|
|
56564
56500
|
"",
|
|
@@ -56636,6 +56572,7 @@ async function parseArgs(argv) {
|
|
|
56636
56572
|
delay: 0,
|
|
56637
56573
|
log: false,
|
|
56638
56574
|
verbose: false,
|
|
56575
|
+
manualTest: false,
|
|
56639
56576
|
linearTeam: "",
|
|
56640
56577
|
linearAssignee: "",
|
|
56641
56578
|
pollInterval: 60,
|
|
@@ -56643,7 +56580,8 @@ async function parseArgs(argv) {
|
|
|
56643
56580
|
worktree: false,
|
|
56644
56581
|
indicators: {},
|
|
56645
56582
|
createPr: false,
|
|
56646
|
-
fixCi: false
|
|
56583
|
+
fixCi: false,
|
|
56584
|
+
maxTickets: 0
|
|
56647
56585
|
};
|
|
56648
56586
|
let expectModel = false;
|
|
56649
56587
|
let expectModelFlag = false;
|
|
@@ -56659,6 +56597,7 @@ async function parseArgs(argv) {
|
|
|
56659
56597
|
let expectLinearAssignee = false;
|
|
56660
56598
|
let expectPollInterval = false;
|
|
56661
56599
|
let expectConcurrency = false;
|
|
56600
|
+
let expectMaxTickets = false;
|
|
56662
56601
|
let expectIndicator = false;
|
|
56663
56602
|
for (const arg of argv) {
|
|
56664
56603
|
if (expectModel) {
|
|
@@ -56737,6 +56676,11 @@ async function parseArgs(argv) {
|
|
|
56737
56676
|
expectConcurrency = false;
|
|
56738
56677
|
continue;
|
|
56739
56678
|
}
|
|
56679
|
+
if (expectMaxTickets) {
|
|
56680
|
+
result2.maxTickets = parseInt(arg, 10);
|
|
56681
|
+
expectMaxTickets = false;
|
|
56682
|
+
continue;
|
|
56683
|
+
}
|
|
56740
56684
|
if (expectIndicator) {
|
|
56741
56685
|
const { key, marker } = parseIndicatorArg(arg);
|
|
56742
56686
|
mergeIndicator(result2.indicators, key, marker);
|
|
@@ -56807,6 +56751,9 @@ async function parseArgs(argv) {
|
|
|
56807
56751
|
case "--concurrency":
|
|
56808
56752
|
expectConcurrency = true;
|
|
56809
56753
|
break;
|
|
56754
|
+
case "--max-tickets":
|
|
56755
|
+
expectMaxTickets = true;
|
|
56756
|
+
break;
|
|
56810
56757
|
case "--worktree":
|
|
56811
56758
|
result2.worktree = true;
|
|
56812
56759
|
break;
|
|
@@ -56819,6 +56766,9 @@ async function parseArgs(argv) {
|
|
|
56819
56766
|
case "--fix-ci":
|
|
56820
56767
|
result2.fixCi = true;
|
|
56821
56768
|
break;
|
|
56769
|
+
case "--manual-test":
|
|
56770
|
+
result2.manualTest = true;
|
|
56771
|
+
break;
|
|
56822
56772
|
default:
|
|
56823
56773
|
if (VALID_MODES.has(arg)) {
|
|
56824
56774
|
result2.mode = arg;
|
|
@@ -56834,7 +56784,7 @@ async function parseArgs(argv) {
|
|
|
56834
56784
|
// packages/context/src/context.ts
|
|
56835
56785
|
import { AsyncLocalStorage } from "async_hooks";
|
|
56836
56786
|
import {
|
|
56837
|
-
readFileSync as
|
|
56787
|
+
readFileSync as readFileSync3,
|
|
56838
56788
|
writeFileSync,
|
|
56839
56789
|
existsSync as existsSync2,
|
|
56840
56790
|
unlinkSync,
|
|
@@ -56847,7 +56797,7 @@ class FileSystemProvider {
|
|
|
56847
56797
|
read(path) {
|
|
56848
56798
|
if (!existsSync2(path))
|
|
56849
56799
|
return null;
|
|
56850
|
-
return
|
|
56800
|
+
return readFileSync3(path, "utf-8");
|
|
56851
56801
|
}
|
|
56852
56802
|
write(path, content) {
|
|
56853
56803
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -60907,6 +60857,7 @@ var StateSchema = exports_external.object({
|
|
|
60907
60857
|
lastModified: exports_external.string(),
|
|
60908
60858
|
engine: exports_external.enum(["claude", "codex"]).default("claude"),
|
|
60909
60859
|
model: exports_external.string().default("opus"),
|
|
60860
|
+
manualTest: exports_external.boolean().default(false),
|
|
60910
60861
|
usage: UsageSchema.default({}),
|
|
60911
60862
|
history: exports_external.array(HistoryEntrySchema).default([]),
|
|
60912
60863
|
metadata: exports_external.object({ branch: exports_external.string().optional() }).default({})
|
|
@@ -60981,6 +60932,7 @@ function buildInitialState(options) {
|
|
|
60981
60932
|
prompt: options.prompt,
|
|
60982
60933
|
engine: options.engine ?? "claude",
|
|
60983
60934
|
model: options.model ?? "opus",
|
|
60935
|
+
manualTest: options.manualTest ?? false,
|
|
60984
60936
|
createdAt: now2,
|
|
60985
60937
|
lastModified: now2,
|
|
60986
60938
|
metadata: { branch }
|
|
@@ -65738,10 +65690,10 @@ async function runEngine(opts) {
|
|
|
65738
65690
|
rawWriter.write(line + `
|
|
65739
65691
|
`);
|
|
65740
65692
|
};
|
|
65741
|
-
const closeRaw = () => new Promise((
|
|
65693
|
+
const closeRaw = () => new Promise((resolve2) => {
|
|
65742
65694
|
if (!rawWriter)
|
|
65743
|
-
return
|
|
65744
|
-
rawWriter.end(
|
|
65695
|
+
return resolve2();
|
|
65696
|
+
rawWriter.end(resolve2);
|
|
65745
65697
|
});
|
|
65746
65698
|
const emit = opts.onFeedEvent;
|
|
65747
65699
|
function emitEvent(event) {
|
|
@@ -66567,14 +66519,14 @@ async function addSourceContext(frames) {
|
|
|
66567
66519
|
return frames;
|
|
66568
66520
|
}
|
|
66569
66521
|
function getContextLinesFromFile(path, ranges, output) {
|
|
66570
|
-
return new Promise((
|
|
66522
|
+
return new Promise((resolve2) => {
|
|
66571
66523
|
const stream = createReadStream(path);
|
|
66572
66524
|
const lineReaded = createInterface({
|
|
66573
66525
|
input: stream
|
|
66574
66526
|
});
|
|
66575
66527
|
function destroyStreamAndResolve() {
|
|
66576
66528
|
stream.destroy();
|
|
66577
|
-
|
|
66529
|
+
resolve2();
|
|
66578
66530
|
}
|
|
66579
66531
|
let lineNumber = 0;
|
|
66580
66532
|
let currentRangeIndex = 0;
|
|
@@ -69106,15 +69058,15 @@ class PostHogBackendClient extends PostHogCoreStateless {
|
|
|
69106
69058
|
if (this.featureFlagsPoller === undefined) {
|
|
69107
69059
|
return false;
|
|
69108
69060
|
}
|
|
69109
|
-
return new Promise((
|
|
69061
|
+
return new Promise((resolve2) => {
|
|
69110
69062
|
const timeout = setTimeout(() => {
|
|
69111
69063
|
cleanup();
|
|
69112
|
-
|
|
69064
|
+
resolve2(false);
|
|
69113
69065
|
}, timeoutMs);
|
|
69114
69066
|
const cleanup = this._events.on("localEvaluationFlagsLoaded", (count) => {
|
|
69115
69067
|
clearTimeout(timeout);
|
|
69116
69068
|
cleanup();
|
|
69117
|
-
|
|
69069
|
+
resolve2(count > 0);
|
|
69118
69070
|
});
|
|
69119
69071
|
});
|
|
69120
69072
|
}
|
|
@@ -69601,6 +69553,34 @@ function buildTaskPrompt(state, taskDir) {
|
|
|
69601
69553
|
|
|
69602
69554
|
`;
|
|
69603
69555
|
}
|
|
69556
|
+
if (state.manualTest) {
|
|
69557
|
+
const tasksContent2 = storage.read(join7(taskDir, "tasks.md"));
|
|
69558
|
+
const hasUncheckedTasks = tasksContent2 !== null && /^- \[ \]/m.test(tasksContent2);
|
|
69559
|
+
if (!hasUncheckedTasks) {
|
|
69560
|
+
const hasManualTestSection = tasksContent2 !== null && /^## Manual Testing/m.test(tasksContent2);
|
|
69561
|
+
if (!hasManualTestSection) {
|
|
69562
|
+
prompt += `---
|
|
69563
|
+
|
|
69564
|
+
## Manual Testing Phase
|
|
69565
|
+
|
|
69566
|
+
`;
|
|
69567
|
+
prompt += `All primary implementation tasks are complete. Now create manual test tasks.
|
|
69568
|
+
|
|
69569
|
+
`;
|
|
69570
|
+
prompt += `1. Analyze the specification and implementation
|
|
69571
|
+
`;
|
|
69572
|
+
prompt += `2. Identify critical manual test scenarios (UI interactions, edge cases, user workflows, integration testing)
|
|
69573
|
+
`;
|
|
69574
|
+
prompt += "3. Add a `## Manual Testing` section to tasks.md with test items as `- [ ] Test scenario description`\n";
|
|
69575
|
+
prompt += `4. Complete each test and check it off when done
|
|
69576
|
+
|
|
69577
|
+
`;
|
|
69578
|
+
prompt += `---
|
|
69579
|
+
|
|
69580
|
+
`;
|
|
69581
|
+
}
|
|
69582
|
+
}
|
|
69583
|
+
}
|
|
69604
69584
|
prompt += `Change name: \`${state.name}\`
|
|
69605
69585
|
|
|
69606
69586
|
`;
|
|
@@ -69718,7 +69698,7 @@ function mergeUsage(base2, resumed) {
|
|
|
69718
69698
|
}
|
|
69719
69699
|
// apps/cli/src/hooks/useLoop.ts
|
|
69720
69700
|
function sleep(seconds) {
|
|
69721
|
-
return new Promise((
|
|
69701
|
+
return new Promise((resolve2) => setTimeout(resolve2, seconds * 1000));
|
|
69722
69702
|
}
|
|
69723
69703
|
function useLoop(opts) {
|
|
69724
69704
|
const [state, setState] = import_react55.useState(null);
|
|
@@ -69772,7 +69752,8 @@ function useLoop(opts) {
|
|
|
69772
69752
|
name: opts.name,
|
|
69773
69753
|
prompt: opts.prompt,
|
|
69774
69754
|
engine: opts.engine,
|
|
69775
|
-
model: opts.model
|
|
69755
|
+
model: opts.model,
|
|
69756
|
+
manualTest: opts.manualTest
|
|
69776
69757
|
});
|
|
69777
69758
|
writeState(stateDir, currentState);
|
|
69778
69759
|
}
|
|
@@ -70177,6 +70158,7 @@ var RalphyConfigSchema = exports_external.object({
|
|
|
70177
70158
|
iterationDelaySeconds: exports_external.number().int().nonnegative().default(0),
|
|
70178
70159
|
logRawStream: exports_external.boolean().default(false),
|
|
70179
70160
|
taskVerbose: exports_external.boolean().default(false),
|
|
70161
|
+
enableManualTest: exports_external.boolean().default(false),
|
|
70180
70162
|
useWorktree: exports_external.boolean().default(false),
|
|
70181
70163
|
cleanupWorktreeOnSuccess: exports_external.boolean().default(false),
|
|
70182
70164
|
setupScript: exports_external.string().optional(),
|
|
@@ -70201,6 +70183,7 @@ var RalphyConfigSchema = exports_external.object({
|
|
|
70201
70183
|
pollIntervalSeconds: 60,
|
|
70202
70184
|
maxIterationsPerTask: 0,
|
|
70203
70185
|
maxCostUsdPerTask: 0,
|
|
70186
|
+
enableManualTest: false,
|
|
70204
70187
|
engine: "claude",
|
|
70205
70188
|
model: "opus",
|
|
70206
70189
|
linear: { postComments: true, updateEveryIterations: 10, indicators: {} }
|
|
@@ -70401,10 +70384,8 @@ function buildIssueFilter(spec) {
|
|
|
70401
70384
|
branches.push({ state: { name: { in: statuses } } });
|
|
70402
70385
|
if (labels.length > 0)
|
|
70403
70386
|
branches.push({ labels: { some: { name: { in: labels } } } });
|
|
70404
|
-
|
|
70405
|
-
Object.assign(where,
|
|
70406
|
-
else
|
|
70407
|
-
where.or = branches;
|
|
70387
|
+
for (const b of branches)
|
|
70388
|
+
Object.assign(where, b);
|
|
70408
70389
|
} else {
|
|
70409
70390
|
where.state = { type: { in: ["unstarted", "started", "backlog"] } };
|
|
70410
70391
|
}
|
|
@@ -70562,6 +70543,7 @@ class AgentCoordinator {
|
|
|
70562
70543
|
queue = [];
|
|
70563
70544
|
stopped = false;
|
|
70564
70545
|
conflictNotified = new Set;
|
|
70546
|
+
ticketsStarted = 0;
|
|
70565
70547
|
constructor(deps, opts) {
|
|
70566
70548
|
this.deps = deps;
|
|
70567
70549
|
this.opts = opts;
|
|
@@ -70575,6 +70557,9 @@ class AgentCoordinator {
|
|
|
70575
70557
|
get activeWorkers() {
|
|
70576
70558
|
return this.workers;
|
|
70577
70559
|
}
|
|
70560
|
+
get ticketsStartedCount() {
|
|
70561
|
+
return this.ticketsStarted;
|
|
70562
|
+
}
|
|
70578
70563
|
async init() {}
|
|
70579
70564
|
async pollOnce() {
|
|
70580
70565
|
if (this.stopped)
|
|
@@ -70596,8 +70581,17 @@ class AgentCoordinator {
|
|
|
70596
70581
|
const queuedIds = new Set(this.queue.map((q) => q.issue.id));
|
|
70597
70582
|
const activeIds = new Set(this.workers.map((w) => w.issueId));
|
|
70598
70583
|
const eligible = (id) => !queuedIds.has(id) && !activeIds.has(id) && !this.pendingIds.has(id);
|
|
70584
|
+
const maxT = this.opts.maxTickets ?? 0;
|
|
70585
|
+
const atTicketLimit = () => {
|
|
70586
|
+
if (maxT === 0)
|
|
70587
|
+
return false;
|
|
70588
|
+
const inFlight = this.ticketsStarted + this.queue.length + this.workers.length + this.pendingIds.size;
|
|
70589
|
+
return inFlight >= maxT;
|
|
70590
|
+
};
|
|
70599
70591
|
let added = 0;
|
|
70600
70592
|
for (const issue of inProgress) {
|
|
70593
|
+
if (atTicketLimit())
|
|
70594
|
+
break;
|
|
70601
70595
|
if (!eligible(issue.id))
|
|
70602
70596
|
continue;
|
|
70603
70597
|
if (!this.dependenciesResolved(issue))
|
|
@@ -70607,6 +70601,8 @@ class AgentCoordinator {
|
|
|
70607
70601
|
added += 1;
|
|
70608
70602
|
}
|
|
70609
70603
|
for (const issue of conflicted) {
|
|
70604
|
+
if (atTicketLimit())
|
|
70605
|
+
break;
|
|
70610
70606
|
if (!eligible(issue.id))
|
|
70611
70607
|
continue;
|
|
70612
70608
|
this.queue.push({ issue, mode: "conflict-fix" });
|
|
@@ -70614,6 +70610,8 @@ class AgentCoordinator {
|
|
|
70614
70610
|
added += 1;
|
|
70615
70611
|
}
|
|
70616
70612
|
for (const issue of todo) {
|
|
70613
|
+
if (atTicketLimit())
|
|
70614
|
+
break;
|
|
70617
70615
|
if (!eligible(issue.id))
|
|
70618
70616
|
continue;
|
|
70619
70617
|
if (!this.dependenciesResolved(issue))
|
|
@@ -70808,6 +70806,11 @@ class AgentCoordinator {
|
|
|
70808
70806
|
};
|
|
70809
70807
|
this.workers.push(worker);
|
|
70810
70808
|
this.pendingIds.delete(issue.id);
|
|
70809
|
+
this.ticketsStarted += 1;
|
|
70810
|
+
const maxT = this.opts.maxTickets ?? 0;
|
|
70811
|
+
if (maxT > 0 && this.ticketsStarted >= maxT) {
|
|
70812
|
+
this.deps.onLog(` ticket limit reached (${maxT}) \u2014 no new issues will be picked up`, "yellow");
|
|
70813
|
+
}
|
|
70811
70814
|
capture("agent_worker_spawned", {
|
|
70812
70815
|
spawn_mode: mode,
|
|
70813
70816
|
issue_identifier: issue.identifier
|
|
@@ -71829,6 +71832,8 @@ PR: ${prUrl}` : ""
|
|
|
71829
71832
|
c.push("--log");
|
|
71830
71833
|
if (args.verbose || cfg.taskVerbose)
|
|
71831
71834
|
c.push("--verbose");
|
|
71835
|
+
if (args.manualTest || cfg.enableManualTest)
|
|
71836
|
+
c.push("--manual-test");
|
|
71832
71837
|
return c;
|
|
71833
71838
|
}
|
|
71834
71839
|
function defaultSpawn(changeName, cmd, cwd2, note) {
|
|
@@ -72054,7 +72059,8 @@ PR: ${prUrl}` : ""
|
|
|
72054
72059
|
...indicators.setConflicted !== undefined ? { setConflicted: indicators.setConflicted } : {},
|
|
72055
72060
|
...indicators.clearConflicted !== undefined ? { clearConflicted: indicators.clearConflicted } : {},
|
|
72056
72061
|
postComments: cfg.linear.postComments,
|
|
72057
|
-
commentEveryIterations: cfg.linear.updateEveryIterations
|
|
72062
|
+
commentEveryIterations: cfg.linear.updateEveryIterations,
|
|
72063
|
+
...args.maxTickets > 0 ? { maxTickets: args.maxTickets } : {}
|
|
72058
72064
|
});
|
|
72059
72065
|
const filterDesc = describeIndicators(indicators, team, assignee);
|
|
72060
72066
|
return {
|
|
@@ -72496,6 +72502,13 @@ function AgentMode({ args, projectRoot, statesDir, tasksDir }) {
|
|
|
72496
72502
|
cfg.maxCostUsdPerTask
|
|
72497
72503
|
]
|
|
72498
72504
|
}, undefined, true, undefined, this),
|
|
72505
|
+
args.maxTickets > 0 && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
72506
|
+
color: "yellow",
|
|
72507
|
+
children: [
|
|
72508
|
+
" \u2502 tickets \u2264",
|
|
72509
|
+
args.maxTickets
|
|
72510
|
+
]
|
|
72511
|
+
}, undefined, true, undefined, this),
|
|
72499
72512
|
cfg.createPrOnSuccess && /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(Text, {
|
|
72500
72513
|
color: "green",
|
|
72501
72514
|
children: " \u25CF PR"
|
|
@@ -73152,6 +73165,41 @@ function ErrorMessage({ message }) {
|
|
|
73152
73165
|
children: message
|
|
73153
73166
|
}, undefined, false, undefined, this);
|
|
73154
73167
|
}
|
|
73168
|
+
function TaskModeWrapper({ args, statesDir, tasksDir, projectRoot }) {
|
|
73169
|
+
const [config, setConfig] = import_react58.useState(null);
|
|
73170
|
+
const [error, setError] = import_react58.useState(null);
|
|
73171
|
+
import_react58.useEffect(() => {
|
|
73172
|
+
loadRalphyConfig(projectRoot).then((cfg) => setConfig({ manualTest: cfg.enableManualTest })).catch((err) => setError(err.message));
|
|
73173
|
+
}, [projectRoot]);
|
|
73174
|
+
if (error) {
|
|
73175
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(ErrorMessage, {
|
|
73176
|
+
message: `Error loading config: ${error}`
|
|
73177
|
+
}, undefined, false, undefined, this);
|
|
73178
|
+
}
|
|
73179
|
+
if (!config) {
|
|
73180
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(Text, {}, undefined, false, undefined, this);
|
|
73181
|
+
}
|
|
73182
|
+
const manualTest = args.manualTest || config.manualTest;
|
|
73183
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskLoop, {
|
|
73184
|
+
opts: {
|
|
73185
|
+
name: args.name,
|
|
73186
|
+
prompt: args.prompt,
|
|
73187
|
+
engine: args.engine,
|
|
73188
|
+
model: args.model,
|
|
73189
|
+
maxIterations: args.maxIterations,
|
|
73190
|
+
maxCostUsd: args.maxCostUsd,
|
|
73191
|
+
maxRuntimeMinutes: args.maxRuntimeMinutes,
|
|
73192
|
+
maxConsecutiveFailures: args.maxConsecutiveFailures,
|
|
73193
|
+
delay: args.delay,
|
|
73194
|
+
log: args.log,
|
|
73195
|
+
verbose: args.verbose,
|
|
73196
|
+
manualTest,
|
|
73197
|
+
statesDir,
|
|
73198
|
+
tasksDir,
|
|
73199
|
+
changeStore: new OpenSpecChangeStore
|
|
73200
|
+
}
|
|
73201
|
+
}, undefined, false, undefined, this);
|
|
73202
|
+
}
|
|
73155
73203
|
function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
73156
73204
|
switch (args.mode) {
|
|
73157
73205
|
case "list":
|
|
@@ -73203,23 +73251,11 @@ function App2({ args, statesDir, tasksDir, projectRoot }) {
|
|
|
73203
73251
|
message: "Error: --name is required for task mode"
|
|
73204
73252
|
}, undefined, false, undefined, this);
|
|
73205
73253
|
}
|
|
73206
|
-
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(
|
|
73207
|
-
|
|
73208
|
-
|
|
73209
|
-
|
|
73210
|
-
|
|
73211
|
-
model: args.model,
|
|
73212
|
-
maxIterations: args.maxIterations,
|
|
73213
|
-
maxCostUsd: args.maxCostUsd,
|
|
73214
|
-
maxRuntimeMinutes: args.maxRuntimeMinutes,
|
|
73215
|
-
maxConsecutiveFailures: args.maxConsecutiveFailures,
|
|
73216
|
-
delay: args.delay,
|
|
73217
|
-
log: args.log,
|
|
73218
|
-
verbose: args.verbose,
|
|
73219
|
-
statesDir,
|
|
73220
|
-
tasksDir,
|
|
73221
|
-
changeStore: new OpenSpecChangeStore
|
|
73222
|
-
}
|
|
73254
|
+
return /* @__PURE__ */ jsx_dev_runtime10.jsxDEV(TaskModeWrapper, {
|
|
73255
|
+
args,
|
|
73256
|
+
statesDir,
|
|
73257
|
+
tasksDir,
|
|
73258
|
+
projectRoot
|
|
73223
73259
|
}, undefined, false, undefined, this);
|
|
73224
73260
|
}
|
|
73225
73261
|
}
|
|
@@ -73236,7 +73272,7 @@ async function findProjectRoot() {
|
|
|
73236
73272
|
while (dir !== "/") {
|
|
73237
73273
|
if (await exists2(join19(dir, "openspec")))
|
|
73238
73274
|
return dir;
|
|
73239
|
-
dir =
|
|
73275
|
+
dir = resolve2(dir, "..");
|
|
73240
73276
|
}
|
|
73241
73277
|
return process.cwd();
|
|
73242
73278
|
}
|
package/dist/mcp/index.js
CHANGED
|
@@ -23966,6 +23966,7 @@ var StateSchema = exports_external.object({
|
|
|
23966
23966
|
lastModified: exports_external.string(),
|
|
23967
23967
|
engine: exports_external.enum(["claude", "codex"]).default("claude"),
|
|
23968
23968
|
model: exports_external.string().default("opus"),
|
|
23969
|
+
manualTest: exports_external.boolean().default(false),
|
|
23969
23970
|
usage: UsageSchema.default({}),
|
|
23970
23971
|
history: exports_external.array(HistoryEntrySchema).default([]),
|
|
23971
23972
|
metadata: exports_external.object({ branch: exports_external.string().optional() }).default({})
|
|
@@ -24031,6 +24032,7 @@ function buildInitialState(options) {
|
|
|
24031
24032
|
prompt: options.prompt,
|
|
24032
24033
|
engine: options.engine ?? "claude",
|
|
24033
24034
|
model: options.model ?? "opus",
|
|
24035
|
+
manualTest: options.manualTest ?? false,
|
|
24034
24036
|
createdAt: now,
|
|
24035
24037
|
lastModified: now,
|
|
24036
24038
|
metadata: { branch }
|