@neriros/ralphy 3.10.9 → 3.10.11
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/mcp/index.js +10 -0
- package/dist/shell/index.js +1662 -1024
- package/package.json +1 -1
package/dist/shell/index.js
CHANGED
|
@@ -18928,8 +18928,8 @@ import { readFileSync } from "fs";
|
|
|
18928
18928
|
import { resolve } from "path";
|
|
18929
18929
|
function getVersion() {
|
|
18930
18930
|
try {
|
|
18931
|
-
if ("3.10.
|
|
18932
|
-
return "3.10.
|
|
18931
|
+
if ("3.10.11")
|
|
18932
|
+
return "3.10.11";
|
|
18933
18933
|
} catch {}
|
|
18934
18934
|
const dirsToTry = [];
|
|
18935
18935
|
try {
|
|
@@ -19376,6 +19376,764 @@ var init_src2 = __esm(() => {
|
|
|
19376
19376
|
noopFallback = createNoopBus();
|
|
19377
19377
|
});
|
|
19378
19378
|
|
|
19379
|
+
// packages/workflow/src/fields.ts
|
|
19380
|
+
function fieldsForMode(mode, answers = {}, restrictTo) {
|
|
19381
|
+
const all = mode === "customized" ? CUSTOMIZED_FIELDS : QUICK_FIELDS;
|
|
19382
|
+
const allowed = restrictTo ? new Set(restrictTo) : null;
|
|
19383
|
+
return all.filter((field) => {
|
|
19384
|
+
if (HIDDEN_FIELD_IDS.has(field.id))
|
|
19385
|
+
return false;
|
|
19386
|
+
if (allowed && !allowed.has(field.id))
|
|
19387
|
+
return false;
|
|
19388
|
+
return !field.when || field.when(answers);
|
|
19389
|
+
});
|
|
19390
|
+
}
|
|
19391
|
+
function findField(id) {
|
|
19392
|
+
return CUSTOMIZED_FIELDS.find((field) => field.id === id);
|
|
19393
|
+
}
|
|
19394
|
+
function modelOptionValues() {
|
|
19395
|
+
const field = findField("model");
|
|
19396
|
+
return field && field.spec.kind === "select" ? field.spec.options.map((o) => o.value) : [];
|
|
19397
|
+
}
|
|
19398
|
+
var PROMPT_BODY_FIELD_ID = "promptBody", REPO_LINK_FIELD_ID = "repo.link", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_ASSIGNEE_CHOICE_FIELD_ID = "linear.assigneeChoice", LINEAR_ASSIGNEE_VALUE_FIELD_ID = "linear.assigneeValue", LINEAR_FILTER_DESCRIPTION, LINEAR_ASSIGNEE_CHOICE, LINEAR_ASSIGNEE_VALUE, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, concurrencyForcesWorktree = (answers) => {
|
|
19399
|
+
const value = answers["concurrency"];
|
|
19400
|
+
return typeof value === "number" && value > 1;
|
|
19401
|
+
}, worktreeEnabled = (answers) => answers["useWorktree"] === true || concurrencyForcesWorktree(answers), HIDDEN_FIELD_IDS, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
|
|
19402
|
+
var init_fields = __esm(() => {
|
|
19403
|
+
PROJECT_NAME = {
|
|
19404
|
+
id: "project.name",
|
|
19405
|
+
label: "Project name",
|
|
19406
|
+
description: "The project's display name. Ralphy puts it in the agent's prompt and in its logs.",
|
|
19407
|
+
spec: { kind: "text", placeholder: "my-project" }
|
|
19408
|
+
};
|
|
19409
|
+
LINEAR_TEAM = {
|
|
19410
|
+
id: "linear.team",
|
|
19411
|
+
label: "Linear team key",
|
|
19412
|
+
hint: "e.g. ENG \u2014 leave blank to match all teams",
|
|
19413
|
+
description: "The Linear team this repository is linked to, given by its key (e.g. ENG). Ralphy only picks up issues from this team. Leave blank to watch every team.",
|
|
19414
|
+
emptyLabel: "all teams",
|
|
19415
|
+
spec: { kind: "text" }
|
|
19416
|
+
};
|
|
19417
|
+
REPO_LINK = {
|
|
19418
|
+
id: "repo.link",
|
|
19419
|
+
label: "Record this repository in WORKFLOW.md?",
|
|
19420
|
+
description: "Record the detected git repository in WORKFLOW.md so Ralphy maps this project's Linear issues to it. Confirm to adopt the detected repo; decline to leave it out.",
|
|
19421
|
+
spec: yes(),
|
|
19422
|
+
when: (answers) => typeof answers["repo.name"] === "string" && answers["repo.name"] !== ""
|
|
19423
|
+
};
|
|
19424
|
+
LINEAR_FILTER_DESCRIPTION = "Global filter applied to every Linear ticket fetch, as an 'assignee = <value>' clause. " + "<value> is 'me' (issues assigned to you), 'any' (regardless of assignee), 'unassigned', " + "or a specific Linear user (email or user-id). Blank defaults to 'assignee = me'.";
|
|
19425
|
+
LINEAR_ASSIGNEE_CHOICE = {
|
|
19426
|
+
id: LINEAR_ASSIGNEE_CHOICE_FIELD_ID,
|
|
19427
|
+
label: "Linear assignee filter",
|
|
19428
|
+
description: "Which Linear issues Ralphy fetches, by assignee: 'me' (assigned to you), 'any' (regardless of assignee), 'unassigned', or a specific user you name next.",
|
|
19429
|
+
spec: {
|
|
19430
|
+
kind: "select",
|
|
19431
|
+
options: [
|
|
19432
|
+
{ label: "me (assigned to you)", value: "me" },
|
|
19433
|
+
{ label: "any (regardless of assignee)", value: "any" },
|
|
19434
|
+
{ label: "unassigned", value: "unassigned" },
|
|
19435
|
+
{ label: "a specific user (email or user-id)\u2026", value: "other" }
|
|
19436
|
+
]
|
|
19437
|
+
}
|
|
19438
|
+
};
|
|
19439
|
+
LINEAR_ASSIGNEE_VALUE = {
|
|
19440
|
+
id: LINEAR_ASSIGNEE_VALUE_FIELD_ID,
|
|
19441
|
+
label: "Assignee email or user-id",
|
|
19442
|
+
description: "The specific Linear user to filter by \u2014 their email address or Linear user-id.",
|
|
19443
|
+
spec: { kind: "text", placeholder: "you@example.com" },
|
|
19444
|
+
when: (answers) => answers[LINEAR_ASSIGNEE_CHOICE_FIELD_ID] === "other"
|
|
19445
|
+
};
|
|
19446
|
+
QUICK_FIELDS = [
|
|
19447
|
+
PROJECT_NAME,
|
|
19448
|
+
LINEAR_TEAM,
|
|
19449
|
+
REPO_LINK,
|
|
19450
|
+
LINEAR_ASSIGNEE_CHOICE,
|
|
19451
|
+
LINEAR_ASSIGNEE_VALUE
|
|
19452
|
+
];
|
|
19453
|
+
HIDDEN_FIELD_IDS = new Set([
|
|
19454
|
+
"appendPrompt",
|
|
19455
|
+
"metaPrompt.enabled",
|
|
19456
|
+
"metaPrompt.effort",
|
|
19457
|
+
"logRawStream",
|
|
19458
|
+
"maxConsecutiveFailuresPerTask",
|
|
19459
|
+
"prDraft",
|
|
19460
|
+
"manualMergeWhenAutoMergeDisabled",
|
|
19461
|
+
"finalizeNoOpAsDone",
|
|
19462
|
+
"linear.confirmationMode.maxConfirmationRounds",
|
|
19463
|
+
"openspec.reviewPhase.enabled"
|
|
19464
|
+
]);
|
|
19465
|
+
CUSTOMIZED_FIELDS = [
|
|
19466
|
+
PROJECT_NAME,
|
|
19467
|
+
{
|
|
19468
|
+
id: "project.language",
|
|
19469
|
+
label: "Language",
|
|
19470
|
+
description: "Primary programming language (e.g. TypeScript). Added to the agent's prompt as context.",
|
|
19471
|
+
spec: { kind: "text", placeholder: "TypeScript" }
|
|
19472
|
+
},
|
|
19473
|
+
{
|
|
19474
|
+
id: "project.framework",
|
|
19475
|
+
label: "Framework",
|
|
19476
|
+
description: "Primary framework or toolchain (e.g. Bun + Nx). Added to the agent's prompt as context.",
|
|
19477
|
+
spec: { kind: "text", placeholder: "Bun + Nx" }
|
|
19478
|
+
},
|
|
19479
|
+
{
|
|
19480
|
+
id: "commands.test",
|
|
19481
|
+
label: "Test command",
|
|
19482
|
+
description: "Shell command Ralphy runs to check the agent's work each iteration; its exit code decides pass or fail.",
|
|
19483
|
+
spec: { kind: "text", placeholder: "bun test" }
|
|
19484
|
+
},
|
|
19485
|
+
{
|
|
19486
|
+
id: "commands.lint",
|
|
19487
|
+
label: "Lint command",
|
|
19488
|
+
description: "Shell command Ralphy runs to lint the code before a task is allowed to finish.",
|
|
19489
|
+
spec: { kind: "text", placeholder: "bun run lint" }
|
|
19490
|
+
},
|
|
19491
|
+
{
|
|
19492
|
+
id: "commands.build",
|
|
19493
|
+
label: "Build command",
|
|
19494
|
+
description: "Shell command Ralphy runs to confirm the project still compiles / builds.",
|
|
19495
|
+
spec: { kind: "text", placeholder: "bun run build" }
|
|
19496
|
+
},
|
|
19497
|
+
{
|
|
19498
|
+
id: "commands.typecheck",
|
|
19499
|
+
label: "Typecheck command",
|
|
19500
|
+
description: "Shell command Ralphy runs to confirm the project's types still pass.",
|
|
19501
|
+
spec: { kind: "text", placeholder: "bun run typecheck" }
|
|
19502
|
+
},
|
|
19503
|
+
{
|
|
19504
|
+
id: "engine",
|
|
19505
|
+
label: "Engine",
|
|
19506
|
+
description: "Which AI coding tool runs the loop: 'claude' (Claude Code) or 'codex' (OpenAI Codex).",
|
|
19507
|
+
spec: {
|
|
19508
|
+
kind: "select",
|
|
19509
|
+
options: [
|
|
19510
|
+
{ label: "claude", value: "claude" },
|
|
19511
|
+
{ label: "codex", value: "codex" }
|
|
19512
|
+
]
|
|
19513
|
+
}
|
|
19514
|
+
},
|
|
19515
|
+
{
|
|
19516
|
+
id: "model",
|
|
19517
|
+
label: "Model tier",
|
|
19518
|
+
description: "Model tier the engine uses. 'opus' is the most capable, 'haiku' the cheapest and fastest; higher tiers cost more per token.",
|
|
19519
|
+
spec: {
|
|
19520
|
+
kind: "select",
|
|
19521
|
+
options: [
|
|
19522
|
+
{ label: "opus", value: "opus" },
|
|
19523
|
+
{ label: "sonnet", value: "sonnet" },
|
|
19524
|
+
{ label: "haiku", value: "haiku" }
|
|
19525
|
+
]
|
|
19526
|
+
}
|
|
19527
|
+
},
|
|
19528
|
+
{
|
|
19529
|
+
id: "logRawStream",
|
|
19530
|
+
label: "Log the raw engine stream to stdout?",
|
|
19531
|
+
description: "Print the engine's raw event stream to the terminal. Very verbose \u2014 mainly for debugging.",
|
|
19532
|
+
spec: no()
|
|
19533
|
+
},
|
|
19534
|
+
{
|
|
19535
|
+
id: "taskVerbose",
|
|
19536
|
+
label: "Show detailed task output?",
|
|
19537
|
+
description: "Show detailed per-task output (passes --verbose to the task sub-process) for extra diagnostics.",
|
|
19538
|
+
spec: no()
|
|
19539
|
+
},
|
|
19540
|
+
{
|
|
19541
|
+
id: "concurrency",
|
|
19542
|
+
label: "Concurrency (parallel tasks)",
|
|
19543
|
+
description: "How many tasks Ralphy works on at once. Higher finishes faster but uses more API quota simultaneously.",
|
|
19544
|
+
spec: { kind: "number", placeholder: "1" }
|
|
19545
|
+
},
|
|
19546
|
+
{
|
|
19547
|
+
id: "pollIntervalSeconds",
|
|
19548
|
+
label: "Poll interval (seconds)",
|
|
19549
|
+
description: "In agent mode, how often (in seconds) Ralphy checks Linear for new issues to pick up.",
|
|
19550
|
+
spec: { kind: "number", placeholder: "60" }
|
|
19551
|
+
},
|
|
19552
|
+
{
|
|
19553
|
+
id: "iterationDelaySeconds",
|
|
19554
|
+
label: "Delay between iterations (seconds)",
|
|
19555
|
+
description: "Seconds to pause between loop iterations \u2014 a throttle to slow spend. 0 means no pause.",
|
|
19556
|
+
spec: { kind: "number", placeholder: "0" }
|
|
19557
|
+
},
|
|
19558
|
+
{
|
|
19559
|
+
id: "maxIterationsPerTask",
|
|
19560
|
+
label: "Max iterations per task (0 = unlimited)",
|
|
19561
|
+
description: "Stop a task after this many loop iterations. 0 means no limit (run until done or another limit hits).",
|
|
19562
|
+
spec: { kind: "number", placeholder: "0" }
|
|
19563
|
+
},
|
|
19564
|
+
{
|
|
19565
|
+
id: "maxCostUsdPerTask",
|
|
19566
|
+
label: "Max cost USD per task (0 = unlimited)",
|
|
19567
|
+
description: "Stop a task once its API spend passes this many US dollars. 0 means no cost limit.",
|
|
19568
|
+
spec: { kind: "number", placeholder: "0" }
|
|
19569
|
+
},
|
|
19570
|
+
{
|
|
19571
|
+
id: "maxRuntimeMinutesPerTask",
|
|
19572
|
+
label: "Max runtime minutes per task (0 = unlimited)",
|
|
19573
|
+
description: "Stop a task after this many minutes of wall-clock time. 0 means no time limit.",
|
|
19574
|
+
spec: { kind: "number", placeholder: "0" }
|
|
19575
|
+
},
|
|
19576
|
+
{
|
|
19577
|
+
id: "maxConsecutiveFailuresPerTask",
|
|
19578
|
+
label: "Max consecutive identical failures",
|
|
19579
|
+
description: "Give up on a task after this many identical failures in a row \u2014 a guard against stuck loops.",
|
|
19580
|
+
spec: { kind: "number", placeholder: "5" }
|
|
19581
|
+
},
|
|
19582
|
+
{
|
|
19583
|
+
id: "useWorktree",
|
|
19584
|
+
label: "Run each task in an isolated git worktree?",
|
|
19585
|
+
description: "Run each task in its own git worktree (a separate working copy of the repo) so parallel tasks don't overwrite each other's files. Forced on when concurrency is greater than 1.",
|
|
19586
|
+
spec: no(),
|
|
19587
|
+
when: (answers) => !concurrencyForcesWorktree(answers)
|
|
19588
|
+
},
|
|
19589
|
+
{
|
|
19590
|
+
id: "cleanupWorktreeOnSuccess",
|
|
19591
|
+
label: "Delete the worktree after a successful task?",
|
|
19592
|
+
description: "Delete a task's worktree (its separate working copy) once it succeeds, to reclaim disk space.",
|
|
19593
|
+
spec: no(),
|
|
19594
|
+
when: worktreeEnabled
|
|
19595
|
+
},
|
|
19596
|
+
{
|
|
19597
|
+
id: "setupScript",
|
|
19598
|
+
label: "Worktree setup script (runs before each task)",
|
|
19599
|
+
description: "Part of the worktree flow: a shell script run once in each task's fresh worktree before the task starts \u2014 e.g. to install dependencies in the new working copy.",
|
|
19600
|
+
spec: { kind: "text" },
|
|
19601
|
+
when: worktreeEnabled
|
|
19602
|
+
},
|
|
19603
|
+
{
|
|
19604
|
+
id: "teardownScript",
|
|
19605
|
+
label: "Worktree teardown script (runs after each task)",
|
|
19606
|
+
description: "Part of the worktree flow: a shell script run once in each task's worktree after the task ends \u2014 e.g. to clean up before the worktree is removed.",
|
|
19607
|
+
spec: { kind: "text" },
|
|
19608
|
+
when: worktreeEnabled
|
|
19609
|
+
},
|
|
19610
|
+
{
|
|
19611
|
+
id: "enableManualTest",
|
|
19612
|
+
label: "Enable the manual-test phase?",
|
|
19613
|
+
description: "Add a phase that pauses for a human to manually test the change (e.g. in the UI) before the task is marked done.",
|
|
19614
|
+
spec: no()
|
|
19615
|
+
},
|
|
19616
|
+
{
|
|
19617
|
+
id: "appendPrompt",
|
|
19618
|
+
label: "Extra text appended to every prompt",
|
|
19619
|
+
description: "Free text added to the end of every prompt sent to the agent \u2014 house rules or reminders.",
|
|
19620
|
+
spec: { kind: "text" }
|
|
19621
|
+
},
|
|
19622
|
+
{
|
|
19623
|
+
id: "createPrOnSuccess",
|
|
19624
|
+
label: "Open a pull request when a task succeeds?",
|
|
19625
|
+
description: "When a task succeeds, automatically push the branch and open a GitHub pull request (PR).",
|
|
19626
|
+
spec: no()
|
|
19627
|
+
},
|
|
19628
|
+
{
|
|
19629
|
+
id: "prDraft",
|
|
19630
|
+
label: "Open pull requests as drafts?",
|
|
19631
|
+
description: "Open PRs as drafts (marked not-ready-for-review) instead of ready for review.",
|
|
19632
|
+
spec: no(),
|
|
19633
|
+
when: isOn("createPrOnSuccess")
|
|
19634
|
+
},
|
|
19635
|
+
{
|
|
19636
|
+
id: "prBaseBranch",
|
|
19637
|
+
label: "PR base branch",
|
|
19638
|
+
description: "The branch new pull requests merge into (their base) \u2014 e.g. main.",
|
|
19639
|
+
spec: { kind: "text", placeholder: "main" },
|
|
19640
|
+
when: isOn("createPrOnSuccess")
|
|
19641
|
+
},
|
|
19642
|
+
{
|
|
19643
|
+
id: "stackPrsOnDependencies",
|
|
19644
|
+
label: "Stack dependent issues' PRs onto their blocker's PR?",
|
|
19645
|
+
description: "If an issue is blocked by another that already has an open PR, base this issue's PR on that PR's branch instead of main (a 'stacked' PR).",
|
|
19646
|
+
spec: no(),
|
|
19647
|
+
when: isOn("createPrOnSuccess")
|
|
19648
|
+
},
|
|
19649
|
+
{
|
|
19650
|
+
id: "autoMergeStrategy",
|
|
19651
|
+
label: "Auto-merge strategy",
|
|
19652
|
+
description: "How GitHub combines the PR's commits when it auto-merges: squash (one commit), merge (a merge commit), or rebase.",
|
|
19653
|
+
spec: {
|
|
19654
|
+
kind: "select",
|
|
19655
|
+
options: [
|
|
19656
|
+
{ label: "squash", value: "squash" },
|
|
19657
|
+
{ label: "merge", value: "merge" },
|
|
19658
|
+
{ label: "rebase", value: "rebase" }
|
|
19659
|
+
]
|
|
19660
|
+
},
|
|
19661
|
+
when: isOn("createPrOnSuccess")
|
|
19662
|
+
},
|
|
19663
|
+
{
|
|
19664
|
+
id: "manualMergeWhenAutoMergeDisabled",
|
|
19665
|
+
label: "Merge manually when GitHub auto-merge is disabled?",
|
|
19666
|
+
description: "If the repo doesn't have GitHub's auto-merge feature enabled, have Ralphy merge the PR itself once checks pass.",
|
|
19667
|
+
spec: yes(),
|
|
19668
|
+
when: isOn("createPrOnSuccess")
|
|
19669
|
+
},
|
|
19670
|
+
{
|
|
19671
|
+
id: "finalizeNoOpAsDone",
|
|
19672
|
+
label: "Finalize a no-op (meta-only) change as done?",
|
|
19673
|
+
description: "If a change ended up touching only meta files (specs, task lists) and no real code, mark the issue done instead of retrying it.",
|
|
19674
|
+
spec: yes()
|
|
19675
|
+
},
|
|
19676
|
+
{
|
|
19677
|
+
id: "fixCiOnFailure",
|
|
19678
|
+
label: "Let the agent fix CI failures?",
|
|
19679
|
+
description: "After opening a PR, watch its CI (the automated checks GitHub runs) and let the agent push fixes when they fail.",
|
|
19680
|
+
spec: no()
|
|
19681
|
+
},
|
|
19682
|
+
{
|
|
19683
|
+
id: "maxCiFixAttempts",
|
|
19684
|
+
label: "Max CI-fix attempts per task",
|
|
19685
|
+
description: "Stop trying to fix failing CI after this many attempts.",
|
|
19686
|
+
spec: { kind: "number", placeholder: "5" },
|
|
19687
|
+
when: isOn("fixCiOnFailure")
|
|
19688
|
+
},
|
|
19689
|
+
{
|
|
19690
|
+
id: "ciPollIntervalSeconds",
|
|
19691
|
+
label: "CI status poll interval (seconds)",
|
|
19692
|
+
description: "How often (in seconds) to re-check the PR's CI status while waiting on or fixing it.",
|
|
19693
|
+
spec: { kind: "number", placeholder: "30" },
|
|
19694
|
+
when: isOn("fixCiOnFailure")
|
|
19695
|
+
},
|
|
19696
|
+
{
|
|
19697
|
+
id: "ignoreCiChecks",
|
|
19698
|
+
label: "CI checks to ignore",
|
|
19699
|
+
description: "Names of CI checks to ignore when deciding whether a PR is green \u2014 e.g. known-flaky jobs.",
|
|
19700
|
+
spec: { kind: "list", placeholder: "check name" }
|
|
19701
|
+
},
|
|
19702
|
+
{
|
|
19703
|
+
id: "rules",
|
|
19704
|
+
label: "Project rules",
|
|
19705
|
+
description: "House rules added to every prompt (e.g. 'never edit generated files'). One rule per entry.",
|
|
19706
|
+
spec: { kind: "list", placeholder: "a rule" }
|
|
19707
|
+
},
|
|
19708
|
+
{
|
|
19709
|
+
id: "boundaries.never_touch",
|
|
19710
|
+
label: "Never-touch globs",
|
|
19711
|
+
description: "Glob patterns for files the agent must never modify (e.g. dist/**).",
|
|
19712
|
+
spec: { kind: "list", placeholder: "dist/**" }
|
|
19713
|
+
},
|
|
19714
|
+
LINEAR_TEAM,
|
|
19715
|
+
REPO_LINK,
|
|
19716
|
+
LINEAR_ASSIGNEE_CHOICE,
|
|
19717
|
+
LINEAR_ASSIGNEE_VALUE,
|
|
19718
|
+
{
|
|
19719
|
+
id: "linear.postComments",
|
|
19720
|
+
label: "Post progress comments on the Linear issue?",
|
|
19721
|
+
description: "Post progress comments on the Linear issue while a task runs.",
|
|
19722
|
+
spec: yes()
|
|
19723
|
+
},
|
|
19724
|
+
{
|
|
19725
|
+
id: "linear.updateEveryIterations",
|
|
19726
|
+
label: "Post a progress update every N iterations (0 = off)",
|
|
19727
|
+
description: "Post a progress comment every N loop iterations. 0 turns periodic updates off.",
|
|
19728
|
+
spec: { kind: "number", placeholder: "10" }
|
|
19729
|
+
},
|
|
19730
|
+
{
|
|
19731
|
+
id: "linear.mentionTrigger",
|
|
19732
|
+
label: "Watch comments/PRs for @mentions?",
|
|
19733
|
+
description: "Watch a finished issue's comments and its PR for @mentions of Ralphy, and re-engage when mentioned.",
|
|
19734
|
+
spec: yes()
|
|
19735
|
+
},
|
|
19736
|
+
{
|
|
19737
|
+
id: "linear.mentionHandle",
|
|
19738
|
+
label: "Mention handle",
|
|
19739
|
+
description: "The @handle that, when mentioned, makes Ralphy pick the issue back up (e.g. @ralphy).",
|
|
19740
|
+
spec: { kind: "text", placeholder: "@ralphy" },
|
|
19741
|
+
when: isOn("linear.mentionTrigger")
|
|
19742
|
+
},
|
|
19743
|
+
{
|
|
19744
|
+
id: "linear.codeReviewTrigger",
|
|
19745
|
+
label: "Watch PRs for unresolved review threads?",
|
|
19746
|
+
description: "Watch open PRs for unresolved review comments and re-engage to address them.",
|
|
19747
|
+
spec: yes()
|
|
19748
|
+
},
|
|
19749
|
+
{
|
|
19750
|
+
id: "linear.codeReviewStaleHours",
|
|
19751
|
+
label: "Code-review stale window (hours)",
|
|
19752
|
+
description: "Ignore review comments older than this many hours, so stale threads don't re-trigger work.",
|
|
19753
|
+
spec: { kind: "number", placeholder: "24" },
|
|
19754
|
+
when: isOn("linear.codeReviewTrigger")
|
|
19755
|
+
},
|
|
19756
|
+
{
|
|
19757
|
+
id: "linear.syncTasksToComment",
|
|
19758
|
+
label: "Sync tasks into a sticky Linear comment?",
|
|
19759
|
+
description: "Keep one pinned ('sticky') Linear comment in sync with the task checklist (tasks.md).",
|
|
19760
|
+
spec: yes()
|
|
19761
|
+
},
|
|
19762
|
+
{
|
|
19763
|
+
id: "linear.syncSpecsAsAttachments",
|
|
19764
|
+
label: "Upload plan as attachments to the Linear ticket?",
|
|
19765
|
+
description: "Upload the OpenSpec planning docs (proposal.md, design.md) to the issue as attachments. OpenSpec is Ralphy's spec-driven planning format.",
|
|
19766
|
+
spec: yes()
|
|
19767
|
+
},
|
|
19768
|
+
{
|
|
19769
|
+
id: "linear.specAttachmentFormats",
|
|
19770
|
+
label: "Plan attachment formats",
|
|
19771
|
+
description: "Which formats to upload the spec docs in: 'md' (raw markdown), 'pdf' (a rendered PDF), or both.",
|
|
19772
|
+
spec: {
|
|
19773
|
+
kind: "multiselect",
|
|
19774
|
+
options: [
|
|
19775
|
+
{ label: "md", value: "md" },
|
|
19776
|
+
{ label: "pdf", value: "pdf" }
|
|
19777
|
+
]
|
|
19778
|
+
},
|
|
19779
|
+
when: isOn("linear.syncSpecsAsAttachments")
|
|
19780
|
+
},
|
|
19781
|
+
{
|
|
19782
|
+
id: "linear.confirmationMode.enabled",
|
|
19783
|
+
label: "Enable the human confirmation gate?",
|
|
19784
|
+
description: "Pause after the agent finishes planning and wait for a human to approve before it writes any code (a confirmation gate).",
|
|
19785
|
+
spec: no()
|
|
19786
|
+
},
|
|
19787
|
+
{
|
|
19788
|
+
id: "linear.confirmationMode.timeoutHours",
|
|
19789
|
+
label: "Confirmation timeout (hours)",
|
|
19790
|
+
description: "If no one approves or rejects within this many hours, auto-resolve the confirmation gate.",
|
|
19791
|
+
spec: { kind: "number", placeholder: "48" },
|
|
19792
|
+
when: isOn("linear.confirmationMode.enabled")
|
|
19793
|
+
},
|
|
19794
|
+
{
|
|
19795
|
+
id: "linear.confirmationMode.maxConfirmationRounds",
|
|
19796
|
+
label: "Max confirmation rounds",
|
|
19797
|
+
description: "How many times the plan can be revised and re-submitted for approval before Ralphy gives up.",
|
|
19798
|
+
spec: { kind: "number", placeholder: "3" },
|
|
19799
|
+
when: isOn("linear.confirmationMode.enabled")
|
|
19800
|
+
},
|
|
19801
|
+
{
|
|
19802
|
+
id: "linear.indicators",
|
|
19803
|
+
label: "Linear lifecycle indicators",
|
|
19804
|
+
description: "How Ralphy maps lifecycle events to Linear statuses/labels \u2014 which issues to pick up (todo) and what to set when a task is in progress, done, or errored.",
|
|
19805
|
+
spec: { kind: "indicators" }
|
|
19806
|
+
},
|
|
19807
|
+
{
|
|
19808
|
+
id: "preExistingErrorCheck.enabled",
|
|
19809
|
+
label: "Enable the base-branch health gate?",
|
|
19810
|
+
description: "Before picking up new work, run health-check commands on the base branch and pause if it's already broken, so the agent isn't blamed for pre-existing failures.",
|
|
19811
|
+
spec: no()
|
|
19812
|
+
},
|
|
19813
|
+
{
|
|
19814
|
+
id: "preExistingErrorCheck.commands",
|
|
19815
|
+
label: "Health-gate commands (blank = use lint/test)",
|
|
19816
|
+
description: "Commands run against the base branch to judge its health. Leave empty to reuse your lint/test commands.",
|
|
19817
|
+
spec: { kind: "list", placeholder: "bun run lint" },
|
|
19818
|
+
when: isOn("preExistingErrorCheck.enabled")
|
|
19819
|
+
},
|
|
19820
|
+
{
|
|
19821
|
+
id: "preExistingErrorCheck.baseBranch",
|
|
19822
|
+
label: "Health-gate base branch",
|
|
19823
|
+
description: "The branch the health gate checks out and tests (usually main).",
|
|
19824
|
+
spec: { kind: "text", placeholder: "main" },
|
|
19825
|
+
when: isOn("preExistingErrorCheck.enabled")
|
|
19826
|
+
},
|
|
19827
|
+
{
|
|
19828
|
+
id: "preExistingErrorCheck.label",
|
|
19829
|
+
label: "Health-gate Linear label",
|
|
19830
|
+
description: "Linear label applied to the ticket Ralphy opens when the base branch is found broken.",
|
|
19831
|
+
spec: { kind: "text", placeholder: "ralph:pre-existing-error" },
|
|
19832
|
+
when: isOn("preExistingErrorCheck.enabled")
|
|
19833
|
+
},
|
|
19834
|
+
{
|
|
19835
|
+
id: "prTracker.enabled",
|
|
19836
|
+
label: "Enable the PR tracker?",
|
|
19837
|
+
description: "Keep watching the PRs Ralphy opened and automatically try to recover any whose merge state goes red (conflicts or failing CI).",
|
|
19838
|
+
spec: yes()
|
|
19839
|
+
},
|
|
19840
|
+
{
|
|
19841
|
+
id: "prTracker.maxRecoveryAttempts",
|
|
19842
|
+
label: "PR tracker max recovery attempts",
|
|
19843
|
+
description: "Give up auto-recovering a red PR after this many attempts, then flag it for a human.",
|
|
19844
|
+
spec: { kind: "number", placeholder: "3" },
|
|
19845
|
+
when: isOn("prTracker.enabled")
|
|
19846
|
+
},
|
|
19847
|
+
{
|
|
19848
|
+
id: "prTracker.advanceMergedToDone",
|
|
19849
|
+
label: "Advance merged PRs to done automatically?",
|
|
19850
|
+
description: "Move an issue to its done state as soon as its PR is merged.",
|
|
19851
|
+
spec: no(),
|
|
19852
|
+
when: isOn("prTracker.enabled")
|
|
19853
|
+
},
|
|
19854
|
+
{
|
|
19855
|
+
id: "metaPrompt.enabled",
|
|
19856
|
+
label: "Enable the meta-prompt addendum?",
|
|
19857
|
+
description: "Add Ralphy's task-level 'meta-prompt' layer (extra framing instructions) to each phase. Leave on unless you want raw prompts.",
|
|
19858
|
+
spec: yes()
|
|
19859
|
+
},
|
|
19860
|
+
{
|
|
19861
|
+
id: "metaPrompt.effort",
|
|
19862
|
+
label: "Per-ticket effort tier",
|
|
19863
|
+
description: "How much effort the meta-prompt nudges the agent toward per ticket. 'auto' detects it from the ticket; 'light'/'standard'/'heavy' pin every ticket to that tier.",
|
|
19864
|
+
spec: {
|
|
19865
|
+
kind: "select",
|
|
19866
|
+
options: [
|
|
19867
|
+
{ label: "auto", value: "auto" },
|
|
19868
|
+
{ label: "light", value: "light" },
|
|
19869
|
+
{ label: "standard", value: "standard" },
|
|
19870
|
+
{ label: "heavy", value: "heavy" }
|
|
19871
|
+
]
|
|
19872
|
+
},
|
|
19873
|
+
when: isOn("metaPrompt.enabled")
|
|
19874
|
+
},
|
|
19875
|
+
{
|
|
19876
|
+
id: "openspec.reviewPhase.enabled",
|
|
19877
|
+
label: "Enable the OpenSpec review phase?",
|
|
19878
|
+
description: "After all tasks finish, spawn a separate reviewer agent that reads the full diff and writes review findings; open findings loop back into more work.",
|
|
19879
|
+
spec: no()
|
|
19880
|
+
},
|
|
19881
|
+
{
|
|
19882
|
+
id: "openspec.reviewPhase.maxRounds",
|
|
19883
|
+
label: "Review phase max rounds",
|
|
19884
|
+
description: "How many review\u2192fix cycles to run before the change is archived regardless.",
|
|
19885
|
+
spec: { kind: "number", placeholder: "1" },
|
|
19886
|
+
when: isOn("openspec.reviewPhase.enabled")
|
|
19887
|
+
},
|
|
19888
|
+
{
|
|
19889
|
+
id: "openspec.reviewPhase.reviewerModel",
|
|
19890
|
+
label: "Reviewer model (blank = same as main)",
|
|
19891
|
+
description: "Model used for the review pass. Blank reuses the main model; a cheaper tier (e.g. haiku) saves cost.",
|
|
19892
|
+
spec: { kind: "text", placeholder: "haiku" },
|
|
19893
|
+
when: isOn("openspec.reviewPhase.enabled")
|
|
19894
|
+
},
|
|
19895
|
+
{
|
|
19896
|
+
id: "openspec.reviewPhase.reviewerContextStrategy",
|
|
19897
|
+
label: "Reviewer context",
|
|
19898
|
+
description: "'fresh' gives the reviewer a brand-new session (unbiased); 'warm' resumes the last task's session (more context, cheaper).",
|
|
19899
|
+
spec: {
|
|
19900
|
+
kind: "select",
|
|
19901
|
+
options: [
|
|
19902
|
+
{ label: "fresh", value: "fresh" },
|
|
19903
|
+
{ label: "warm", value: "warm" }
|
|
19904
|
+
]
|
|
19905
|
+
},
|
|
19906
|
+
when: isOn("openspec.reviewPhase.enabled")
|
|
19907
|
+
},
|
|
19908
|
+
{
|
|
19909
|
+
id: PROMPT_BODY_FIELD_ID,
|
|
19910
|
+
label: "Customize the prompt sent to the agent?",
|
|
19911
|
+
description: "The prompt the agent receives lives in the file body \u2014 a template filled with per-issue values (e.g. {{ issue.identifier }}). Edit it here, or leave it and finish to keep the default.",
|
|
19912
|
+
spec: { kind: "multiline" }
|
|
19913
|
+
}
|
|
19914
|
+
];
|
|
19915
|
+
COMMON_CLI_OPTIONS = [
|
|
19916
|
+
{ fieldId: "model", flag: "--model", argKey: "model", kind: "model" },
|
|
19917
|
+
{ fieldId: "iterationDelaySeconds", flag: "--delay", argKey: "delay", kind: "int" },
|
|
19918
|
+
{ fieldId: "maxCostUsdPerTask", flag: "--max-cost", argKey: "maxCostUsd", kind: "float" },
|
|
19919
|
+
{
|
|
19920
|
+
fieldId: "maxRuntimeMinutesPerTask",
|
|
19921
|
+
flag: "--max-runtime",
|
|
19922
|
+
argKey: "maxRuntimeMinutes",
|
|
19923
|
+
kind: "float"
|
|
19924
|
+
},
|
|
19925
|
+
{
|
|
19926
|
+
fieldId: "maxConsecutiveFailuresPerTask",
|
|
19927
|
+
flag: "--max-failures",
|
|
19928
|
+
argKey: "maxConsecutiveFailures",
|
|
19929
|
+
kind: "int"
|
|
19930
|
+
},
|
|
19931
|
+
{
|
|
19932
|
+
fieldId: "maxIterationsPerTask",
|
|
19933
|
+
flag: "--max-iterations",
|
|
19934
|
+
argKey: "maxIterations",
|
|
19935
|
+
kind: "int"
|
|
19936
|
+
},
|
|
19937
|
+
{ fieldId: "logRawStream", flag: "--log", argKey: "log", kind: "boolean" },
|
|
19938
|
+
{ fieldId: "taskVerbose", flag: "--verbose", argKey: "verbose", kind: "boolean" }
|
|
19939
|
+
];
|
|
19940
|
+
FIELD_DESCRIPTIONS = [
|
|
19941
|
+
...CUSTOMIZED_FIELDS.filter((field) => Boolean(field.description) && field.spec.kind !== "multiline").map((field) => ({ path: field.id.split("."), description: field.description })),
|
|
19942
|
+
{ path: ["linear", "filter"], description: LINEAR_FILTER_DESCRIPTION }
|
|
19943
|
+
];
|
|
19944
|
+
});
|
|
19945
|
+
|
|
19946
|
+
// packages/cli-args/src/common-args.ts
|
|
19947
|
+
import { resolve as resolve2 } from "path";
|
|
19948
|
+
function initialCommonArgs() {
|
|
19949
|
+
return {
|
|
19950
|
+
engine: "claude",
|
|
19951
|
+
model: "opus",
|
|
19952
|
+
engineSet: false,
|
|
19953
|
+
maxIterations: 0,
|
|
19954
|
+
maxCostUsd: 0,
|
|
19955
|
+
maxRuntimeMinutes: 0,
|
|
19956
|
+
maxConsecutiveFailures: 5,
|
|
19957
|
+
delay: 0,
|
|
19958
|
+
log: false,
|
|
19959
|
+
verbose: false,
|
|
19960
|
+
projectRoot: undefined,
|
|
19961
|
+
workflowFile: undefined,
|
|
19962
|
+
name: "",
|
|
19963
|
+
prompt: "",
|
|
19964
|
+
fromAgent: false
|
|
19965
|
+
};
|
|
19966
|
+
}
|
|
19967
|
+
function applyValueOption(option, args, raw) {
|
|
19968
|
+
const setter = VALUE_SETTERS[option.argKey];
|
|
19969
|
+
if (!setter)
|
|
19970
|
+
throw new Error("no value setter registered for CLI option");
|
|
19971
|
+
setter(args, raw);
|
|
19972
|
+
}
|
|
19973
|
+
function applyBooleanOption(option, args) {
|
|
19974
|
+
const setter = BOOLEAN_SETTERS[option.argKey];
|
|
19975
|
+
if (!setter)
|
|
19976
|
+
throw new Error("no boolean setter registered for CLI option");
|
|
19977
|
+
setter(args);
|
|
19978
|
+
}
|
|
19979
|
+
function emptyParseState() {
|
|
19980
|
+
return {
|
|
19981
|
+
pendingOption: null,
|
|
19982
|
+
expectClaudeModel: false,
|
|
19983
|
+
expectProjectRoot: false,
|
|
19984
|
+
expectWorkflow: false,
|
|
19985
|
+
expectName: false,
|
|
19986
|
+
expectPrompt: false,
|
|
19987
|
+
expectPromptFile: false,
|
|
19988
|
+
promptFilePath: null,
|
|
19989
|
+
workflowFileRaw: null
|
|
19990
|
+
};
|
|
19991
|
+
}
|
|
19992
|
+
function parseCommonArg(arg, args, state) {
|
|
19993
|
+
if (state.pendingOption) {
|
|
19994
|
+
applyValueOption(state.pendingOption, args, arg);
|
|
19995
|
+
state.pendingOption = null;
|
|
19996
|
+
return true;
|
|
19997
|
+
}
|
|
19998
|
+
if (state.expectClaudeModel) {
|
|
19999
|
+
state.expectClaudeModel = false;
|
|
20000
|
+
if (VALID_MODELS.has(arg)) {
|
|
20001
|
+
args.model = arg;
|
|
20002
|
+
return true;
|
|
20003
|
+
}
|
|
20004
|
+
}
|
|
20005
|
+
if (state.expectProjectRoot) {
|
|
20006
|
+
args.projectRoot = arg;
|
|
20007
|
+
state.expectProjectRoot = false;
|
|
20008
|
+
return true;
|
|
20009
|
+
}
|
|
20010
|
+
if (state.expectWorkflow) {
|
|
20011
|
+
state.workflowFileRaw = arg;
|
|
20012
|
+
args.workflowFile = resolve2(arg);
|
|
20013
|
+
state.expectWorkflow = false;
|
|
20014
|
+
return true;
|
|
20015
|
+
}
|
|
20016
|
+
if (state.expectName) {
|
|
20017
|
+
args.name = arg;
|
|
20018
|
+
state.expectName = false;
|
|
20019
|
+
return true;
|
|
20020
|
+
}
|
|
20021
|
+
if (state.expectPrompt) {
|
|
20022
|
+
args.prompt = arg;
|
|
20023
|
+
state.promptFilePath = null;
|
|
20024
|
+
state.expectPrompt = false;
|
|
20025
|
+
return true;
|
|
20026
|
+
}
|
|
20027
|
+
if (state.expectPromptFile) {
|
|
20028
|
+
state.promptFilePath = arg;
|
|
20029
|
+
state.expectPromptFile = false;
|
|
20030
|
+
return true;
|
|
20031
|
+
}
|
|
20032
|
+
const option = OPTION_BY_FLAG.get(arg);
|
|
20033
|
+
if (option) {
|
|
20034
|
+
if (option.kind === "boolean")
|
|
20035
|
+
applyBooleanOption(option, args);
|
|
20036
|
+
else
|
|
20037
|
+
state.pendingOption = option;
|
|
20038
|
+
return true;
|
|
20039
|
+
}
|
|
20040
|
+
switch (arg) {
|
|
20041
|
+
case "--claude":
|
|
20042
|
+
if (args.engineSet && args.engine !== "claude") {
|
|
20043
|
+
throw new Error("Choose only one engine flag: --claude or --codex");
|
|
20044
|
+
}
|
|
20045
|
+
args.engine = "claude";
|
|
20046
|
+
args.engineSet = true;
|
|
20047
|
+
state.expectClaudeModel = true;
|
|
20048
|
+
return true;
|
|
20049
|
+
case "--codex":
|
|
20050
|
+
if (args.engineSet && args.engine !== "codex") {
|
|
20051
|
+
throw new Error("Choose only one engine flag: --claude or --codex");
|
|
20052
|
+
}
|
|
20053
|
+
args.engine = "codex";
|
|
20054
|
+
args.engineSet = true;
|
|
20055
|
+
return true;
|
|
20056
|
+
case "--unlimited":
|
|
20057
|
+
args.maxIterations = 0;
|
|
20058
|
+
return true;
|
|
20059
|
+
case "--project-root":
|
|
20060
|
+
state.expectProjectRoot = true;
|
|
20061
|
+
return true;
|
|
20062
|
+
case "--workflow":
|
|
20063
|
+
state.expectWorkflow = true;
|
|
20064
|
+
return true;
|
|
20065
|
+
case "--name":
|
|
20066
|
+
state.expectName = true;
|
|
20067
|
+
return true;
|
|
20068
|
+
case "--prompt":
|
|
20069
|
+
state.expectPrompt = true;
|
|
20070
|
+
return true;
|
|
20071
|
+
case "--prompt-file":
|
|
20072
|
+
state.expectPromptFile = true;
|
|
20073
|
+
return true;
|
|
20074
|
+
case "--from-agent":
|
|
20075
|
+
args.fromAgent = true;
|
|
20076
|
+
return true;
|
|
20077
|
+
default:
|
|
20078
|
+
return false;
|
|
20079
|
+
}
|
|
20080
|
+
}
|
|
20081
|
+
async function resolvePromptFile(args, state) {
|
|
20082
|
+
if (state.promptFilePath !== null) {
|
|
20083
|
+
args.prompt = await Bun.file(state.promptFilePath).text();
|
|
20084
|
+
}
|
|
20085
|
+
}
|
|
20086
|
+
function resolveWorkflowFile(args, state) {
|
|
20087
|
+
if (state.workflowFileRaw !== null && args.projectRoot !== undefined) {
|
|
20088
|
+
args.workflowFile = resolve2(args.projectRoot, state.workflowFileRaw);
|
|
20089
|
+
}
|
|
20090
|
+
}
|
|
20091
|
+
function parseWorkflowPathArgs(argv) {
|
|
20092
|
+
const args = initialCommonArgs();
|
|
20093
|
+
const state = emptyParseState();
|
|
20094
|
+
for (const token of argv)
|
|
20095
|
+
parseCommonArg(token, args, state);
|
|
20096
|
+
resolveWorkflowFile(args, state);
|
|
20097
|
+
return { projectRoot: args.projectRoot, workflowFile: args.workflowFile };
|
|
20098
|
+
}
|
|
20099
|
+
var VALID_MODELS, OPTION_BY_FLAG, VALUE_FLAGS, VALUE_SETTERS, BOOLEAN_SETTERS;
|
|
20100
|
+
var init_common_args = __esm(() => {
|
|
20101
|
+
init_fields();
|
|
20102
|
+
VALID_MODELS = new Set(modelOptionValues());
|
|
20103
|
+
OPTION_BY_FLAG = new Map(COMMON_CLI_OPTIONS.map((option) => [option.flag, option]));
|
|
20104
|
+
VALUE_FLAGS = new Set(COMMON_CLI_OPTIONS.filter((option) => option.kind !== "boolean").map((option) => option.flag));
|
|
20105
|
+
VALUE_SETTERS = {
|
|
20106
|
+
model: (args, raw) => {
|
|
20107
|
+
if (!VALID_MODELS.has(raw))
|
|
20108
|
+
throw new Error("Invalid model");
|
|
20109
|
+
args.model = raw;
|
|
20110
|
+
},
|
|
20111
|
+
delay: (args, raw) => {
|
|
20112
|
+
args.delay = parseInt(raw, 10);
|
|
20113
|
+
},
|
|
20114
|
+
maxCostUsd: (args, raw) => {
|
|
20115
|
+
args.maxCostUsd = parseFloat(raw);
|
|
20116
|
+
},
|
|
20117
|
+
maxRuntimeMinutes: (args, raw) => {
|
|
20118
|
+
args.maxRuntimeMinutes = parseFloat(raw);
|
|
20119
|
+
},
|
|
20120
|
+
maxConsecutiveFailures: (args, raw) => {
|
|
20121
|
+
args.maxConsecutiveFailures = parseInt(raw, 10);
|
|
20122
|
+
},
|
|
20123
|
+
maxIterations: (args, raw) => {
|
|
20124
|
+
args.maxIterations = parseInt(raw, 10);
|
|
20125
|
+
}
|
|
20126
|
+
};
|
|
20127
|
+
BOOLEAN_SETTERS = {
|
|
20128
|
+
log: (args) => {
|
|
20129
|
+
args.log = true;
|
|
20130
|
+
},
|
|
20131
|
+
verbose: (args) => {
|
|
20132
|
+
args.verbose = true;
|
|
20133
|
+
}
|
|
20134
|
+
};
|
|
20135
|
+
});
|
|
20136
|
+
|
|
19379
20137
|
// node_modules/.bun/react@18.3.1/node_modules/react/cjs/react.development.js
|
|
19380
20138
|
var require_react_development = __commonJS((exports, module) => {
|
|
19381
20139
|
if (true) {
|
|
@@ -21016,14 +21774,14 @@ Check the top-level render call using <` + parentName + ">.";
|
|
|
21016
21774
|
var thenableResult = result;
|
|
21017
21775
|
var wasAwaited = false;
|
|
21018
21776
|
var thenable = {
|
|
21019
|
-
then: function(
|
|
21777
|
+
then: function(resolve3, reject) {
|
|
21020
21778
|
wasAwaited = true;
|
|
21021
21779
|
thenableResult.then(function(returnValue2) {
|
|
21022
21780
|
popActScope(prevActScopeDepth);
|
|
21023
21781
|
if (actScopeDepth === 0) {
|
|
21024
|
-
recursivelyFlushAsyncActWork(returnValue2,
|
|
21782
|
+
recursivelyFlushAsyncActWork(returnValue2, resolve3, reject);
|
|
21025
21783
|
} else {
|
|
21026
|
-
|
|
21784
|
+
resolve3(returnValue2);
|
|
21027
21785
|
}
|
|
21028
21786
|
}, function(error2) {
|
|
21029
21787
|
popActScope(prevActScopeDepth);
|
|
@@ -21052,20 +21810,20 @@ Check the top-level render call using <` + parentName + ">.";
|
|
|
21052
21810
|
ReactCurrentActQueue.current = null;
|
|
21053
21811
|
}
|
|
21054
21812
|
var _thenable = {
|
|
21055
|
-
then: function(
|
|
21813
|
+
then: function(resolve3, reject) {
|
|
21056
21814
|
if (ReactCurrentActQueue.current === null) {
|
|
21057
21815
|
ReactCurrentActQueue.current = [];
|
|
21058
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
21816
|
+
recursivelyFlushAsyncActWork(returnValue, resolve3, reject);
|
|
21059
21817
|
} else {
|
|
21060
|
-
|
|
21818
|
+
resolve3(returnValue);
|
|
21061
21819
|
}
|
|
21062
21820
|
}
|
|
21063
21821
|
};
|
|
21064
21822
|
return _thenable;
|
|
21065
21823
|
} else {
|
|
21066
21824
|
var _thenable2 = {
|
|
21067
|
-
then: function(
|
|
21068
|
-
|
|
21825
|
+
then: function(resolve3, reject) {
|
|
21826
|
+
resolve3(returnValue);
|
|
21069
21827
|
}
|
|
21070
21828
|
};
|
|
21071
21829
|
return _thenable2;
|
|
@@ -21081,7 +21839,7 @@ Check the top-level render call using <` + parentName + ">.";
|
|
|
21081
21839
|
actScopeDepth = prevActScopeDepth;
|
|
21082
21840
|
}
|
|
21083
21841
|
}
|
|
21084
|
-
function recursivelyFlushAsyncActWork(returnValue,
|
|
21842
|
+
function recursivelyFlushAsyncActWork(returnValue, resolve3, reject) {
|
|
21085
21843
|
{
|
|
21086
21844
|
var queue = ReactCurrentActQueue.current;
|
|
21087
21845
|
if (queue !== null) {
|
|
@@ -21090,16 +21848,16 @@ Check the top-level render call using <` + parentName + ">.";
|
|
|
21090
21848
|
enqueueTask(function() {
|
|
21091
21849
|
if (queue.length === 0) {
|
|
21092
21850
|
ReactCurrentActQueue.current = null;
|
|
21093
|
-
|
|
21851
|
+
resolve3(returnValue);
|
|
21094
21852
|
} else {
|
|
21095
|
-
recursivelyFlushAsyncActWork(returnValue,
|
|
21853
|
+
recursivelyFlushAsyncActWork(returnValue, resolve3, reject);
|
|
21096
21854
|
}
|
|
21097
21855
|
});
|
|
21098
21856
|
} catch (error2) {
|
|
21099
21857
|
reject(error2);
|
|
21100
21858
|
}
|
|
21101
21859
|
} else {
|
|
21102
|
-
|
|
21860
|
+
resolve3(returnValue);
|
|
21103
21861
|
}
|
|
21104
21862
|
}
|
|
21105
21863
|
}
|
|
@@ -59172,8 +59930,8 @@ class Ink {
|
|
|
59172
59930
|
}
|
|
59173
59931
|
}
|
|
59174
59932
|
async waitUntilExit() {
|
|
59175
|
-
this.exitPromise ||= new Promise((
|
|
59176
|
-
this.resolveExitPromise =
|
|
59933
|
+
this.exitPromise ||= new Promise((resolve3, reject2) => {
|
|
59934
|
+
this.resolveExitPromise = resolve3;
|
|
59177
59935
|
this.rejectExitPromise = reject2;
|
|
59178
59936
|
});
|
|
59179
59937
|
return this.exitPromise;
|
|
@@ -59633,19 +60391,23 @@ var init_build2 = __esm(async () => {
|
|
|
59633
60391
|
// packages/paths/src/paths.ts
|
|
59634
60392
|
import { exists } from "fs/promises";
|
|
59635
60393
|
import { homedir as homedir3 } from "os";
|
|
59636
|
-
import { basename, join as join4, resolve as
|
|
59637
|
-
async function findProjectRoot() {
|
|
59638
|
-
let dir =
|
|
60394
|
+
import { basename, join as join4, resolve as resolve3 } from "path";
|
|
60395
|
+
async function findProjectRoot(startDir = process.cwd()) {
|
|
60396
|
+
let dir = startDir;
|
|
59639
60397
|
while (dir !== "/") {
|
|
59640
|
-
if (await exists(join4(dir,
|
|
60398
|
+
if (await exists(join4(dir, ROOT_MARKER)))
|
|
59641
60399
|
return dir;
|
|
59642
|
-
dir =
|
|
60400
|
+
dir = resolve3(dir, "..");
|
|
59643
60401
|
}
|
|
59644
|
-
return
|
|
60402
|
+
return startDir;
|
|
59645
60403
|
}
|
|
59646
60404
|
function worktreesDir(projectRoot) {
|
|
59647
60405
|
return join4(homedir3(), ".ralph", basename(projectRoot), "worktrees");
|
|
59648
60406
|
}
|
|
60407
|
+
function setupBackupPath() {
|
|
60408
|
+
return join4(homedir3(), ".ralph", "setup.tmp");
|
|
60409
|
+
}
|
|
60410
|
+
var ROOT_MARKER = "WORKFLOW.md";
|
|
59649
60411
|
var init_paths = () => {};
|
|
59650
60412
|
|
|
59651
60413
|
// node_modules/.bun/yaml@2.9.0/node_modules/yaml/dist/nodes/identity.js
|
|
@@ -80635,7 +81397,7 @@ var init_schema = __esm(() => {
|
|
|
80635
81397
|
maxRuntimeMinutesPerTask: exports_external.number().nonnegative().default(0),
|
|
80636
81398
|
maxConsecutiveFailuresPerTask: exports_external.number().int().nonnegative().default(5),
|
|
80637
81399
|
iterationDelaySeconds: exports_external.number().int().nonnegative().default(0),
|
|
80638
|
-
logRawStream: exports_external.boolean().default(
|
|
81400
|
+
logRawStream: exports_external.boolean().default(true),
|
|
80639
81401
|
taskVerbose: exports_external.boolean().default(false),
|
|
80640
81402
|
enableManualTest: exports_external.boolean().default(false),
|
|
80641
81403
|
useWorktree: exports_external.boolean().default(false),
|
|
@@ -80809,13 +81571,14 @@ maxConsecutiveFailuresPerTask: 5
|
|
|
80809
81571
|
|
|
80810
81572
|
engine: claude
|
|
80811
81573
|
model: opus
|
|
80812
|
-
logRawStream:
|
|
81574
|
+
logRawStream: true
|
|
80813
81575
|
taskVerbose: false
|
|
80814
81576
|
|
|
80815
81577
|
useWorktree: false
|
|
80816
81578
|
cleanupWorktreeOnSuccess: false
|
|
80817
81579
|
|
|
80818
81580
|
createPrOnSuccess: false
|
|
81581
|
+
prDraft: true
|
|
80819
81582
|
prBaseBranch: main
|
|
80820
81583
|
stackPrsOnDependencies: false
|
|
80821
81584
|
autoMergeStrategy: squash
|
|
@@ -80868,6 +81631,10 @@ linear:
|
|
|
80868
81631
|
# filter:
|
|
80869
81632
|
# - type: label
|
|
80870
81633
|
# value: "ralph:auto-merge"
|
|
81634
|
+
|
|
81635
|
+
openspec:
|
|
81636
|
+
reviewPhase:
|
|
81637
|
+
enabled: true
|
|
80871
81638
|
---
|
|
80872
81639
|
You are working on {{ issue.identifier }}: {{ issue.title }}.
|
|
80873
81640
|
|
|
@@ -81065,537 +81832,6 @@ function renderTemplate(src, ctx) {
|
|
|
81065
81832
|
return renderNodes(tree, ctx);
|
|
81066
81833
|
}
|
|
81067
81834
|
|
|
81068
|
-
// packages/workflow/src/fields.ts
|
|
81069
|
-
function fieldsForMode(mode, answers = {}, restrictTo) {
|
|
81070
|
-
const all = mode === "customized" ? CUSTOMIZED_FIELDS : QUICK_FIELDS;
|
|
81071
|
-
const allowed = restrictTo ? new Set(restrictTo) : null;
|
|
81072
|
-
return all.filter((field) => {
|
|
81073
|
-
if (allowed && !allowed.has(field.id))
|
|
81074
|
-
return false;
|
|
81075
|
-
return !field.when || field.when(answers);
|
|
81076
|
-
});
|
|
81077
|
-
}
|
|
81078
|
-
function findField(id) {
|
|
81079
|
-
return CUSTOMIZED_FIELDS.find((field) => field.id === id);
|
|
81080
|
-
}
|
|
81081
|
-
function modelOptionValues() {
|
|
81082
|
-
const field = findField("model");
|
|
81083
|
-
return field && field.spec.kind === "select" ? field.spec.options.map((o) => o.value) : [];
|
|
81084
|
-
}
|
|
81085
|
-
var PROMPT_BODY_FIELD_ID = "promptBody", REPO_LINK_FIELD_ID = "repo.link", AWAITING_STATUS_FIELD_ID = "linear.confirmationMode.awaitingStatus", yes = () => ({ kind: "confirm", defaultChoice: "confirm" }), no = () => ({ kind: "confirm", defaultChoice: "cancel" }), PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_FILTER, QUICK_FIELDS, isOn = (id) => (answers) => answers[id] === true, CUSTOMIZED_FIELDS, COMMON_CLI_OPTIONS, FIELD_DESCRIPTIONS;
|
|
81086
|
-
var init_fields = __esm(() => {
|
|
81087
|
-
PROJECT_NAME = {
|
|
81088
|
-
id: "project.name",
|
|
81089
|
-
label: "Project name",
|
|
81090
|
-
description: "The project's display name. Ralphy puts it in the agent's prompt and in its logs.",
|
|
81091
|
-
spec: { kind: "text", placeholder: "my-project" }
|
|
81092
|
-
};
|
|
81093
|
-
LINEAR_TEAM = {
|
|
81094
|
-
id: "linear.team",
|
|
81095
|
-
label: "Linear team key",
|
|
81096
|
-
hint: "e.g. ENG \u2014 leave blank to match all teams",
|
|
81097
|
-
description: "The Linear team this repository is linked to, given by its key (e.g. ENG). Ralphy only picks up issues from this team. Leave blank to watch every team.",
|
|
81098
|
-
emptyLabel: "all teams",
|
|
81099
|
-
spec: { kind: "text" }
|
|
81100
|
-
};
|
|
81101
|
-
REPO_LINK = {
|
|
81102
|
-
id: "repo.link",
|
|
81103
|
-
label: "Link this repository to the team?",
|
|
81104
|
-
description: "Record the detected git repository in WORKFLOW.md and link it to the Linear team above. Confirm to adopt the detected repo; decline to leave it out.",
|
|
81105
|
-
spec: yes(),
|
|
81106
|
-
when: (answers) => typeof answers["repo.name"] === "string" && answers["repo.name"] !== ""
|
|
81107
|
-
};
|
|
81108
|
-
LINEAR_FILTER = {
|
|
81109
|
-
id: "linear.filter",
|
|
81110
|
-
label: "Linear filter",
|
|
81111
|
-
hint: "e.g. 'assignee = me', 'assignee = any', 'assignee = unassigned', or an email/user-id",
|
|
81112
|
-
description: "Global filter applied to every Linear ticket fetch. The only clause today is 'assignee = <value>', where <value> is 'me' (issues assigned to you), 'any' (regardless of assignee), 'unassigned', a Linear user id, or an email. Blank defaults to 'assignee = me'.",
|
|
81113
|
-
emptyLabel: "assignee = me",
|
|
81114
|
-
spec: { kind: "text", placeholder: "assignee = me" }
|
|
81115
|
-
};
|
|
81116
|
-
QUICK_FIELDS = [PROJECT_NAME, LINEAR_TEAM, REPO_LINK, LINEAR_FILTER];
|
|
81117
|
-
CUSTOMIZED_FIELDS = [
|
|
81118
|
-
PROJECT_NAME,
|
|
81119
|
-
{
|
|
81120
|
-
id: "project.language",
|
|
81121
|
-
label: "Language",
|
|
81122
|
-
description: "Primary programming language (e.g. TypeScript). Added to the agent's prompt as context.",
|
|
81123
|
-
spec: { kind: "text", placeholder: "TypeScript" }
|
|
81124
|
-
},
|
|
81125
|
-
{
|
|
81126
|
-
id: "project.framework",
|
|
81127
|
-
label: "Framework",
|
|
81128
|
-
description: "Primary framework or toolchain (e.g. Bun + Nx). Added to the agent's prompt as context.",
|
|
81129
|
-
spec: { kind: "text", placeholder: "Bun + Nx" }
|
|
81130
|
-
},
|
|
81131
|
-
{
|
|
81132
|
-
id: "commands.test",
|
|
81133
|
-
label: "Test command",
|
|
81134
|
-
description: "Shell command Ralphy runs to check the agent's work each iteration; its exit code decides pass or fail.",
|
|
81135
|
-
spec: { kind: "text", placeholder: "bun test" }
|
|
81136
|
-
},
|
|
81137
|
-
{
|
|
81138
|
-
id: "commands.lint",
|
|
81139
|
-
label: "Lint command",
|
|
81140
|
-
description: "Shell command Ralphy runs to lint the code before a task is allowed to finish.",
|
|
81141
|
-
spec: { kind: "text", placeholder: "bun run lint" }
|
|
81142
|
-
},
|
|
81143
|
-
{
|
|
81144
|
-
id: "commands.build",
|
|
81145
|
-
label: "Build command",
|
|
81146
|
-
description: "Shell command Ralphy runs to confirm the project still compiles / builds.",
|
|
81147
|
-
spec: { kind: "text", placeholder: "bun run build" }
|
|
81148
|
-
},
|
|
81149
|
-
{
|
|
81150
|
-
id: "commands.typecheck",
|
|
81151
|
-
label: "Typecheck command",
|
|
81152
|
-
description: "Shell command Ralphy runs to confirm the project's types still pass.",
|
|
81153
|
-
spec: { kind: "text", placeholder: "bun run typecheck" }
|
|
81154
|
-
},
|
|
81155
|
-
{
|
|
81156
|
-
id: "engine",
|
|
81157
|
-
label: "Engine",
|
|
81158
|
-
description: "Which AI coding tool runs the loop: 'claude' (Claude Code) or 'codex' (OpenAI Codex).",
|
|
81159
|
-
spec: {
|
|
81160
|
-
kind: "select",
|
|
81161
|
-
options: [
|
|
81162
|
-
{ label: "claude", value: "claude" },
|
|
81163
|
-
{ label: "codex", value: "codex" }
|
|
81164
|
-
]
|
|
81165
|
-
}
|
|
81166
|
-
},
|
|
81167
|
-
{
|
|
81168
|
-
id: "model",
|
|
81169
|
-
label: "Model tier",
|
|
81170
|
-
description: "Model tier the engine uses. 'opus' is the most capable, 'haiku' the cheapest and fastest; higher tiers cost more per token.",
|
|
81171
|
-
spec: {
|
|
81172
|
-
kind: "select",
|
|
81173
|
-
options: [
|
|
81174
|
-
{ label: "opus", value: "opus" },
|
|
81175
|
-
{ label: "sonnet", value: "sonnet" },
|
|
81176
|
-
{ label: "haiku", value: "haiku" }
|
|
81177
|
-
]
|
|
81178
|
-
}
|
|
81179
|
-
},
|
|
81180
|
-
{
|
|
81181
|
-
id: "logRawStream",
|
|
81182
|
-
label: "Log the raw engine stream to stdout?",
|
|
81183
|
-
description: "Print the engine's raw event stream to the terminal. Very verbose \u2014 mainly for debugging.",
|
|
81184
|
-
spec: no()
|
|
81185
|
-
},
|
|
81186
|
-
{
|
|
81187
|
-
id: "taskVerbose",
|
|
81188
|
-
label: "Pass --verbose to the task sub-process?",
|
|
81189
|
-
description: "Run the per-task process with --verbose for extra diagnostic output.",
|
|
81190
|
-
spec: no()
|
|
81191
|
-
},
|
|
81192
|
-
{
|
|
81193
|
-
id: "concurrency",
|
|
81194
|
-
label: "Concurrency (parallel tasks)",
|
|
81195
|
-
description: "How many tasks Ralphy works on at once. Higher finishes faster but uses more API quota simultaneously.",
|
|
81196
|
-
spec: { kind: "number", placeholder: "1" }
|
|
81197
|
-
},
|
|
81198
|
-
{
|
|
81199
|
-
id: "pollIntervalSeconds",
|
|
81200
|
-
label: "Poll interval (seconds)",
|
|
81201
|
-
description: "In agent mode, how often (in seconds) Ralphy checks Linear for new issues to pick up.",
|
|
81202
|
-
spec: { kind: "number", placeholder: "60" }
|
|
81203
|
-
},
|
|
81204
|
-
{
|
|
81205
|
-
id: "iterationDelaySeconds",
|
|
81206
|
-
label: "Delay between iterations (seconds)",
|
|
81207
|
-
description: "Seconds to pause between loop iterations \u2014 a throttle to slow spend. 0 means no pause.",
|
|
81208
|
-
spec: { kind: "number", placeholder: "0" }
|
|
81209
|
-
},
|
|
81210
|
-
{
|
|
81211
|
-
id: "maxIterationsPerTask",
|
|
81212
|
-
label: "Max iterations per task (0 = unlimited)",
|
|
81213
|
-
description: "Stop a task after this many loop iterations. 0 means no limit (run until done or another limit hits).",
|
|
81214
|
-
spec: { kind: "number", placeholder: "0" }
|
|
81215
|
-
},
|
|
81216
|
-
{
|
|
81217
|
-
id: "maxCostUsdPerTask",
|
|
81218
|
-
label: "Max cost USD per task (0 = unlimited)",
|
|
81219
|
-
description: "Stop a task once its API spend passes this many US dollars. 0 means no cost limit.",
|
|
81220
|
-
spec: { kind: "number", placeholder: "0" }
|
|
81221
|
-
},
|
|
81222
|
-
{
|
|
81223
|
-
id: "maxRuntimeMinutesPerTask",
|
|
81224
|
-
label: "Max runtime minutes per task (0 = unlimited)",
|
|
81225
|
-
description: "Stop a task after this many minutes of wall-clock time. 0 means no time limit.",
|
|
81226
|
-
spec: { kind: "number", placeholder: "0" }
|
|
81227
|
-
},
|
|
81228
|
-
{
|
|
81229
|
-
id: "maxConsecutiveFailuresPerTask",
|
|
81230
|
-
label: "Max consecutive identical failures",
|
|
81231
|
-
description: "Give up on a task after this many identical failures in a row \u2014 a guard against stuck loops.",
|
|
81232
|
-
spec: { kind: "number", placeholder: "5" }
|
|
81233
|
-
},
|
|
81234
|
-
{
|
|
81235
|
-
id: "useWorktree",
|
|
81236
|
-
label: "Run each task in an isolated git worktree?",
|
|
81237
|
-
description: "Run each task in its own git worktree (a separate working copy of the repo) so parallel tasks don't overwrite each other's files.",
|
|
81238
|
-
spec: no()
|
|
81239
|
-
},
|
|
81240
|
-
{
|
|
81241
|
-
id: "cleanupWorktreeOnSuccess",
|
|
81242
|
-
label: "Delete the worktree after a successful task?",
|
|
81243
|
-
description: "Delete a task's worktree (its separate working copy) once it succeeds, to reclaim disk space.",
|
|
81244
|
-
spec: no(),
|
|
81245
|
-
when: isOn("useWorktree")
|
|
81246
|
-
},
|
|
81247
|
-
{
|
|
81248
|
-
id: "setupScript",
|
|
81249
|
-
label: "Setup script (runs before each task)",
|
|
81250
|
-
description: "Shell script run once before each task starts \u2014 e.g. to install dependencies.",
|
|
81251
|
-
spec: { kind: "text" }
|
|
81252
|
-
},
|
|
81253
|
-
{
|
|
81254
|
-
id: "teardownScript",
|
|
81255
|
-
label: "Teardown script (runs after each task)",
|
|
81256
|
-
description: "Shell script run once after each task ends \u2014 e.g. to clean up temporary state.",
|
|
81257
|
-
spec: { kind: "text" }
|
|
81258
|
-
},
|
|
81259
|
-
{
|
|
81260
|
-
id: "enableManualTest",
|
|
81261
|
-
label: "Enable the manual-test phase?",
|
|
81262
|
-
description: "Add a phase that pauses for a human to manually test the change (e.g. in the UI) before the task is marked done.",
|
|
81263
|
-
spec: no()
|
|
81264
|
-
},
|
|
81265
|
-
{
|
|
81266
|
-
id: "appendPrompt",
|
|
81267
|
-
label: "Extra text appended to every prompt",
|
|
81268
|
-
description: "Free text added to the end of every prompt sent to the agent \u2014 house rules or reminders.",
|
|
81269
|
-
spec: { kind: "text" }
|
|
81270
|
-
},
|
|
81271
|
-
{
|
|
81272
|
-
id: "createPrOnSuccess",
|
|
81273
|
-
label: "Open a pull request when a task succeeds?",
|
|
81274
|
-
description: "When a task succeeds, automatically push the branch and open a GitHub pull request (PR).",
|
|
81275
|
-
spec: no()
|
|
81276
|
-
},
|
|
81277
|
-
{
|
|
81278
|
-
id: "prDraft",
|
|
81279
|
-
label: "Open pull requests as drafts?",
|
|
81280
|
-
description: "Open PRs as drafts (marked not-ready-for-review) instead of ready for review.",
|
|
81281
|
-
spec: no(),
|
|
81282
|
-
when: isOn("createPrOnSuccess")
|
|
81283
|
-
},
|
|
81284
|
-
{
|
|
81285
|
-
id: "prBaseBranch",
|
|
81286
|
-
label: "PR base branch",
|
|
81287
|
-
description: "The branch new pull requests merge into (their base) \u2014 e.g. main.",
|
|
81288
|
-
spec: { kind: "text", placeholder: "main" },
|
|
81289
|
-
when: isOn("createPrOnSuccess")
|
|
81290
|
-
},
|
|
81291
|
-
{
|
|
81292
|
-
id: "stackPrsOnDependencies",
|
|
81293
|
-
label: "Stack dependent issues' PRs onto their blocker's PR?",
|
|
81294
|
-
description: "If an issue is blocked by another that already has an open PR, base this issue's PR on that PR's branch instead of main (a 'stacked' PR).",
|
|
81295
|
-
spec: no(),
|
|
81296
|
-
when: isOn("createPrOnSuccess")
|
|
81297
|
-
},
|
|
81298
|
-
{
|
|
81299
|
-
id: "autoMergeStrategy",
|
|
81300
|
-
label: "Auto-merge strategy",
|
|
81301
|
-
description: "How GitHub combines the PR's commits when it auto-merges: squash (one commit), merge (a merge commit), or rebase.",
|
|
81302
|
-
spec: {
|
|
81303
|
-
kind: "select",
|
|
81304
|
-
options: [
|
|
81305
|
-
{ label: "squash", value: "squash" },
|
|
81306
|
-
{ label: "merge", value: "merge" },
|
|
81307
|
-
{ label: "rebase", value: "rebase" }
|
|
81308
|
-
]
|
|
81309
|
-
},
|
|
81310
|
-
when: isOn("createPrOnSuccess")
|
|
81311
|
-
},
|
|
81312
|
-
{
|
|
81313
|
-
id: "manualMergeWhenAutoMergeDisabled",
|
|
81314
|
-
label: "Merge manually when GitHub auto-merge is disabled?",
|
|
81315
|
-
description: "If the repo doesn't have GitHub's auto-merge feature enabled, have Ralphy merge the PR itself once checks pass.",
|
|
81316
|
-
spec: yes(),
|
|
81317
|
-
when: isOn("createPrOnSuccess")
|
|
81318
|
-
},
|
|
81319
|
-
{
|
|
81320
|
-
id: "finalizeNoOpAsDone",
|
|
81321
|
-
label: "Finalize a no-op (meta-only) change as done?",
|
|
81322
|
-
description: "If a change ended up touching only meta files (specs, task lists) and no real code, mark the issue done instead of retrying it.",
|
|
81323
|
-
spec: yes()
|
|
81324
|
-
},
|
|
81325
|
-
{
|
|
81326
|
-
id: "fixCiOnFailure",
|
|
81327
|
-
label: "Let the agent fix CI failures?",
|
|
81328
|
-
description: "After opening a PR, watch its CI (the automated checks GitHub runs) and let the agent push fixes when they fail.",
|
|
81329
|
-
spec: no()
|
|
81330
|
-
},
|
|
81331
|
-
{
|
|
81332
|
-
id: "maxCiFixAttempts",
|
|
81333
|
-
label: "Max CI-fix attempts per task",
|
|
81334
|
-
description: "Stop trying to fix failing CI after this many attempts.",
|
|
81335
|
-
spec: { kind: "number", placeholder: "5" },
|
|
81336
|
-
when: isOn("fixCiOnFailure")
|
|
81337
|
-
},
|
|
81338
|
-
{
|
|
81339
|
-
id: "ciPollIntervalSeconds",
|
|
81340
|
-
label: "CI status poll interval (seconds)",
|
|
81341
|
-
description: "How often (in seconds) to re-check the PR's CI status while waiting on or fixing it.",
|
|
81342
|
-
spec: { kind: "number", placeholder: "30" },
|
|
81343
|
-
when: isOn("fixCiOnFailure")
|
|
81344
|
-
},
|
|
81345
|
-
{
|
|
81346
|
-
id: "ignoreCiChecks",
|
|
81347
|
-
label: "CI checks to ignore",
|
|
81348
|
-
description: "Names of CI checks to ignore when deciding whether a PR is green \u2014 e.g. known-flaky jobs.",
|
|
81349
|
-
spec: { kind: "list", placeholder: "check name" }
|
|
81350
|
-
},
|
|
81351
|
-
{
|
|
81352
|
-
id: "rules",
|
|
81353
|
-
label: "Project rules",
|
|
81354
|
-
description: "House rules added to every prompt (e.g. 'never edit generated files'). One rule per entry.",
|
|
81355
|
-
spec: { kind: "list", placeholder: "a rule" }
|
|
81356
|
-
},
|
|
81357
|
-
{
|
|
81358
|
-
id: "boundaries.never_touch",
|
|
81359
|
-
label: "Never-touch globs",
|
|
81360
|
-
description: "Glob patterns for files the agent must never modify (e.g. dist/**).",
|
|
81361
|
-
spec: { kind: "list", placeholder: "dist/**" }
|
|
81362
|
-
},
|
|
81363
|
-
LINEAR_TEAM,
|
|
81364
|
-
REPO_LINK,
|
|
81365
|
-
LINEAR_FILTER,
|
|
81366
|
-
{
|
|
81367
|
-
id: "linear.postComments",
|
|
81368
|
-
label: "Post progress comments on the Linear issue?",
|
|
81369
|
-
description: "Post progress comments on the Linear issue while a task runs.",
|
|
81370
|
-
spec: yes()
|
|
81371
|
-
},
|
|
81372
|
-
{
|
|
81373
|
-
id: "linear.updateEveryIterations",
|
|
81374
|
-
label: "Post a progress update every N iterations (0 = off)",
|
|
81375
|
-
description: "Post a progress comment every N loop iterations. 0 turns periodic updates off.",
|
|
81376
|
-
spec: { kind: "number", placeholder: "10" }
|
|
81377
|
-
},
|
|
81378
|
-
{
|
|
81379
|
-
id: "linear.mentionTrigger",
|
|
81380
|
-
label: "Watch comments/PRs for @mentions?",
|
|
81381
|
-
description: "Watch a finished issue's comments and its PR for @mentions of Ralphy, and re-engage when mentioned.",
|
|
81382
|
-
spec: yes()
|
|
81383
|
-
},
|
|
81384
|
-
{
|
|
81385
|
-
id: "linear.mentionHandle",
|
|
81386
|
-
label: "Mention handle",
|
|
81387
|
-
description: "The @handle that, when mentioned, makes Ralphy pick the issue back up (e.g. @ralphy).",
|
|
81388
|
-
spec: { kind: "text", placeholder: "@ralphy" },
|
|
81389
|
-
when: isOn("linear.mentionTrigger")
|
|
81390
|
-
},
|
|
81391
|
-
{
|
|
81392
|
-
id: "linear.codeReviewTrigger",
|
|
81393
|
-
label: "Watch PRs for unresolved review threads?",
|
|
81394
|
-
description: "Watch open PRs for unresolved review comments and re-engage to address them.",
|
|
81395
|
-
spec: yes()
|
|
81396
|
-
},
|
|
81397
|
-
{
|
|
81398
|
-
id: "linear.codeReviewStaleHours",
|
|
81399
|
-
label: "Code-review stale window (hours)",
|
|
81400
|
-
description: "Ignore review comments older than this many hours, so stale threads don't re-trigger work.",
|
|
81401
|
-
spec: { kind: "number", placeholder: "24" },
|
|
81402
|
-
when: isOn("linear.codeReviewTrigger")
|
|
81403
|
-
},
|
|
81404
|
-
{
|
|
81405
|
-
id: "linear.syncTasksToComment",
|
|
81406
|
-
label: "Mirror tasks.md into a sticky Linear comment?",
|
|
81407
|
-
description: "Keep one pinned ('sticky') Linear comment in sync with the task checklist (tasks.md).",
|
|
81408
|
-
spec: yes()
|
|
81409
|
-
},
|
|
81410
|
-
{
|
|
81411
|
-
id: "linear.syncSpecsAsAttachments",
|
|
81412
|
-
label: "Upload proposal.md / design.md as attachments?",
|
|
81413
|
-
description: "Upload the OpenSpec planning docs (proposal.md, design.md) to the issue as attachments. OpenSpec is Ralphy's spec-driven planning format.",
|
|
81414
|
-
spec: yes()
|
|
81415
|
-
},
|
|
81416
|
-
{
|
|
81417
|
-
id: "linear.specAttachmentFormats",
|
|
81418
|
-
label: "Spec attachment formats",
|
|
81419
|
-
description: "Which formats to upload the spec docs in: 'md' (raw markdown), 'pdf' (a rendered PDF), or both.",
|
|
81420
|
-
spec: {
|
|
81421
|
-
kind: "multiselect",
|
|
81422
|
-
options: [
|
|
81423
|
-
{ label: "md", value: "md" },
|
|
81424
|
-
{ label: "pdf", value: "pdf" }
|
|
81425
|
-
]
|
|
81426
|
-
},
|
|
81427
|
-
when: isOn("linear.syncSpecsAsAttachments")
|
|
81428
|
-
},
|
|
81429
|
-
{
|
|
81430
|
-
id: "linear.confirmationMode.enabled",
|
|
81431
|
-
label: "Enable the human confirmation gate?",
|
|
81432
|
-
description: "Pause after the agent finishes planning and wait for a human to approve before it writes any code (a confirmation gate).",
|
|
81433
|
-
spec: no()
|
|
81434
|
-
},
|
|
81435
|
-
{
|
|
81436
|
-
id: "linear.confirmationMode.timeoutHours",
|
|
81437
|
-
label: "Confirmation timeout (hours)",
|
|
81438
|
-
description: "If no one approves or rejects within this many hours, auto-resolve the confirmation gate.",
|
|
81439
|
-
spec: { kind: "number", placeholder: "48" },
|
|
81440
|
-
when: isOn("linear.confirmationMode.enabled")
|
|
81441
|
-
},
|
|
81442
|
-
{
|
|
81443
|
-
id: "linear.confirmationMode.maxConfirmationRounds",
|
|
81444
|
-
label: "Max confirmation rounds",
|
|
81445
|
-
description: "How many times the plan can be revised and re-submitted for approval before Ralphy gives up.",
|
|
81446
|
-
spec: { kind: "number", placeholder: "3" },
|
|
81447
|
-
when: isOn("linear.confirmationMode.enabled")
|
|
81448
|
-
},
|
|
81449
|
-
{
|
|
81450
|
-
id: AWAITING_STATUS_FIELD_ID,
|
|
81451
|
-
label: "Park awaiting-approval tickets in a status?",
|
|
81452
|
-
hint: "e.g. Planned \u2014 blank keeps them In Progress",
|
|
81453
|
-
description: "When the confirmation gate opens, move the ticket to this Linear status so the board shows it waiting on a human (it must be a real status in your team). Ralphy also adds it to the in-progress pickup filter so the parked ticket keeps being polled, and re-asserts In Progress on approval. Leave blank to keep parked tickets in In Progress. Pairs with status-based indicators.",
|
|
81454
|
-
spec: { kind: "text", placeholder: "Planned" },
|
|
81455
|
-
when: isOn("linear.confirmationMode.enabled")
|
|
81456
|
-
},
|
|
81457
|
-
{
|
|
81458
|
-
id: "linear.indicators",
|
|
81459
|
-
label: "Linear lifecycle indicators",
|
|
81460
|
-
description: "How Ralphy maps lifecycle events to Linear statuses/labels \u2014 which issues to pick up (todo) and what to set when a task is in progress, done, or errored.",
|
|
81461
|
-
spec: { kind: "indicators" }
|
|
81462
|
-
},
|
|
81463
|
-
{
|
|
81464
|
-
id: "preExistingErrorCheck.enabled",
|
|
81465
|
-
label: "Enable the base-branch health gate?",
|
|
81466
|
-
description: "Before picking up new work, run health-check commands on the base branch and pause if it's already broken, so the agent isn't blamed for pre-existing failures.",
|
|
81467
|
-
spec: no()
|
|
81468
|
-
},
|
|
81469
|
-
{
|
|
81470
|
-
id: "preExistingErrorCheck.commands",
|
|
81471
|
-
label: "Health-gate commands (blank = use lint/test)",
|
|
81472
|
-
description: "Commands run against the base branch to judge its health. Leave empty to reuse your lint/test commands.",
|
|
81473
|
-
spec: { kind: "list", placeholder: "bun run lint" },
|
|
81474
|
-
when: isOn("preExistingErrorCheck.enabled")
|
|
81475
|
-
},
|
|
81476
|
-
{
|
|
81477
|
-
id: "preExistingErrorCheck.baseBranch",
|
|
81478
|
-
label: "Health-gate base branch",
|
|
81479
|
-
description: "The branch the health gate checks out and tests (usually main).",
|
|
81480
|
-
spec: { kind: "text", placeholder: "main" },
|
|
81481
|
-
when: isOn("preExistingErrorCheck.enabled")
|
|
81482
|
-
},
|
|
81483
|
-
{
|
|
81484
|
-
id: "preExistingErrorCheck.label",
|
|
81485
|
-
label: "Health-gate Linear label",
|
|
81486
|
-
description: "Linear label applied to the ticket Ralphy opens when the base branch is found broken.",
|
|
81487
|
-
spec: { kind: "text", placeholder: "ralph:pre-existing-error" },
|
|
81488
|
-
when: isOn("preExistingErrorCheck.enabled")
|
|
81489
|
-
},
|
|
81490
|
-
{
|
|
81491
|
-
id: "prTracker.enabled",
|
|
81492
|
-
label: "Enable the PR tracker?",
|
|
81493
|
-
description: "Keep watching the PRs Ralphy opened and automatically try to recover any whose merge state goes red (conflicts or failing CI).",
|
|
81494
|
-
spec: yes()
|
|
81495
|
-
},
|
|
81496
|
-
{
|
|
81497
|
-
id: "prTracker.maxRecoveryAttempts",
|
|
81498
|
-
label: "PR tracker max recovery attempts",
|
|
81499
|
-
description: "Give up auto-recovering a red PR after this many attempts, then flag it for a human.",
|
|
81500
|
-
spec: { kind: "number", placeholder: "3" },
|
|
81501
|
-
when: isOn("prTracker.enabled")
|
|
81502
|
-
},
|
|
81503
|
-
{
|
|
81504
|
-
id: "prTracker.advanceMergedToDone",
|
|
81505
|
-
label: "Advance merged PRs to done automatically?",
|
|
81506
|
-
description: "Move an issue to its done state as soon as its PR is merged.",
|
|
81507
|
-
spec: no(),
|
|
81508
|
-
when: isOn("prTracker.enabled")
|
|
81509
|
-
},
|
|
81510
|
-
{
|
|
81511
|
-
id: "metaPrompt.enabled",
|
|
81512
|
-
label: "Enable the meta-prompt addendum?",
|
|
81513
|
-
description: "Add Ralphy's task-level 'meta-prompt' layer (extra framing instructions) to each phase. Leave on unless you want raw prompts.",
|
|
81514
|
-
spec: yes()
|
|
81515
|
-
},
|
|
81516
|
-
{
|
|
81517
|
-
id: "metaPrompt.effort",
|
|
81518
|
-
label: "Per-ticket effort tier",
|
|
81519
|
-
description: "How much effort the meta-prompt nudges the agent toward per ticket. 'auto' detects it from the ticket; 'light'/'standard'/'heavy' pin every ticket to that tier.",
|
|
81520
|
-
spec: {
|
|
81521
|
-
kind: "select",
|
|
81522
|
-
options: [
|
|
81523
|
-
{ label: "auto", value: "auto" },
|
|
81524
|
-
{ label: "light", value: "light" },
|
|
81525
|
-
{ label: "standard", value: "standard" },
|
|
81526
|
-
{ label: "heavy", value: "heavy" }
|
|
81527
|
-
]
|
|
81528
|
-
},
|
|
81529
|
-
when: isOn("metaPrompt.enabled")
|
|
81530
|
-
},
|
|
81531
|
-
{
|
|
81532
|
-
id: "openspec.reviewPhase.enabled",
|
|
81533
|
-
label: "Enable the OpenSpec review phase?",
|
|
81534
|
-
description: "After all tasks finish, spawn a separate reviewer agent that reads the full diff and writes review findings; open findings loop back into more work.",
|
|
81535
|
-
spec: no()
|
|
81536
|
-
},
|
|
81537
|
-
{
|
|
81538
|
-
id: "openspec.reviewPhase.maxRounds",
|
|
81539
|
-
label: "Review phase max rounds",
|
|
81540
|
-
description: "How many review\u2192fix cycles to run before the change is archived regardless.",
|
|
81541
|
-
spec: { kind: "number", placeholder: "1" },
|
|
81542
|
-
when: isOn("openspec.reviewPhase.enabled")
|
|
81543
|
-
},
|
|
81544
|
-
{
|
|
81545
|
-
id: "openspec.reviewPhase.reviewerModel",
|
|
81546
|
-
label: "Reviewer model (blank = same as main)",
|
|
81547
|
-
description: "Model used for the review pass. Blank reuses the main model; a cheaper tier (e.g. haiku) saves cost.",
|
|
81548
|
-
spec: { kind: "text", placeholder: "haiku" },
|
|
81549
|
-
when: isOn("openspec.reviewPhase.enabled")
|
|
81550
|
-
},
|
|
81551
|
-
{
|
|
81552
|
-
id: "openspec.reviewPhase.reviewerContextStrategy",
|
|
81553
|
-
label: "Reviewer context",
|
|
81554
|
-
description: "'fresh' gives the reviewer a brand-new session (unbiased); 'warm' resumes the last task's session (more context, cheaper).",
|
|
81555
|
-
spec: {
|
|
81556
|
-
kind: "select",
|
|
81557
|
-
options: [
|
|
81558
|
-
{ label: "fresh", value: "fresh" },
|
|
81559
|
-
{ label: "warm", value: "warm" }
|
|
81560
|
-
]
|
|
81561
|
-
},
|
|
81562
|
-
when: isOn("openspec.reviewPhase.enabled")
|
|
81563
|
-
},
|
|
81564
|
-
{
|
|
81565
|
-
id: PROMPT_BODY_FIELD_ID,
|
|
81566
|
-
label: "Customize the prompt sent to the agent?",
|
|
81567
|
-
description: "The prompt the agent receives lives in the file body \u2014 a template filled with per-issue values (e.g. {{ issue.identifier }}). Edit it here, or leave it and finish to keep the default.",
|
|
81568
|
-
spec: { kind: "multiline" }
|
|
81569
|
-
}
|
|
81570
|
-
];
|
|
81571
|
-
COMMON_CLI_OPTIONS = [
|
|
81572
|
-
{ fieldId: "model", flag: "--model", argKey: "model", kind: "model" },
|
|
81573
|
-
{ fieldId: "iterationDelaySeconds", flag: "--delay", argKey: "delay", kind: "int" },
|
|
81574
|
-
{ fieldId: "maxCostUsdPerTask", flag: "--max-cost", argKey: "maxCostUsd", kind: "float" },
|
|
81575
|
-
{
|
|
81576
|
-
fieldId: "maxRuntimeMinutesPerTask",
|
|
81577
|
-
flag: "--max-runtime",
|
|
81578
|
-
argKey: "maxRuntimeMinutes",
|
|
81579
|
-
kind: "float"
|
|
81580
|
-
},
|
|
81581
|
-
{
|
|
81582
|
-
fieldId: "maxConsecutiveFailuresPerTask",
|
|
81583
|
-
flag: "--max-failures",
|
|
81584
|
-
argKey: "maxConsecutiveFailures",
|
|
81585
|
-
kind: "int"
|
|
81586
|
-
},
|
|
81587
|
-
{
|
|
81588
|
-
fieldId: "maxIterationsPerTask",
|
|
81589
|
-
flag: "--max-iterations",
|
|
81590
|
-
argKey: "maxIterations",
|
|
81591
|
-
kind: "int"
|
|
81592
|
-
},
|
|
81593
|
-
{ fieldId: "logRawStream", flag: "--log", argKey: "log", kind: "boolean" },
|
|
81594
|
-
{ fieldId: "taskVerbose", flag: "--verbose", argKey: "verbose", kind: "boolean" }
|
|
81595
|
-
];
|
|
81596
|
-
FIELD_DESCRIPTIONS = CUSTOMIZED_FIELDS.filter((field) => Boolean(field.description) && field.spec.kind !== "multiline").map((field) => ({ path: field.id.split("."), description: field.description }));
|
|
81597
|
-
});
|
|
81598
|
-
|
|
81599
81835
|
// packages/workflow/src/wizard.ts
|
|
81600
81836
|
function indicatorsForPreset(preset) {
|
|
81601
81837
|
if (preset === "status-standard") {
|
|
@@ -81689,6 +81925,87 @@ var init_wizard = __esm(() => {
|
|
|
81689
81925
|
import_yaml = __toESM(require_dist(), 1);
|
|
81690
81926
|
});
|
|
81691
81927
|
|
|
81928
|
+
// packages/workflow/src/migrate/normalize.ts
|
|
81929
|
+
function defaultLeafEntries() {
|
|
81930
|
+
const defaults2 = WorkflowConfigSchema.parse({});
|
|
81931
|
+
const entries = [];
|
|
81932
|
+
const walk = (node2, prefix) => {
|
|
81933
|
+
if (Array.isArray(node2)) {
|
|
81934
|
+
entries.push({ path: prefix, value: node2 });
|
|
81935
|
+
return;
|
|
81936
|
+
}
|
|
81937
|
+
if (node2 && typeof node2 === "object") {
|
|
81938
|
+
const keys2 = Object.keys(node2);
|
|
81939
|
+
if (keys2.length === 0)
|
|
81940
|
+
return;
|
|
81941
|
+
for (const key of keys2)
|
|
81942
|
+
walk(node2[key], [...prefix, key]);
|
|
81943
|
+
return;
|
|
81944
|
+
}
|
|
81945
|
+
entries.push({ path: prefix, value: node2 });
|
|
81946
|
+
};
|
|
81947
|
+
walk(defaults2, []);
|
|
81948
|
+
return entries.filter((entry) => !(entry.path.length === 1 && entry.path[0] === "version"));
|
|
81949
|
+
}
|
|
81950
|
+
function stampDescription(document2, path) {
|
|
81951
|
+
const match = FIELD_DESCRIPTIONS.find((description) => description.path.length === path.length && description.path.every((segment, index) => segment === path[index]));
|
|
81952
|
+
if (!match)
|
|
81953
|
+
return;
|
|
81954
|
+
const parent = path.length === 1 ? document2.contents : document2.getIn(path.slice(0, -1), true);
|
|
81955
|
+
if (!import_yaml2.default.isMap(parent))
|
|
81956
|
+
return;
|
|
81957
|
+
const leaf = path[path.length - 1];
|
|
81958
|
+
const pair = parent.items.find((item) => import_yaml2.default.isScalar(item.key) && String(item.key.value) === leaf);
|
|
81959
|
+
if (!pair || !import_yaml2.default.isScalar(pair.key))
|
|
81960
|
+
return;
|
|
81961
|
+
pair.key.commentBefore = toCommentLines(match.description);
|
|
81962
|
+
}
|
|
81963
|
+
function normalizeWorkflowMarkdown(markdown) {
|
|
81964
|
+
const match = FRONTMATTER_RE.exec(markdown);
|
|
81965
|
+
if (!match)
|
|
81966
|
+
return { markdown, changed: false, added: [] };
|
|
81967
|
+
const document2 = import_yaml2.default.parseDocument(match[1] ?? "");
|
|
81968
|
+
if (!import_yaml2.default.isMap(document2.contents))
|
|
81969
|
+
return { markdown, changed: false, added: [] };
|
|
81970
|
+
const body = match[2] ?? "";
|
|
81971
|
+
const added = [];
|
|
81972
|
+
for (const { path, value } of defaultLeafEntries()) {
|
|
81973
|
+
if (document2.getIn(path) !== undefined)
|
|
81974
|
+
continue;
|
|
81975
|
+
document2.setIn(path, value);
|
|
81976
|
+
stampDescription(document2, path);
|
|
81977
|
+
added.push(path.join("."));
|
|
81978
|
+
}
|
|
81979
|
+
const gateEnabled = document2.getIn(["linear", "confirmationMode", "enabled"]) === true;
|
|
81980
|
+
const hasGetApproved = document2.getIn(["linear", "indicators", "getApproved"]) !== undefined;
|
|
81981
|
+
if (gateEnabled && !hasGetApproved) {
|
|
81982
|
+
document2.setIn(["linear", "indicators", "getApproved"], DEFAULT_APPROVAL_INDICATORS.getApproved);
|
|
81983
|
+
if (document2.getIn(["linear", "indicators", "clearApproved"]) === undefined) {
|
|
81984
|
+
document2.setIn(["linear", "indicators", "clearApproved"], DEFAULT_APPROVAL_INDICATORS.clearApproved);
|
|
81985
|
+
}
|
|
81986
|
+
added.push("linear.indicators.getApproved");
|
|
81987
|
+
}
|
|
81988
|
+
if (added.length === 0)
|
|
81989
|
+
return { markdown, changed: false, added: [] };
|
|
81990
|
+
const frontmatter = document2.toString({ flowCollectionPadding: false }).replace(/\n+$/, "");
|
|
81991
|
+
return { markdown: `---
|
|
81992
|
+
${frontmatter}
|
|
81993
|
+
---
|
|
81994
|
+
${body}`, changed: true, added };
|
|
81995
|
+
}
|
|
81996
|
+
var import_yaml2, DEFAULT_APPROVAL_INDICATORS;
|
|
81997
|
+
var init_normalize = __esm(() => {
|
|
81998
|
+
init_schema();
|
|
81999
|
+
init_default();
|
|
82000
|
+
init_fields();
|
|
82001
|
+
init_wizard();
|
|
82002
|
+
import_yaml2 = __toESM(require_dist(), 1);
|
|
82003
|
+
DEFAULT_APPROVAL_INDICATORS = {
|
|
82004
|
+
getApproved: { filter: [{ type: "label", value: "approved" }] },
|
|
82005
|
+
clearApproved: { type: "label", value: "approved" }
|
|
82006
|
+
};
|
|
82007
|
+
});
|
|
82008
|
+
|
|
81692
82009
|
// packages/workflow/src/confirmation.ts
|
|
81693
82010
|
function matchesIndicator(indicator, ticket) {
|
|
81694
82011
|
if (!indicator || indicator.filter.length === 0)
|
|
@@ -81796,6 +82113,7 @@ __export(exports_workflow, {
|
|
|
81796
82113
|
renderTemplate: () => renderTemplate,
|
|
81797
82114
|
parseWorkflow: () => parseWorkflow,
|
|
81798
82115
|
parseLinearFilter: () => parseLinearFilter,
|
|
82116
|
+
normalizeWorkflowMarkdown: () => normalizeWorkflowMarkdown,
|
|
81799
82117
|
matchesIndicator: () => matchesIndicator,
|
|
81800
82118
|
loadWorkflow: () => loadWorkflow,
|
|
81801
82119
|
ensureWorkflow: () => ensureWorkflow,
|
|
@@ -81805,6 +82123,7 @@ __export(exports_workflow, {
|
|
|
81805
82123
|
WORKFLOW_FILE: () => WORKFLOW_FILE,
|
|
81806
82124
|
FRONTMATTER_RE: () => FRONTMATTER_RE,
|
|
81807
82125
|
DEFAULT_WORKFLOW_MD: () => DEFAULT_WORKFLOW_MD,
|
|
82126
|
+
DEFAULT_APPROVAL_INDICATORS: () => DEFAULT_APPROVAL_INDICATORS,
|
|
81808
82127
|
CURRENT_WORKFLOW_VERSION: () => CURRENT_WORKFLOW_VERSION
|
|
81809
82128
|
});
|
|
81810
82129
|
import { join as join5 } from "path";
|
|
@@ -81818,7 +82137,7 @@ function parseWorkflow(text, path = "") {
|
|
|
81818
82137
|
const body = m[2] ?? "";
|
|
81819
82138
|
let raw;
|
|
81820
82139
|
try {
|
|
81821
|
-
raw =
|
|
82140
|
+
raw = import_yaml3.default.parse(yamlText, { schema: "core" });
|
|
81822
82141
|
} catch (err) {
|
|
81823
82142
|
throw new Error(`WORKFLOW.md frontmatter is not valid YAML.
|
|
81824
82143
|
` + (path ? ` File: ${path}
|
|
@@ -81909,7 +82228,7 @@ function applyAliases(cfg) {
|
|
|
81909
82228
|
function workflowPath(projectRoot, workflowFile) {
|
|
81910
82229
|
return workflowFile ?? join5(projectRoot, WORKFLOW_FILE);
|
|
81911
82230
|
}
|
|
81912
|
-
async function loadWorkflow(projectRoot, workflowFile) {
|
|
82231
|
+
async function loadWorkflow(projectRoot, workflowFile, options = {}) {
|
|
81913
82232
|
const path = workflowPath(projectRoot, workflowFile);
|
|
81914
82233
|
const file2 = Bun.file(path);
|
|
81915
82234
|
if (!await file2.exists()) {
|
|
@@ -81917,7 +82236,10 @@ async function loadWorkflow(projectRoot, workflowFile) {
|
|
|
81917
82236
|
return { config: config2, body: extractDefaultBody(), path };
|
|
81918
82237
|
}
|
|
81919
82238
|
const text = await file2.text();
|
|
81920
|
-
|
|
82239
|
+
const normalized = normalizeWorkflowMarkdown(text);
|
|
82240
|
+
if (normalized.changed && options.persist)
|
|
82241
|
+
await Bun.write(path, normalized.markdown);
|
|
82242
|
+
return parseWorkflow(normalized.markdown, path);
|
|
81921
82243
|
}
|
|
81922
82244
|
async function ensureWorkflow(projectRoot, workflowFile) {
|
|
81923
82245
|
const path = workflowPath(projectRoot, workflowFile);
|
|
@@ -81952,15 +82274,17 @@ function renderWorkflowPrompt(workflow, ctx) {
|
|
|
81952
82274
|
};
|
|
81953
82275
|
return renderTemplate(workflow.body, fullCtx);
|
|
81954
82276
|
}
|
|
81955
|
-
var
|
|
82277
|
+
var import_yaml3, WORKFLOW_FILE = "WORKFLOW.md";
|
|
81956
82278
|
var init_workflow = __esm(() => {
|
|
81957
82279
|
init_schema();
|
|
81958
82280
|
init_default();
|
|
81959
82281
|
init_wizard();
|
|
82282
|
+
init_normalize();
|
|
81960
82283
|
init_schema();
|
|
81961
82284
|
init_default();
|
|
81962
82285
|
init_linear_filter();
|
|
81963
|
-
|
|
82286
|
+
init_normalize();
|
|
82287
|
+
import_yaml3 = __toESM(require_dist(), 1);
|
|
81964
82288
|
});
|
|
81965
82289
|
|
|
81966
82290
|
// packages/core/src/repo/index.ts
|
|
@@ -82911,6 +83235,24 @@ function resolveIndicators(value) {
|
|
|
82911
83235
|
}
|
|
82912
83236
|
function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
|
|
82913
83237
|
const values2 = { ...answers };
|
|
83238
|
+
const concurrencyValue = values2["concurrency"];
|
|
83239
|
+
if (typeof concurrencyValue === "number" && concurrencyValue > 1) {
|
|
83240
|
+
values2["useWorktree"] = true;
|
|
83241
|
+
}
|
|
83242
|
+
const assigneeChoice = values2[LINEAR_ASSIGNEE_CHOICE_FIELD_ID];
|
|
83243
|
+
if (typeof assigneeChoice === "string") {
|
|
83244
|
+
let assignee;
|
|
83245
|
+
if (assigneeChoice === "other") {
|
|
83246
|
+
const raw = values2[LINEAR_ASSIGNEE_VALUE_FIELD_ID];
|
|
83247
|
+
assignee = typeof raw === "string" && raw.trim() !== "" ? raw.trim() : undefined;
|
|
83248
|
+
} else {
|
|
83249
|
+
assignee = assigneeChoice;
|
|
83250
|
+
}
|
|
83251
|
+
if (assignee)
|
|
83252
|
+
values2["linear.filter"] = `assignee = ${assignee}`;
|
|
83253
|
+
}
|
|
83254
|
+
delete values2[LINEAR_ASSIGNEE_CHOICE_FIELD_ID];
|
|
83255
|
+
delete values2[LINEAR_ASSIGNEE_VALUE_FIELD_ID];
|
|
82914
83256
|
if ("linear.indicators" in values2) {
|
|
82915
83257
|
const indicators = resolveIndicators(values2["linear.indicators"]);
|
|
82916
83258
|
if (indicators)
|
|
@@ -82926,20 +83268,20 @@ function buildFromAnswers(mode, answers, build = buildWorkflowMarkdown) {
|
|
|
82926
83268
|
values2["linear.indicators"] = map3;
|
|
82927
83269
|
}
|
|
82928
83270
|
}
|
|
82929
|
-
|
|
82930
|
-
const parkStatus = typeof parkStatusRaw === "string" ? parkStatusRaw.trim() : "";
|
|
82931
|
-
if (values2["linear.confirmationMode.enabled"] === true && parkStatus && values2["linear.indicators"] && typeof values2["linear.indicators"] === "object") {
|
|
83271
|
+
if (values2["linear.confirmationMode.enabled"] === true && values2["linear.indicators"] && typeof values2["linear.indicators"] === "object") {
|
|
82932
83272
|
const map3 = { ...values2["linear.indicators"] };
|
|
82933
|
-
map3.setAwaitingConfirmation
|
|
82934
|
-
const
|
|
82935
|
-
|
|
82936
|
-
|
|
82937
|
-
filter2.
|
|
83273
|
+
const awaiting = map3.setAwaitingConfirmation;
|
|
83274
|
+
const parkMarker = Array.isArray(awaiting) ? awaiting.find((marker) => marker.type === "status") : awaiting;
|
|
83275
|
+
if (parkMarker && !Array.isArray(parkMarker) && "type" in parkMarker && parkMarker.type === "status") {
|
|
83276
|
+
const existing = map3.getInProgress;
|
|
83277
|
+
const filter2 = existing && !Array.isArray(existing) && "filter" in existing ? [...existing.filter] : [];
|
|
83278
|
+
if (!filter2.some((marker) => marker.type === "status" && marker.value === parkMarker.value)) {
|
|
83279
|
+
filter2.push({ type: "status", value: parkMarker.value });
|
|
83280
|
+
map3.getInProgress = { filter: filter2 };
|
|
83281
|
+
values2["linear.indicators"] = map3;
|
|
83282
|
+
}
|
|
82938
83283
|
}
|
|
82939
|
-
map3.getInProgress = { filter: filter2 };
|
|
82940
|
-
values2["linear.indicators"] = map3;
|
|
82941
83284
|
}
|
|
82942
|
-
delete values2[AWAITING_STATUS_FIELD_ID];
|
|
82943
83285
|
const linkRepo = values2[REPO_LINK_FIELD_ID] === true;
|
|
82944
83286
|
delete values2[REPO_LINK_FIELD_ID];
|
|
82945
83287
|
if (!linkRepo) {
|
|
@@ -83028,7 +83370,8 @@ function SetupWizard({
|
|
|
83028
83370
|
buildMarkdown,
|
|
83029
83371
|
onlyFields,
|
|
83030
83372
|
initialBody,
|
|
83031
|
-
detectedRepo
|
|
83373
|
+
detectedRepo,
|
|
83374
|
+
onAnswersChange
|
|
83032
83375
|
}) {
|
|
83033
83376
|
const { exit } = use_app_default();
|
|
83034
83377
|
const startValues = initialValues ?? {};
|
|
@@ -83103,6 +83446,7 @@ function SetupWizard({
|
|
|
83103
83446
|
setAnswers(source);
|
|
83104
83447
|
setIndex(target);
|
|
83105
83448
|
initEditing(fieldsFor(mode, source)[target], source);
|
|
83449
|
+
onAnswersChange?.({ mode, values: source });
|
|
83106
83450
|
};
|
|
83107
83451
|
const valuesToWrite = (source) => {
|
|
83108
83452
|
if (!onlyFields)
|
|
@@ -83140,6 +83484,7 @@ function SetupWizard({
|
|
|
83140
83484
|
setIndex(0);
|
|
83141
83485
|
setVisited(new Set([fieldsFor(chosen, answers)[0].id]));
|
|
83142
83486
|
initEditing(fieldsFor(chosen, answers)[0], answers);
|
|
83487
|
+
onAnswersChange?.({ mode: chosen, values: answers });
|
|
83143
83488
|
}
|
|
83144
83489
|
return;
|
|
83145
83490
|
}
|
|
@@ -83240,7 +83585,15 @@ ${draft.slice(at2)}`, at2 + 1);
|
|
|
83240
83585
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
83241
83586
|
bold: true,
|
|
83242
83587
|
children: "Ralphy setup"
|
|
83243
|
-
}, undefined, false, undefined, this)
|
|
83588
|
+
}, undefined, false, undefined, this),
|
|
83589
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
83590
|
+
dimColor: true,
|
|
83591
|
+
children: [
|
|
83592
|
+
" \xB7 ",
|
|
83593
|
+
"v",
|
|
83594
|
+
VERSION
|
|
83595
|
+
]
|
|
83596
|
+
}, undefined, true, undefined, this)
|
|
83244
83597
|
]
|
|
83245
83598
|
}, undefined, true, undefined, this),
|
|
83246
83599
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
@@ -83301,6 +83654,9 @@ ${draft.slice(at2)}`, at2 + 1);
|
|
|
83301
83654
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
83302
83655
|
dimColor: true,
|
|
83303
83656
|
children: [
|
|
83657
|
+
" \xB7 ",
|
|
83658
|
+
"v",
|
|
83659
|
+
VERSION,
|
|
83304
83660
|
" \xB7 ",
|
|
83305
83661
|
mode,
|
|
83306
83662
|
" \xB7 step ",
|
|
@@ -83393,7 +83749,7 @@ function hintFor(kind) {
|
|
|
83393
83749
|
return `\u2191\u2193 to switch \xB7 enter to confirm and continue \xB7 ${nav}`;
|
|
83394
83750
|
}
|
|
83395
83751
|
if (kind === "multiselect") {
|
|
83396
|
-
return `\u2191\u2193 to move \xB7 space to
|
|
83752
|
+
return `\u2191\u2193 to move \xB7 space to select \xB7 enter to confirm \xB7 ${nav}`;
|
|
83397
83753
|
}
|
|
83398
83754
|
if (kind === "list") {
|
|
83399
83755
|
return `type + enter to add \xB7 empty enter to finish \xB7 ${nav}`;
|
|
@@ -83656,6 +84012,16 @@ function ChoicePrompt({
|
|
|
83656
84012
|
]
|
|
83657
84013
|
}, undefined, true, undefined, this);
|
|
83658
84014
|
}
|
|
84015
|
+
function ResumeOrFreshPrompt({
|
|
84016
|
+
onChoice
|
|
84017
|
+
}) {
|
|
84018
|
+
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(ChoicePrompt, {
|
|
84019
|
+
title: "Unfinished setup found",
|
|
84020
|
+
subtitle: "A previous setup session was interrupted \u2014 resume it or start over",
|
|
84021
|
+
options: RESUME_FRESH_OPTIONS,
|
|
84022
|
+
onChoice
|
|
84023
|
+
}, undefined, false, undefined, this);
|
|
84024
|
+
}
|
|
83659
84025
|
function EditOrExitPrompt({ onChoice }) {
|
|
83660
84026
|
return /* @__PURE__ */ jsx_dev_runtime.jsxDEV(ChoicePrompt, {
|
|
83661
84027
|
title: "WORKFLOW.md already exists",
|
|
@@ -83831,9 +84197,7 @@ function IndicatorBuilder({
|
|
|
83831
84197
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
83832
84198
|
dimColor: true,
|
|
83833
84199
|
children: [
|
|
83834
|
-
" \xB7 ",
|
|
83835
|
-
state.label,
|
|
83836
|
-
" \xB7 ",
|
|
84200
|
+
" \xB7 step ",
|
|
83837
84201
|
stateIndex + 1,
|
|
83838
84202
|
"/",
|
|
83839
84203
|
states.length
|
|
@@ -83841,6 +84205,16 @@ function IndicatorBuilder({
|
|
|
83841
84205
|
}, undefined, true, undefined, this)
|
|
83842
84206
|
]
|
|
83843
84207
|
}, undefined, true, undefined, this),
|
|
84208
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
84209
|
+
children: [
|
|
84210
|
+
" Configuring: ",
|
|
84211
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
84212
|
+
bold: true,
|
|
84213
|
+
color: "cyan",
|
|
84214
|
+
children: state.label
|
|
84215
|
+
}, undefined, false, undefined, this)
|
|
84216
|
+
]
|
|
84217
|
+
}, undefined, true, undefined, this),
|
|
83844
84218
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
83845
84219
|
dimColor: true,
|
|
83846
84220
|
children: [
|
|
@@ -83862,8 +84236,15 @@ function IndicatorBuilder({
|
|
|
83862
84236
|
children: phase === "type" ? /* @__PURE__ */ jsx_dev_runtime.jsxDEV(jsx_dev_runtime.Fragment, {
|
|
83863
84237
|
children: [
|
|
83864
84238
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
83865
|
-
children:
|
|
83866
|
-
|
|
84239
|
+
children: [
|
|
84240
|
+
"Choose a marker type for ",
|
|
84241
|
+
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(Text, {
|
|
84242
|
+
bold: true,
|
|
84243
|
+
children: state.label
|
|
84244
|
+
}, undefined, false, undefined, this),
|
|
84245
|
+
" (or skip):"
|
|
84246
|
+
]
|
|
84247
|
+
}, undefined, true, undefined, this),
|
|
83867
84248
|
/* @__PURE__ */ jsx_dev_runtime.jsxDEV(OptionList, {
|
|
83868
84249
|
options: typeChoices,
|
|
83869
84250
|
highlight: typeIndex
|
|
@@ -83908,8 +84289,9 @@ function IndicatorBuilder({
|
|
|
83908
84289
|
]
|
|
83909
84290
|
}, undefined, true, undefined, this);
|
|
83910
84291
|
}
|
|
83911
|
-
var import_react22, jsx_dev_runtime, REPO_ANSWER_IDS, MODE_OPTIONS, INDICATOR_OPTIONS, CONFIRM_OPTIONS, EDIT_EXIT_OPTIONS, RECREATE_EXIT_OPTIONS, MIGRATE_OPTIONS, CORE_STATES, CONFIRMATION_STATES, ALL_TYPES;
|
|
84292
|
+
var import_react22, jsx_dev_runtime, REPO_ANSWER_IDS, MODE_OPTIONS, INDICATOR_OPTIONS, CONFIRM_OPTIONS, RESUME_FRESH_OPTIONS, EDIT_EXIT_OPTIONS, RECREATE_EXIT_OPTIONS, MIGRATE_OPTIONS, CORE_STATES, CONFIRMATION_STATES, ALL_TYPES;
|
|
83912
84293
|
var init_SetupWizard = __esm(async () => {
|
|
84294
|
+
init_version();
|
|
83913
84295
|
init_wizard();
|
|
83914
84296
|
init_fields();
|
|
83915
84297
|
await init_build2();
|
|
@@ -83923,14 +84305,18 @@ var init_SetupWizard = __esm(async () => {
|
|
|
83923
84305
|
];
|
|
83924
84306
|
INDICATOR_OPTIONS = [
|
|
83925
84307
|
{ label: "None \u2014 configure later in WORKFLOW.md", value: "none" },
|
|
83926
|
-
{ label: "Status-based (Todo \u2192 In Progress \u2192 In Review)", value: "status-standard" },
|
|
83927
|
-
{ label: "Label-based (ralph:todo / in-progress / done)", value: "label-standard" },
|
|
83928
|
-
{ label: "Custom \u2014
|
|
84308
|
+
{ label: "Status-based preset (Todo \u2192 In Progress \u2192 In Review)", value: "status-standard" },
|
|
84309
|
+
{ label: "Label-based preset (ralph:todo / in-progress / done)", value: "label-standard" },
|
|
84310
|
+
{ label: "Custom \u2014 open a guided builder (enter opens it)", value: "custom" }
|
|
83929
84311
|
];
|
|
83930
84312
|
CONFIRM_OPTIONS = [
|
|
83931
84313
|
{ label: "Yes", value: "true" },
|
|
83932
84314
|
{ label: "No", value: "false" }
|
|
83933
84315
|
];
|
|
84316
|
+
RESUME_FRESH_OPTIONS = [
|
|
84317
|
+
{ label: "Resume where I left off", value: "resume" },
|
|
84318
|
+
{ label: "Start fresh (discard the saved answers)", value: "fresh" }
|
|
84319
|
+
];
|
|
83934
84320
|
EDIT_EXIT_OPTIONS = [
|
|
83935
84321
|
{ label: "Edit it with the setup wizard", value: "edit" },
|
|
83936
84322
|
{ label: "Exit without changes", value: "exit" }
|
|
@@ -84056,7 +84442,7 @@ var init_migrations = __esm(() => {
|
|
|
84056
84442
|
{
|
|
84057
84443
|
version: 3,
|
|
84058
84444
|
description: "The per-workflow `linear.assignee` setting is replaced by a global " + "`linear.filter` expression (e.g. `assignee = me`) applied to every " + "ticket fetch. Existing `assignee` values are folded in automatically; " + "note that an empty filter now defaults to `assignee = me` (it previously " + "meant unassigned-only).",
|
|
84059
|
-
fields: ["linear.
|
|
84445
|
+
fields: ["linear.assigneeChoice", "linear.assigneeValue"]
|
|
84060
84446
|
},
|
|
84061
84447
|
{
|
|
84062
84448
|
version: 4,
|
|
@@ -84067,6 +84453,129 @@ var init_migrations = __esm(() => {
|
|
|
84067
84453
|
LATEST_MIGRATION_VERSION = MIGRATIONS.reduce((max2, migration) => Math.max(max2, migration.version), 0);
|
|
84068
84454
|
});
|
|
84069
84455
|
|
|
84456
|
+
// apps/init/src/project-detect.ts
|
|
84457
|
+
import { join as join6 } from "path";
|
|
84458
|
+
async function readPackageJson(projectRoot) {
|
|
84459
|
+
const file2 = Bun.file(join6(projectRoot, "package.json"));
|
|
84460
|
+
if (!await file2.exists())
|
|
84461
|
+
return null;
|
|
84462
|
+
try {
|
|
84463
|
+
return JSON.parse(await file2.text());
|
|
84464
|
+
} catch {
|
|
84465
|
+
return null;
|
|
84466
|
+
}
|
|
84467
|
+
}
|
|
84468
|
+
async function fileExists(projectRoot, name) {
|
|
84469
|
+
return Bun.file(join6(projectRoot, name)).exists();
|
|
84470
|
+
}
|
|
84471
|
+
async function detectRunPrefix(projectRoot) {
|
|
84472
|
+
const lockfiles = [
|
|
84473
|
+
{ file: "bun.lock", prefix: "bun run" },
|
|
84474
|
+
{ file: "bun.lockb", prefix: "bun run" },
|
|
84475
|
+
{ file: "pnpm-lock.yaml", prefix: "pnpm run" },
|
|
84476
|
+
{ file: "yarn.lock", prefix: "yarn run" },
|
|
84477
|
+
{ file: "package-lock.json", prefix: "npm run" }
|
|
84478
|
+
];
|
|
84479
|
+
for (const { file: file2, prefix } of lockfiles) {
|
|
84480
|
+
if (await fileExists(projectRoot, file2))
|
|
84481
|
+
return prefix;
|
|
84482
|
+
}
|
|
84483
|
+
return "bun run";
|
|
84484
|
+
}
|
|
84485
|
+
async function detectCommandsFromPackageJson(projectRoot) {
|
|
84486
|
+
const pkg = await readPackageJson(projectRoot);
|
|
84487
|
+
const scripts = pkg?.scripts ?? {};
|
|
84488
|
+
const runPrefix = await detectRunPrefix(projectRoot);
|
|
84489
|
+
const commands = {};
|
|
84490
|
+
for (const { field, scripts: names } of COMMAND_FIELD_SCRIPTS) {
|
|
84491
|
+
const name = names.find((candidate) => typeof scripts[candidate] === "string" && scripts[candidate] !== "");
|
|
84492
|
+
if (name)
|
|
84493
|
+
commands[field] = `${runPrefix} ${name}`;
|
|
84494
|
+
}
|
|
84495
|
+
return commands;
|
|
84496
|
+
}
|
|
84497
|
+
async function detectFramework(projectRoot) {
|
|
84498
|
+
const pkg = await readPackageJson(projectRoot);
|
|
84499
|
+
const dependencies = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
84500
|
+
const detected = [];
|
|
84501
|
+
if (await fileExists(projectRoot, "bun.lock") || await fileExists(projectRoot, "bun.lockb")) {
|
|
84502
|
+
detected.push("Bun");
|
|
84503
|
+
}
|
|
84504
|
+
if (await fileExists(projectRoot, "nx.json"))
|
|
84505
|
+
detected.push("Nx");
|
|
84506
|
+
for (const { dependency, name } of FRAMEWORK_MARKERS) {
|
|
84507
|
+
if (dependencies[dependency] && !detected.includes(name)) {
|
|
84508
|
+
detected.push(name);
|
|
84509
|
+
break;
|
|
84510
|
+
}
|
|
84511
|
+
}
|
|
84512
|
+
return detected.length > 0 ? detected.join(" + ") : undefined;
|
|
84513
|
+
}
|
|
84514
|
+
async function gitText(projectRoot, args) {
|
|
84515
|
+
try {
|
|
84516
|
+
const proc = Bun.spawn({
|
|
84517
|
+
cmd: ["git", ...args],
|
|
84518
|
+
cwd: projectRoot,
|
|
84519
|
+
stdout: "pipe",
|
|
84520
|
+
stderr: "ignore",
|
|
84521
|
+
stdin: "ignore"
|
|
84522
|
+
});
|
|
84523
|
+
const out = await new Response(proc.stdout).text();
|
|
84524
|
+
await proc.exited;
|
|
84525
|
+
return out.trim();
|
|
84526
|
+
} catch {
|
|
84527
|
+
return "";
|
|
84528
|
+
}
|
|
84529
|
+
}
|
|
84530
|
+
async function detectDefaultBranch(projectRoot) {
|
|
84531
|
+
const head3 = await gitText(projectRoot, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
|
|
84532
|
+
if (head3) {
|
|
84533
|
+
const branch = head3.replace(/^origin\//, "").trim();
|
|
84534
|
+
if (branch)
|
|
84535
|
+
return branch;
|
|
84536
|
+
}
|
|
84537
|
+
for (const candidate of ["main", "master"]) {
|
|
84538
|
+
const verified = await gitText(projectRoot, ["rev-parse", "--verify", "--quiet", candidate]);
|
|
84539
|
+
if (verified)
|
|
84540
|
+
return candidate;
|
|
84541
|
+
}
|
|
84542
|
+
return;
|
|
84543
|
+
}
|
|
84544
|
+
async function detectInitialValues(projectRoot) {
|
|
84545
|
+
const values2 = { ...await detectCommandsFromPackageJson(projectRoot) };
|
|
84546
|
+
const framework = await detectFramework(projectRoot);
|
|
84547
|
+
if (framework)
|
|
84548
|
+
values2["project.framework"] = framework;
|
|
84549
|
+
const defaultBranch = await detectDefaultBranch(projectRoot);
|
|
84550
|
+
if (defaultBranch)
|
|
84551
|
+
values2["prBaseBranch"] = defaultBranch;
|
|
84552
|
+
return values2;
|
|
84553
|
+
}
|
|
84554
|
+
var COMMAND_FIELD_SCRIPTS, FRAMEWORK_MARKERS;
|
|
84555
|
+
var init_project_detect = __esm(() => {
|
|
84556
|
+
COMMAND_FIELD_SCRIPTS = [
|
|
84557
|
+
{ field: "commands.test", scripts: ["test"] },
|
|
84558
|
+
{ field: "commands.lint", scripts: ["lint"] },
|
|
84559
|
+
{ field: "commands.build", scripts: ["build"] },
|
|
84560
|
+
{ field: "commands.typecheck", scripts: ["typecheck", "type-check", "tsc"] }
|
|
84561
|
+
];
|
|
84562
|
+
FRAMEWORK_MARKERS = [
|
|
84563
|
+
{ dependency: "next", name: "Next.js" },
|
|
84564
|
+
{ dependency: "@remix-run/react", name: "Remix" },
|
|
84565
|
+
{ dependency: "@nestjs/core", name: "NestJS" },
|
|
84566
|
+
{ dependency: "@angular/core", name: "Angular" },
|
|
84567
|
+
{ dependency: "@sveltejs/kit", name: "SvelteKit" },
|
|
84568
|
+
{ dependency: "svelte", name: "Svelte" },
|
|
84569
|
+
{ dependency: "nuxt", name: "Nuxt" },
|
|
84570
|
+
{ dependency: "vue", name: "Vue" },
|
|
84571
|
+
{ dependency: "astro", name: "Astro" },
|
|
84572
|
+
{ dependency: "react", name: "React" },
|
|
84573
|
+
{ dependency: "@nestjs/common", name: "NestJS" },
|
|
84574
|
+
{ dependency: "fastify", name: "Fastify" },
|
|
84575
|
+
{ dependency: "express", name: "Express" }
|
|
84576
|
+
];
|
|
84577
|
+
});
|
|
84578
|
+
|
|
84070
84579
|
// apps/init/src/index.ts
|
|
84071
84580
|
var exports_src = {};
|
|
84072
84581
|
__export(exports_src, {
|
|
@@ -84074,6 +84583,33 @@ __export(exports_src, {
|
|
|
84074
84583
|
maybeRunSetupWizard: () => maybeRunSetupWizard,
|
|
84075
84584
|
main: () => main
|
|
84076
84585
|
});
|
|
84586
|
+
async function readSetupBackup(projectRoot) {
|
|
84587
|
+
const file2 = Bun.file(setupBackupPath());
|
|
84588
|
+
if (!await file2.exists())
|
|
84589
|
+
return null;
|
|
84590
|
+
try {
|
|
84591
|
+
const data = JSON.parse(await file2.text());
|
|
84592
|
+
if (data.projectRoot !== projectRoot)
|
|
84593
|
+
return null;
|
|
84594
|
+
if (data.mode !== "quick" && data.mode !== "permissive" && data.mode !== "customized") {
|
|
84595
|
+
return null;
|
|
84596
|
+
}
|
|
84597
|
+
if (!data.values || typeof data.values !== "object")
|
|
84598
|
+
return null;
|
|
84599
|
+
return { mode: data.mode, values: data.values };
|
|
84600
|
+
} catch {
|
|
84601
|
+
return null;
|
|
84602
|
+
}
|
|
84603
|
+
}
|
|
84604
|
+
async function writeSetupBackup(projectRoot, mode, values2) {
|
|
84605
|
+
const backup = { projectRoot, mode, values: values2 };
|
|
84606
|
+
await Bun.write(setupBackupPath(), JSON.stringify(backup, null, 2));
|
|
84607
|
+
}
|
|
84608
|
+
async function clearSetupBackup() {
|
|
84609
|
+
const file2 = Bun.file(setupBackupPath());
|
|
84610
|
+
if (await file2.exists())
|
|
84611
|
+
await file2.delete();
|
|
84612
|
+
}
|
|
84077
84613
|
function withDetectedRepo(initial2, repo) {
|
|
84078
84614
|
if (!repo)
|
|
84079
84615
|
return initial2;
|
|
@@ -84094,7 +84630,7 @@ async function runSetupWizard(projectRoot, options = {}) {
|
|
|
84094
84630
|
let markdown = null;
|
|
84095
84631
|
const buildMarkdown = options.existing ? (answers, bodyOverride) => applyAnswersToWorkflow(options.existing, answers, bodyOverride) : undefined;
|
|
84096
84632
|
const initialBody = workflowBody(options.existing ?? DEFAULT_WORKFLOW_MD);
|
|
84097
|
-
const initialValues = withDetectedRepo(options.initialValues, options.detectedRepo);
|
|
84633
|
+
const initialValues = options.resumeValues ?? withDetectedRepo(options.initialValues, options.detectedRepo);
|
|
84098
84634
|
clearScreen2();
|
|
84099
84635
|
const { waitUntilExit } = render_default(import_react23.createElement(SetupWizard, {
|
|
84100
84636
|
onComplete: (md) => {
|
|
@@ -84108,21 +84644,33 @@ async function runSetupWizard(projectRoot, options = {}) {
|
|
|
84108
84644
|
...initialValues ? { initialValues } : {},
|
|
84109
84645
|
...options.onlyFields ? { onlyFields: options.onlyFields } : {},
|
|
84110
84646
|
...options.detectedRepo ? { detectedRepo: { owner: options.detectedRepo.owner, name: options.detectedRepo.name } } : {},
|
|
84111
|
-
...buildMarkdown ? { buildMarkdown } : {}
|
|
84647
|
+
...buildMarkdown ? { buildMarkdown } : {},
|
|
84648
|
+
...options.trackBackup ? {
|
|
84649
|
+
onAnswersChange: (state) => {
|
|
84650
|
+
writeSetupBackup(projectRoot, state.mode, state.values);
|
|
84651
|
+
}
|
|
84652
|
+
} : {}
|
|
84112
84653
|
}));
|
|
84113
84654
|
await waitUntilExit();
|
|
84114
84655
|
if (markdown === null)
|
|
84115
84656
|
return false;
|
|
84116
|
-
|
|
84657
|
+
const { markdown: healed } = normalizeWorkflowMarkdown(markdown);
|
|
84658
|
+
await Bun.write(workflowPath(projectRoot, options.workflowFile), healed);
|
|
84659
|
+
await clearSetupBackup();
|
|
84117
84660
|
return true;
|
|
84118
84661
|
}
|
|
84119
|
-
async function maybeRunSetupWizard(projectRoot) {
|
|
84662
|
+
async function maybeRunSetupWizard(projectRoot, workflowFile) {
|
|
84120
84663
|
const root = projectRoot ?? await findProjectRoot();
|
|
84121
|
-
if (await Bun.file(workflowPath(root)).exists())
|
|
84664
|
+
if (await Bun.file(workflowPath(root, workflowFile)).exists())
|
|
84122
84665
|
return false;
|
|
84123
84666
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
84124
84667
|
return false;
|
|
84125
|
-
|
|
84668
|
+
const detected = await detectInitialValues(root);
|
|
84669
|
+
return runSetupWizard(root, {
|
|
84670
|
+
trackBackup: true,
|
|
84671
|
+
...workflowFile ? { workflowFile } : {},
|
|
84672
|
+
...Object.keys(detected).length > 0 ? { initialValues: detected } : {}
|
|
84673
|
+
});
|
|
84126
84674
|
}
|
|
84127
84675
|
function initialValuesFromConfig(config2) {
|
|
84128
84676
|
const values2 = {};
|
|
@@ -84149,8 +84697,16 @@ function initialValuesFromConfig(config2) {
|
|
|
84149
84697
|
values2["useWorktree"] = config2.useWorktree;
|
|
84150
84698
|
if (config2.linear.team)
|
|
84151
84699
|
values2["linear.team"] = config2.linear.team;
|
|
84152
|
-
if (config2.linear.filter)
|
|
84153
|
-
|
|
84700
|
+
if (config2.linear.filter) {
|
|
84701
|
+
const match = /^assignee\s*=\s*(.+)$/i.exec(config2.linear.filter.trim());
|
|
84702
|
+
const assignee = match ? match[1].trim() : "";
|
|
84703
|
+
if (assignee === "me" || assignee === "any" || assignee === "unassigned") {
|
|
84704
|
+
values2["linear.assigneeChoice"] = assignee;
|
|
84705
|
+
} else if (assignee !== "") {
|
|
84706
|
+
values2["linear.assigneeChoice"] = "other";
|
|
84707
|
+
values2["linear.assigneeValue"] = assignee;
|
|
84708
|
+
}
|
|
84709
|
+
}
|
|
84154
84710
|
return values2;
|
|
84155
84711
|
}
|
|
84156
84712
|
async function promptEditOrExit() {
|
|
@@ -84164,6 +84720,17 @@ async function promptEditOrExit() {
|
|
|
84164
84720
|
await waitUntilExit();
|
|
84165
84721
|
return choice;
|
|
84166
84722
|
}
|
|
84723
|
+
async function promptResumeOrFresh() {
|
|
84724
|
+
let choice = "fresh";
|
|
84725
|
+
clearScreen2();
|
|
84726
|
+
const { waitUntilExit } = render_default(import_react23.createElement(ResumeOrFreshPrompt, {
|
|
84727
|
+
onChoice: (value) => {
|
|
84728
|
+
choice = value;
|
|
84729
|
+
}
|
|
84730
|
+
}));
|
|
84731
|
+
await waitUntilExit();
|
|
84732
|
+
return choice;
|
|
84733
|
+
}
|
|
84167
84734
|
async function promptRecreateOrExit() {
|
|
84168
84735
|
let choice = "exit";
|
|
84169
84736
|
clearScreen2();
|
|
@@ -84189,14 +84756,16 @@ async function promptMigrate(fromVersion) {
|
|
|
84189
84756
|
await waitUntilExit();
|
|
84190
84757
|
return choice;
|
|
84191
84758
|
}
|
|
84192
|
-
async function editExisting(projectRoot, path, config2, onlyFields) {
|
|
84759
|
+
async function editExisting(projectRoot, path, config2, workflowFile, onlyFields) {
|
|
84193
84760
|
const existing = await Bun.file(path).text();
|
|
84194
84761
|
const detectedRepo = await detectRepoIdentity(projectRoot);
|
|
84762
|
+
const detected = await detectInitialValues(projectRoot);
|
|
84195
84763
|
const wrote = await runSetupWizard(projectRoot, {
|
|
84196
84764
|
existing,
|
|
84197
84765
|
initialMode: "customized",
|
|
84198
|
-
initialValues: initialValuesFromConfig(config2),
|
|
84766
|
+
initialValues: { ...detected, ...initialValuesFromConfig(config2) },
|
|
84199
84767
|
...detectedRepo ? { detectedRepo } : {},
|
|
84768
|
+
...workflowFile ? { workflowFile } : {},
|
|
84200
84769
|
...onlyFields ? { onlyFields } : {}
|
|
84201
84770
|
});
|
|
84202
84771
|
process.stdout.write(wrote ? `
|
|
@@ -84212,8 +84781,9 @@ async function main(argv) {
|
|
|
84212
84781
|
`);
|
|
84213
84782
|
return 0;
|
|
84214
84783
|
}
|
|
84215
|
-
const projectRoot =
|
|
84216
|
-
const
|
|
84784
|
+
const { projectRoot: rootOverride, workflowFile } = parseWorkflowPathArgs(argv);
|
|
84785
|
+
const projectRoot = rootOverride ?? await findProjectRoot();
|
|
84786
|
+
const path = workflowPath(projectRoot, workflowFile);
|
|
84217
84787
|
const exists2 = await Bun.file(path).exists();
|
|
84218
84788
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
84219
84789
|
if (exists2) {
|
|
@@ -84224,7 +84794,7 @@ async function main(argv) {
|
|
|
84224
84794
|
}
|
|
84225
84795
|
let config2;
|
|
84226
84796
|
try {
|
|
84227
|
-
({ config: config2 } = await loadWorkflow(projectRoot));
|
|
84797
|
+
({ config: config2 } = await loadWorkflow(projectRoot, workflowFile));
|
|
84228
84798
|
} catch {
|
|
84229
84799
|
const choice2 = await promptRecreateOrExit();
|
|
84230
84800
|
if (choice2 === "exit") {
|
|
@@ -84233,7 +84803,10 @@ async function main(argv) {
|
|
|
84233
84803
|
return 0;
|
|
84234
84804
|
}
|
|
84235
84805
|
const detectedRepo2 = await detectRepoIdentity(projectRoot);
|
|
84236
|
-
const wrote2 = await runSetupWizard(projectRoot,
|
|
84806
|
+
const wrote2 = await runSetupWizard(projectRoot, {
|
|
84807
|
+
...detectedRepo2 ? { detectedRepo: detectedRepo2 } : {},
|
|
84808
|
+
...workflowFile ? { workflowFile } : {}
|
|
84809
|
+
});
|
|
84237
84810
|
process.stdout.write(wrote2 ? `
|
|
84238
84811
|
\u2713 Recreated ${path}
|
|
84239
84812
|
` : `
|
|
@@ -84249,7 +84822,7 @@ Setup cancelled \u2014 no file written.
|
|
|
84249
84822
|
return 0;
|
|
84250
84823
|
}
|
|
84251
84824
|
const onlyFields = choice2 === "diff" ? fieldsAddedSince(config2.version) : undefined;
|
|
84252
|
-
return editExisting(projectRoot, path, config2, onlyFields);
|
|
84825
|
+
return editExisting(projectRoot, path, config2, workflowFile, onlyFields);
|
|
84253
84826
|
}
|
|
84254
84827
|
const choice = await promptEditOrExit();
|
|
84255
84828
|
if (choice === "exit") {
|
|
@@ -84257,17 +84830,42 @@ Setup cancelled \u2014 no file written.
|
|
|
84257
84830
|
`);
|
|
84258
84831
|
return 0;
|
|
84259
84832
|
}
|
|
84260
|
-
return editExisting(projectRoot, path, config2);
|
|
84833
|
+
return editExisting(projectRoot, path, config2, workflowFile);
|
|
84261
84834
|
}
|
|
84262
84835
|
if (!interactive) {
|
|
84263
84836
|
const { ensureWorkflow: ensureWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
|
|
84264
|
-
const written = await ensureWorkflow2(projectRoot);
|
|
84837
|
+
const written = await ensureWorkflow2(projectRoot, workflowFile);
|
|
84265
84838
|
process.stdout.write(`Non-interactive shell \u2014 wrote default WORKFLOW.md: ${written}
|
|
84266
84839
|
`);
|
|
84267
84840
|
return 0;
|
|
84268
84841
|
}
|
|
84842
|
+
const backup = await readSetupBackup(projectRoot);
|
|
84843
|
+
if (backup) {
|
|
84844
|
+
const choice = await promptResumeOrFresh();
|
|
84845
|
+
if (choice === "resume") {
|
|
84846
|
+
const wrote2 = await runSetupWizard(projectRoot, {
|
|
84847
|
+
initialMode: backup.mode,
|
|
84848
|
+
resumeValues: backup.values,
|
|
84849
|
+
trackBackup: true,
|
|
84850
|
+
...workflowFile ? { workflowFile } : {}
|
|
84851
|
+
});
|
|
84852
|
+
process.stdout.write(wrote2 ? `
|
|
84853
|
+
\u2713 Created ${path}
|
|
84854
|
+
` : `
|
|
84855
|
+
Setup cancelled \u2014 no file written.
|
|
84856
|
+
`);
|
|
84857
|
+
return 0;
|
|
84858
|
+
}
|
|
84859
|
+
await clearSetupBackup();
|
|
84860
|
+
}
|
|
84269
84861
|
const detectedRepo = await detectRepoIdentity(projectRoot);
|
|
84270
|
-
const
|
|
84862
|
+
const detected = await detectInitialValues(projectRoot);
|
|
84863
|
+
const wrote = await runSetupWizard(projectRoot, {
|
|
84864
|
+
trackBackup: true,
|
|
84865
|
+
...detectedRepo ? { detectedRepo } : {},
|
|
84866
|
+
...workflowFile ? { workflowFile } : {},
|
|
84867
|
+
...Object.keys(detected).length > 0 ? { initialValues: detected } : {}
|
|
84868
|
+
});
|
|
84271
84869
|
process.stdout.write(wrote ? `
|
|
84272
84870
|
\u2713 Created ${path}
|
|
84273
84871
|
` : `
|
|
@@ -84278,10 +84876,12 @@ Setup cancelled \u2014 no file written.
|
|
|
84278
84876
|
var import_react23, INIT_HELP;
|
|
84279
84877
|
var init_src4 = __esm(async () => {
|
|
84280
84878
|
init_paths();
|
|
84879
|
+
init_common_args();
|
|
84281
84880
|
init_workflow();
|
|
84282
84881
|
init_wizard();
|
|
84283
84882
|
init_repo();
|
|
84284
84883
|
init_migrations();
|
|
84884
|
+
init_project_detect();
|
|
84285
84885
|
await __promiseAll([
|
|
84286
84886
|
init_build2(),
|
|
84287
84887
|
init_SetupWizard()
|
|
@@ -84290,10 +84890,15 @@ var init_src4 = __esm(async () => {
|
|
|
84290
84890
|
INIT_HELP = [
|
|
84291
84891
|
"ralphy init \u2014 create or edit WORKFLOW.md with an interactive setup wizard",
|
|
84292
84892
|
"",
|
|
84293
|
-
"Usage: ralphy init",
|
|
84893
|
+
"Usage: ralphy init [options]",
|
|
84294
84894
|
"",
|
|
84295
84895
|
"Runs a short wizard (quick / permissive / customized) and writes WORKFLOW.md",
|
|
84296
|
-
"to the project root. If WORKFLOW.md already exists, offers to edit it."
|
|
84896
|
+
"to the project root. If WORKFLOW.md already exists, offers to edit it.",
|
|
84897
|
+
"",
|
|
84898
|
+
"Options:",
|
|
84899
|
+
" --project-root <path> Directory to treat as the project root (default: detected)",
|
|
84900
|
+
" --workflow <path> Path to read / write WORKFLOW.md (default: <project>/WORKFLOW.md)",
|
|
84901
|
+
" --help, -h Show this help message"
|
|
84297
84902
|
].join(`
|
|
84298
84903
|
`);
|
|
84299
84904
|
});
|
|
@@ -84380,18 +84985,18 @@ var init_context = __esm(() => {
|
|
|
84380
84985
|
});
|
|
84381
84986
|
|
|
84382
84987
|
// packages/core/src/layout.ts
|
|
84383
|
-
import { join as
|
|
84988
|
+
import { join as join7 } from "path";
|
|
84384
84989
|
function projectLayout(root) {
|
|
84385
|
-
const statesDir =
|
|
84386
|
-
const tasksDir =
|
|
84990
|
+
const statesDir = join7(root, ".ralph", "tasks");
|
|
84991
|
+
const tasksDir = join7(root, "openspec", "changes");
|
|
84387
84992
|
return {
|
|
84388
84993
|
root,
|
|
84389
84994
|
statesDir,
|
|
84390
84995
|
tasksDir,
|
|
84391
|
-
agentStateFile:
|
|
84392
|
-
changeDir: (name) =>
|
|
84393
|
-
taskStateDir: (name) =>
|
|
84394
|
-
stateFile: (name) =>
|
|
84996
|
+
agentStateFile: join7(root, ".ralph", "agent-state.json"),
|
|
84997
|
+
changeDir: (name) => join7(tasksDir, name),
|
|
84998
|
+
taskStateDir: (name) => join7(statesDir, name),
|
|
84999
|
+
stateFile: (name) => join7(statesDir, name, STATE_FILE)
|
|
84395
85000
|
};
|
|
84396
85001
|
}
|
|
84397
85002
|
var STATE_FILE = ".ralph-state.json", GAVEUP_COUNT_FILE = ".ralph-gaveup-count";
|
|
@@ -84400,13 +85005,13 @@ var init_layout = __esm(() => {
|
|
|
84400
85005
|
});
|
|
84401
85006
|
|
|
84402
85007
|
// packages/openspec/src/openspec-bin.ts
|
|
84403
|
-
import { dirname as dirname3, join as
|
|
85008
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
84404
85009
|
function findPackageRoot(startDir) {
|
|
84405
85010
|
let dir = startDir;
|
|
84406
85011
|
for (let i = 0;i < 8; i++) {
|
|
84407
|
-
if (Bun.file(
|
|
85012
|
+
if (Bun.file(join8(dir, "package.json")).size >= 0) {
|
|
84408
85013
|
try {
|
|
84409
|
-
if (Bun.file(
|
|
85014
|
+
if (Bun.file(join8(dir, "package.json")).size > 0)
|
|
84410
85015
|
return dir;
|
|
84411
85016
|
} catch {}
|
|
84412
85017
|
}
|
|
@@ -84442,11 +85047,11 @@ function ensureOpenspecInstalled(fromDir, runner) {
|
|
|
84442
85047
|
function resolveOpenspecBin(fromDir, runner = bunInstallRunner) {
|
|
84443
85048
|
try {
|
|
84444
85049
|
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
84445
|
-
return
|
|
85050
|
+
return join8(dirname3(pkgJsonPath), "bin", "openspec.js");
|
|
84446
85051
|
} catch {
|
|
84447
85052
|
ensureOpenspecInstalled(fromDir, runner);
|
|
84448
85053
|
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
84449
|
-
return
|
|
85054
|
+
return join8(dirname3(pkgJsonPath), "bin", "openspec.js");
|
|
84450
85055
|
}
|
|
84451
85056
|
}
|
|
84452
85057
|
var bunInstallRunner;
|
|
@@ -84468,7 +85073,7 @@ var init_openspec_bin = __esm(() => {
|
|
|
84468
85073
|
});
|
|
84469
85074
|
|
|
84470
85075
|
// packages/openspec/src/openspec-change-store.ts
|
|
84471
|
-
import { dirname as dirname4, join as
|
|
85076
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
84472
85077
|
import { readdir, mkdir as mkdir2 } from "fs/promises";
|
|
84473
85078
|
function runOpenspec(args, options = {}) {
|
|
84474
85079
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
@@ -84538,7 +85143,7 @@ class OpenSpecChangeStore {
|
|
|
84538
85143
|
}
|
|
84539
85144
|
}
|
|
84540
85145
|
getChangeDirectory(name) {
|
|
84541
|
-
return
|
|
85146
|
+
return join9("openspec", "changes", name);
|
|
84542
85147
|
}
|
|
84543
85148
|
async listChanges() {
|
|
84544
85149
|
const result2 = runOpenspec(["list", "--json"]);
|
|
@@ -84552,7 +85157,7 @@ class OpenSpecChangeStore {
|
|
|
84552
85157
|
}
|
|
84553
85158
|
} catch {}
|
|
84554
85159
|
}
|
|
84555
|
-
const changesDir =
|
|
85160
|
+
const changesDir = join9("openspec", "changes");
|
|
84556
85161
|
try {
|
|
84557
85162
|
const entries = await readdir(changesDir, { withFileTypes: true });
|
|
84558
85163
|
return entries.filter((entry) => entry.isDirectory() && entry.name !== "archive").map((entry) => entry.name);
|
|
@@ -84561,18 +85166,18 @@ class OpenSpecChangeStore {
|
|
|
84561
85166
|
}
|
|
84562
85167
|
}
|
|
84563
85168
|
async readTaskList(name) {
|
|
84564
|
-
const file2 = Bun.file(
|
|
85169
|
+
const file2 = Bun.file(join9("openspec", "changes", name, "tasks.md"));
|
|
84565
85170
|
if (!await file2.exists())
|
|
84566
85171
|
return "";
|
|
84567
85172
|
return await file2.text();
|
|
84568
85173
|
}
|
|
84569
85174
|
async writeTaskList(name, content) {
|
|
84570
|
-
const path =
|
|
85175
|
+
const path = join9("openspec", "changes", name, "tasks.md");
|
|
84571
85176
|
await mkdir2(dirname4(path), { recursive: true });
|
|
84572
85177
|
await Bun.write(path, content);
|
|
84573
85178
|
}
|
|
84574
85179
|
async appendSteering(name, message) {
|
|
84575
|
-
const path =
|
|
85180
|
+
const path = join9("openspec", "changes", name, "steering.md");
|
|
84576
85181
|
const file2 = Bun.file(path);
|
|
84577
85182
|
const existing = await file2.exists() ? await file2.text() : null;
|
|
84578
85183
|
const updated = existing ? `${message}
|
|
@@ -84584,7 +85189,7 @@ ${existing.trimStart()}` : `${message}
|
|
|
84584
85189
|
const firstLine = message.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? message.trim();
|
|
84585
85190
|
if (firstLine.length === 0)
|
|
84586
85191
|
return;
|
|
84587
|
-
const tasksPath =
|
|
85192
|
+
const tasksPath = join9("openspec", "changes", name, "tasks.md");
|
|
84588
85193
|
const tasksFile = Bun.file(tasksPath);
|
|
84589
85194
|
const existingTasks = await tasksFile.exists() ? await tasksFile.text() : "";
|
|
84590
85195
|
const taskLine = `- [ ] Address steering: ${firstLine}`;
|
|
@@ -84717,182 +85322,6 @@ var init_output2 = __esm(() => {
|
|
|
84717
85322
|
};
|
|
84718
85323
|
});
|
|
84719
85324
|
|
|
84720
|
-
// packages/cli-args/src/common-args.ts
|
|
84721
|
-
import { resolve as resolve3 } from "path";
|
|
84722
|
-
function initialCommonArgs() {
|
|
84723
|
-
return {
|
|
84724
|
-
engine: "claude",
|
|
84725
|
-
model: "opus",
|
|
84726
|
-
engineSet: false,
|
|
84727
|
-
maxIterations: 0,
|
|
84728
|
-
maxCostUsd: 0,
|
|
84729
|
-
maxRuntimeMinutes: 0,
|
|
84730
|
-
maxConsecutiveFailures: 5,
|
|
84731
|
-
delay: 0,
|
|
84732
|
-
log: false,
|
|
84733
|
-
verbose: false,
|
|
84734
|
-
projectRoot: undefined,
|
|
84735
|
-
workflowFile: undefined,
|
|
84736
|
-
name: "",
|
|
84737
|
-
prompt: "",
|
|
84738
|
-
fromAgent: false
|
|
84739
|
-
};
|
|
84740
|
-
}
|
|
84741
|
-
function applyValueOption(option, args, raw) {
|
|
84742
|
-
const setter = VALUE_SETTERS[option.argKey];
|
|
84743
|
-
if (!setter)
|
|
84744
|
-
throw new Error("no value setter registered for CLI option");
|
|
84745
|
-
setter(args, raw);
|
|
84746
|
-
}
|
|
84747
|
-
function applyBooleanOption(option, args) {
|
|
84748
|
-
const setter = BOOLEAN_SETTERS[option.argKey];
|
|
84749
|
-
if (!setter)
|
|
84750
|
-
throw new Error("no boolean setter registered for CLI option");
|
|
84751
|
-
setter(args);
|
|
84752
|
-
}
|
|
84753
|
-
function emptyParseState() {
|
|
84754
|
-
return {
|
|
84755
|
-
pendingOption: null,
|
|
84756
|
-
expectClaudeModel: false,
|
|
84757
|
-
expectProjectRoot: false,
|
|
84758
|
-
expectWorkflow: false,
|
|
84759
|
-
expectName: false,
|
|
84760
|
-
expectPrompt: false,
|
|
84761
|
-
expectPromptFile: false,
|
|
84762
|
-
promptFilePath: null
|
|
84763
|
-
};
|
|
84764
|
-
}
|
|
84765
|
-
function parseCommonArg(arg, args, state) {
|
|
84766
|
-
if (state.pendingOption) {
|
|
84767
|
-
applyValueOption(state.pendingOption, args, arg);
|
|
84768
|
-
state.pendingOption = null;
|
|
84769
|
-
return true;
|
|
84770
|
-
}
|
|
84771
|
-
if (state.expectClaudeModel) {
|
|
84772
|
-
state.expectClaudeModel = false;
|
|
84773
|
-
if (VALID_MODELS.has(arg)) {
|
|
84774
|
-
args.model = arg;
|
|
84775
|
-
return true;
|
|
84776
|
-
}
|
|
84777
|
-
}
|
|
84778
|
-
if (state.expectProjectRoot) {
|
|
84779
|
-
args.projectRoot = arg;
|
|
84780
|
-
state.expectProjectRoot = false;
|
|
84781
|
-
return true;
|
|
84782
|
-
}
|
|
84783
|
-
if (state.expectWorkflow) {
|
|
84784
|
-
args.workflowFile = resolve3(arg);
|
|
84785
|
-
state.expectWorkflow = false;
|
|
84786
|
-
return true;
|
|
84787
|
-
}
|
|
84788
|
-
if (state.expectName) {
|
|
84789
|
-
args.name = arg;
|
|
84790
|
-
state.expectName = false;
|
|
84791
|
-
return true;
|
|
84792
|
-
}
|
|
84793
|
-
if (state.expectPrompt) {
|
|
84794
|
-
args.prompt = arg;
|
|
84795
|
-
state.promptFilePath = null;
|
|
84796
|
-
state.expectPrompt = false;
|
|
84797
|
-
return true;
|
|
84798
|
-
}
|
|
84799
|
-
if (state.expectPromptFile) {
|
|
84800
|
-
state.promptFilePath = arg;
|
|
84801
|
-
state.expectPromptFile = false;
|
|
84802
|
-
return true;
|
|
84803
|
-
}
|
|
84804
|
-
const option = OPTION_BY_FLAG.get(arg);
|
|
84805
|
-
if (option) {
|
|
84806
|
-
if (option.kind === "boolean")
|
|
84807
|
-
applyBooleanOption(option, args);
|
|
84808
|
-
else
|
|
84809
|
-
state.pendingOption = option;
|
|
84810
|
-
return true;
|
|
84811
|
-
}
|
|
84812
|
-
switch (arg) {
|
|
84813
|
-
case "--claude":
|
|
84814
|
-
if (args.engineSet && args.engine !== "claude") {
|
|
84815
|
-
throw new Error("Choose only one engine flag: --claude or --codex");
|
|
84816
|
-
}
|
|
84817
|
-
args.engine = "claude";
|
|
84818
|
-
args.engineSet = true;
|
|
84819
|
-
state.expectClaudeModel = true;
|
|
84820
|
-
return true;
|
|
84821
|
-
case "--codex":
|
|
84822
|
-
if (args.engineSet && args.engine !== "codex") {
|
|
84823
|
-
throw new Error("Choose only one engine flag: --claude or --codex");
|
|
84824
|
-
}
|
|
84825
|
-
args.engine = "codex";
|
|
84826
|
-
args.engineSet = true;
|
|
84827
|
-
return true;
|
|
84828
|
-
case "--unlimited":
|
|
84829
|
-
args.maxIterations = 0;
|
|
84830
|
-
return true;
|
|
84831
|
-
case "--project-root":
|
|
84832
|
-
state.expectProjectRoot = true;
|
|
84833
|
-
return true;
|
|
84834
|
-
case "--workflow":
|
|
84835
|
-
state.expectWorkflow = true;
|
|
84836
|
-
return true;
|
|
84837
|
-
case "--name":
|
|
84838
|
-
state.expectName = true;
|
|
84839
|
-
return true;
|
|
84840
|
-
case "--prompt":
|
|
84841
|
-
state.expectPrompt = true;
|
|
84842
|
-
return true;
|
|
84843
|
-
case "--prompt-file":
|
|
84844
|
-
state.expectPromptFile = true;
|
|
84845
|
-
return true;
|
|
84846
|
-
case "--from-agent":
|
|
84847
|
-
args.fromAgent = true;
|
|
84848
|
-
return true;
|
|
84849
|
-
default:
|
|
84850
|
-
return false;
|
|
84851
|
-
}
|
|
84852
|
-
}
|
|
84853
|
-
async function resolvePromptFile(args, state) {
|
|
84854
|
-
if (state.promptFilePath !== null) {
|
|
84855
|
-
args.prompt = await Bun.file(state.promptFilePath).text();
|
|
84856
|
-
}
|
|
84857
|
-
}
|
|
84858
|
-
var VALID_MODELS, OPTION_BY_FLAG, VALUE_FLAGS, VALUE_SETTERS, BOOLEAN_SETTERS;
|
|
84859
|
-
var init_common_args = __esm(() => {
|
|
84860
|
-
init_fields();
|
|
84861
|
-
VALID_MODELS = new Set(modelOptionValues());
|
|
84862
|
-
OPTION_BY_FLAG = new Map(COMMON_CLI_OPTIONS.map((option) => [option.flag, option]));
|
|
84863
|
-
VALUE_FLAGS = new Set(COMMON_CLI_OPTIONS.filter((option) => option.kind !== "boolean").map((option) => option.flag));
|
|
84864
|
-
VALUE_SETTERS = {
|
|
84865
|
-
model: (args, raw) => {
|
|
84866
|
-
if (!VALID_MODELS.has(raw))
|
|
84867
|
-
throw new Error("Invalid model");
|
|
84868
|
-
args.model = raw;
|
|
84869
|
-
},
|
|
84870
|
-
delay: (args, raw) => {
|
|
84871
|
-
args.delay = parseInt(raw, 10);
|
|
84872
|
-
},
|
|
84873
|
-
maxCostUsd: (args, raw) => {
|
|
84874
|
-
args.maxCostUsd = parseFloat(raw);
|
|
84875
|
-
},
|
|
84876
|
-
maxRuntimeMinutes: (args, raw) => {
|
|
84877
|
-
args.maxRuntimeMinutes = parseFloat(raw);
|
|
84878
|
-
},
|
|
84879
|
-
maxConsecutiveFailures: (args, raw) => {
|
|
84880
|
-
args.maxConsecutiveFailures = parseInt(raw, 10);
|
|
84881
|
-
},
|
|
84882
|
-
maxIterations: (args, raw) => {
|
|
84883
|
-
args.maxIterations = parseInt(raw, 10);
|
|
84884
|
-
}
|
|
84885
|
-
};
|
|
84886
|
-
BOOLEAN_SETTERS = {
|
|
84887
|
-
log: (args) => {
|
|
84888
|
-
args.log = true;
|
|
84889
|
-
},
|
|
84890
|
-
verbose: (args) => {
|
|
84891
|
-
args.verbose = true;
|
|
84892
|
-
}
|
|
84893
|
-
};
|
|
84894
|
-
});
|
|
84895
|
-
|
|
84896
85325
|
// apps/loop/src/cli.ts
|
|
84897
85326
|
function printLoopHelp() {
|
|
84898
85327
|
log(HELP_TEXT);
|
|
@@ -84961,6 +85390,7 @@ async function parseLoopArgs(argv) {
|
|
|
84961
85390
|
}
|
|
84962
85391
|
}
|
|
84963
85392
|
await resolvePromptFile(result2, state);
|
|
85393
|
+
resolveWorkflowFile(result2, state);
|
|
84964
85394
|
return result2;
|
|
84965
85395
|
}
|
|
84966
85396
|
var VALID_MODES, HELP_TEXT;
|
|
@@ -85040,6 +85470,7 @@ async function parseTaskArgs(argv) {
|
|
|
85040
85470
|
}
|
|
85041
85471
|
}
|
|
85042
85472
|
await resolvePromptFile(result2, state);
|
|
85473
|
+
resolveWorkflowFile(result2, state);
|
|
85043
85474
|
if (!phaseSet) {
|
|
85044
85475
|
throw new Error(`Missing phase. Valid phases: research, plan, execute, review. Run 'ralphy task --help' for usage information.`);
|
|
85045
85476
|
}
|
|
@@ -85107,10 +85538,10 @@ var init_schema2 = __esm(() => {
|
|
|
85107
85538
|
});
|
|
85108
85539
|
|
|
85109
85540
|
// packages/core/src/state/sidecar.ts
|
|
85110
|
-
import { dirname as dirname5, join as
|
|
85541
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
85111
85542
|
import { mkdir as mkdir3, rename, unlink } from "fs/promises";
|
|
85112
85543
|
function slotSidecarPath(changeDir, slot) {
|
|
85113
|
-
return
|
|
85544
|
+
return join10(changeDir, `${CORE_STATE_FILE.replace(/\.json$/, "")}.${slot}.json`);
|
|
85114
85545
|
}
|
|
85115
85546
|
function parseObject(text) {
|
|
85116
85547
|
if (text === null)
|
|
@@ -89159,7 +89590,7 @@ var init_zod2 = __esm(() => {
|
|
|
89159
89590
|
function markersOf(set3) {
|
|
89160
89591
|
return Array.isArray(set3) ? set3 : [set3];
|
|
89161
89592
|
}
|
|
89162
|
-
var IterationUsageSchema, UsageSchema, HistoryEntrySchema, StateSchema, PhaseFrontmatterSchema;
|
|
89593
|
+
var IterationUsageSchema, UsageSchema, HistoryEntrySchema, RevisionSchema, StateSchema, PhaseFrontmatterSchema;
|
|
89163
89594
|
var init_types2 = __esm(() => {
|
|
89164
89595
|
init_zod2();
|
|
89165
89596
|
IterationUsageSchema = exports_external2.object({
|
|
@@ -89192,6 +89623,12 @@ var init_types2 = __esm(() => {
|
|
|
89192
89623
|
appVersion: exports_external2.string().optional(),
|
|
89193
89624
|
usage: IterationUsageSchema.partial().optional()
|
|
89194
89625
|
});
|
|
89626
|
+
RevisionSchema = exports_external2.object({
|
|
89627
|
+
version: exports_external2.number(),
|
|
89628
|
+
attachmentId: exports_external2.string(),
|
|
89629
|
+
sha256: exports_external2.string(),
|
|
89630
|
+
trigger: exports_external2.string()
|
|
89631
|
+
});
|
|
89195
89632
|
StateSchema = exports_external2.object({
|
|
89196
89633
|
version: exports_external2.literal("2"),
|
|
89197
89634
|
name: exports_external2.string(),
|
|
@@ -89240,12 +89677,16 @@ var init_types2 = __esm(() => {
|
|
|
89240
89677
|
attachmentId: exports_external2.string().nullable().default(null),
|
|
89241
89678
|
sha256: exports_external2.string().nullable().default(null)
|
|
89242
89679
|
}).default({ attachmentId: null, sha256: null }),
|
|
89680
|
+
designRevisions: exports_external2.array(RevisionSchema).default([]),
|
|
89681
|
+
designPdfRevisions: exports_external2.array(RevisionSchema).default([]),
|
|
89243
89682
|
legacyProposalPurged: exports_external2.boolean().default(false)
|
|
89244
89683
|
}).default({
|
|
89245
89684
|
proposal: { attachmentId: null, sha256: null },
|
|
89246
89685
|
design: { attachmentId: null, sha256: null },
|
|
89247
89686
|
proposalPdf: { attachmentId: null, sha256: null },
|
|
89248
89687
|
designPdf: { attachmentId: null, sha256: null },
|
|
89688
|
+
designRevisions: [],
|
|
89689
|
+
designPdfRevisions: [],
|
|
89249
89690
|
legacyProposalPurged: false
|
|
89250
89691
|
}),
|
|
89251
89692
|
confirmation: exports_external2.object({
|
|
@@ -89292,7 +89733,7 @@ function formatTaskName(name) {
|
|
|
89292
89733
|
}
|
|
89293
89734
|
|
|
89294
89735
|
// packages/core/src/state.ts
|
|
89295
|
-
import { join as
|
|
89736
|
+
import { join as join11 } from "path";
|
|
89296
89737
|
function stripOwnedSlots(state) {
|
|
89297
89738
|
const out = { ...state };
|
|
89298
89739
|
for (const slot of ALL_OWNED_SLOTS)
|
|
@@ -89300,7 +89741,7 @@ function stripOwnedSlots(state) {
|
|
|
89300
89741
|
return out;
|
|
89301
89742
|
}
|
|
89302
89743
|
function readState(changeDir) {
|
|
89303
|
-
const filePath =
|
|
89744
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89304
89745
|
const raw = getStorage().read(filePath);
|
|
89305
89746
|
if (raw === null)
|
|
89306
89747
|
throw new Error(".ralph-state.json not found");
|
|
@@ -89309,7 +89750,7 @@ function readState(changeDir) {
|
|
|
89309
89750
|
return StateSchema.parse(base2);
|
|
89310
89751
|
}
|
|
89311
89752
|
function tryReadStateRaw(changeDir) {
|
|
89312
|
-
const filePath =
|
|
89753
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89313
89754
|
const text = getStorage().read(filePath);
|
|
89314
89755
|
if (text === null)
|
|
89315
89756
|
return { state: null, raw: null };
|
|
@@ -89325,7 +89766,7 @@ function tryReadStateRaw(changeDir) {
|
|
|
89325
89766
|
return { state: result2.success ? result2.data : null, raw };
|
|
89326
89767
|
}
|
|
89327
89768
|
function writeState(changeDir, state) {
|
|
89328
|
-
const filePath =
|
|
89769
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89329
89770
|
const core2 = stripOwnedSlots(state);
|
|
89330
89771
|
getStorage().write(filePath, JSON.stringify(core2, null, 2) + `
|
|
89331
89772
|
`);
|
|
@@ -89366,7 +89807,7 @@ function buildInitialState(options) {
|
|
|
89366
89807
|
});
|
|
89367
89808
|
}
|
|
89368
89809
|
function ensureState(changeDir) {
|
|
89369
|
-
const filePath =
|
|
89810
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89370
89811
|
const storage = getStorage();
|
|
89371
89812
|
if (storage.read(filePath) !== null) {
|
|
89372
89813
|
return readState(changeDir);
|
|
@@ -89385,7 +89826,7 @@ var init_state = __esm(() => {
|
|
|
89385
89826
|
});
|
|
89386
89827
|
|
|
89387
89828
|
// packages/core/src/state/store.ts
|
|
89388
|
-
import { join as
|
|
89829
|
+
import { join as join12 } from "path";
|
|
89389
89830
|
async function readJson(filePath) {
|
|
89390
89831
|
const file2 = Bun.file(filePath);
|
|
89391
89832
|
if (!await file2.exists())
|
|
@@ -89409,7 +89850,7 @@ async function writeField(changeDir, featureName, path, value) {
|
|
|
89409
89850
|
if (!allowed.includes(topSlot)) {
|
|
89410
89851
|
throw new OwnershipError(featureName, path, `feature '${featureName}' may not write '${path}' (owns ${allowed.join(", ")})`);
|
|
89411
89852
|
}
|
|
89412
|
-
const inline = (await readJson(
|
|
89853
|
+
const inline = (await readJson(join12(changeDir, STATE_FILE3)))[topSlot];
|
|
89413
89854
|
const seed = inline && typeof inline === "object" && !Array.isArray(inline) ? inline : undefined;
|
|
89414
89855
|
await writeSlotField(changeDir, path, value, seed);
|
|
89415
89856
|
}
|
|
@@ -89432,14 +89873,14 @@ var init_store = __esm(() => {
|
|
|
89432
89873
|
});
|
|
89433
89874
|
|
|
89434
89875
|
// apps/loop/src/components/TaskStatus.tsx
|
|
89435
|
-
import { join as
|
|
89876
|
+
import { join as join13 } from "path";
|
|
89436
89877
|
function TaskStatus({ state, stateDir }) {
|
|
89437
89878
|
const storage = getStorage();
|
|
89438
89879
|
const cost = Math.round(state.usage.total_cost_usd * 100) / 100;
|
|
89439
89880
|
const time3 = Math.round(state.usage.total_duration_ms / 1000 * 10) / 10 + "s";
|
|
89440
89881
|
const artifacts = OPENSPEC_ARTIFACTS.map((name) => ({
|
|
89441
89882
|
name,
|
|
89442
|
-
exists: storage.read(
|
|
89883
|
+
exists: storage.read(join13(stateDir, name)) !== null
|
|
89443
89884
|
}));
|
|
89444
89885
|
const recent = state.history.slice(-10);
|
|
89445
89886
|
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
@@ -98157,7 +98598,7 @@ var init_rate_limit_detection = __esm(() => {
|
|
|
98157
98598
|
|
|
98158
98599
|
// packages/engine/src/agents/claude.ts
|
|
98159
98600
|
import { mkdtemp, unlink as unlink2 } from "fs/promises";
|
|
98160
|
-
import { join as
|
|
98601
|
+
import { join as join14 } from "path";
|
|
98161
98602
|
import { tmpdir } from "os";
|
|
98162
98603
|
function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, reviewerModel) {
|
|
98163
98604
|
const effectiveModel = reviewerModel ?? model;
|
|
@@ -98178,7 +98619,7 @@ function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, review
|
|
|
98178
98619
|
}
|
|
98179
98620
|
async function runInteractive(req) {
|
|
98180
98621
|
const { model, prompt, taskDir } = req;
|
|
98181
|
-
const promptFile = taskDir ?
|
|
98622
|
+
const promptFile = taskDir ? join14(taskDir, "_interactive_prompt.md") : join14(await mkdtemp(join14(tmpdir(), "ralph-")), "prompt.md");
|
|
98182
98623
|
await Bun.write(promptFile, prompt);
|
|
98183
98624
|
try {
|
|
98184
98625
|
const cmd = [
|
|
@@ -98205,7 +98646,7 @@ async function runInteractive(req) {
|
|
|
98205
98646
|
env: scrubClaudeEnv(process.env)
|
|
98206
98647
|
});
|
|
98207
98648
|
const exitCode = await proc.exited;
|
|
98208
|
-
const doneFile = taskDir ?
|
|
98649
|
+
const doneFile = taskDir ? join14(taskDir, "_interactive_done") : null;
|
|
98209
98650
|
if (doneFile && await Bun.file(doneFile).exists()) {
|
|
98210
98651
|
return { exitCode: 0, usage: null, sessionId: null, rateLimited: false };
|
|
98211
98652
|
}
|
|
@@ -99708,11 +100149,11 @@ var init_meta_prompt = __esm(() => {
|
|
|
99708
100149
|
});
|
|
99709
100150
|
|
|
99710
100151
|
// packages/core/src/loop.ts
|
|
99711
|
-
import { join as
|
|
100152
|
+
import { join as join15 } from "path";
|
|
99712
100153
|
function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
99713
100154
|
const storage = getStorage();
|
|
99714
100155
|
let prompt = "";
|
|
99715
|
-
const steeringContent = storage.read(
|
|
100156
|
+
const steeringContent = storage.read(join15(taskDir, "steering.md"));
|
|
99716
100157
|
if (steeringContent !== null) {
|
|
99717
100158
|
const steeringLines = steeringContent.split(`
|
|
99718
100159
|
`).filter((line) => !line.startsWith("#")).filter((line) => line.trim()).slice(0, STEERING_MAX_LINES);
|
|
@@ -99731,8 +100172,8 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99731
100172
|
`;
|
|
99732
100173
|
}
|
|
99733
100174
|
}
|
|
99734
|
-
const agentTasksPath =
|
|
99735
|
-
const missionTasksPath =
|
|
100175
|
+
const agentTasksPath = join15(taskDir, AGENT_TASKS_FILENAME);
|
|
100176
|
+
const missionTasksPath = join15(taskDir, MISSION_TASKS_FILENAME);
|
|
99736
100177
|
const agentTasksContent = storage.read(agentTasksPath);
|
|
99737
100178
|
const missionTasksContent = storage.read(missionTasksPath);
|
|
99738
100179
|
let activePath = null;
|
|
@@ -99808,7 +100249,7 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99808
100249
|
}
|
|
99809
100250
|
}
|
|
99810
100251
|
if (reviewPhase?.enabled) {
|
|
99811
|
-
const reviewFindingsPath =
|
|
100252
|
+
const reviewFindingsPath = join15(taskDir, "review-findings.md");
|
|
99812
100253
|
const reviewFindingsContent = storage.read(reviewFindingsPath);
|
|
99813
100254
|
const hasUncheckedMission = missionTasksContent !== null && /^- \[ \]/m.test(missionTasksContent);
|
|
99814
100255
|
const hasUncheckedAgent = agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent);
|
|
@@ -99882,7 +100323,7 @@ When all tasks are complete and all files are committed, push your branch and op
|
|
|
99882
100323
|
}
|
|
99883
100324
|
function buildSteeringBlock(taskDir) {
|
|
99884
100325
|
const storage = getStorage();
|
|
99885
|
-
const steeringContent = storage.read(
|
|
100326
|
+
const steeringContent = storage.read(join15(taskDir, "steering.md"));
|
|
99886
100327
|
if (steeringContent === null)
|
|
99887
100328
|
return "";
|
|
99888
100329
|
const steeringLines = steeringContent.split(`
|
|
@@ -99980,7 +100421,7 @@ function buildPlanPrompt(state, taskDir) {
|
|
|
99980
100421
|
return prompt;
|
|
99981
100422
|
}
|
|
99982
100423
|
function buildReviewPrompt(state, taskDir) {
|
|
99983
|
-
const reviewFindingsPath =
|
|
100424
|
+
const reviewFindingsPath = join15(taskDir, "review-findings.md");
|
|
99984
100425
|
let prompt = buildSteeringBlock(taskDir);
|
|
99985
100426
|
prompt += `---
|
|
99986
100427
|
|
|
@@ -100045,7 +100486,7 @@ function buildPhasePrompt(phase, state, taskDir, reviewPhase, metaPromptOptions)
|
|
|
100045
100486
|
}
|
|
100046
100487
|
function checkStopSignal(taskDir, stateDir) {
|
|
100047
100488
|
const storage = getStorage();
|
|
100048
|
-
const stopFile =
|
|
100489
|
+
const stopFile = join15(taskDir, "STOP");
|
|
100049
100490
|
const reason = storage.read(stopFile);
|
|
100050
100491
|
if (reason === null)
|
|
100051
100492
|
return null;
|
|
@@ -100105,7 +100546,7 @@ function updateStateIteration(stateDir, result2, startedAt, engine, model, usage
|
|
|
100105
100546
|
}
|
|
100106
100547
|
function appendSteeringMessage(taskDir, message) {
|
|
100107
100548
|
const storage = getStorage();
|
|
100108
|
-
const steeringPath =
|
|
100549
|
+
const steeringPath = join15(taskDir, "steering.md");
|
|
100109
100550
|
const existing = storage.read(steeringPath);
|
|
100110
100551
|
const updated = existing ? `${message}
|
|
100111
100552
|
|
|
@@ -100155,7 +100596,7 @@ var init_loop2 = __esm(() => {
|
|
|
100155
100596
|
});
|
|
100156
100597
|
|
|
100157
100598
|
// apps/loop/src/hooks/useLoop.ts
|
|
100158
|
-
import { join as
|
|
100599
|
+
import { join as join16 } from "path";
|
|
100159
100600
|
function sleep(seconds) {
|
|
100160
100601
|
return new Promise((resolve4) => setTimeout(resolve4, seconds * 1000));
|
|
100161
100602
|
}
|
|
@@ -100271,8 +100712,8 @@ function useLoop(opts) {
|
|
|
100271
100712
|
setState(currentState);
|
|
100272
100713
|
if (!actor.getSnapshot().matches("running"))
|
|
100273
100714
|
break;
|
|
100274
|
-
const tasksContent = storage.read(
|
|
100275
|
-
const agentTasksContent = storage.read(
|
|
100715
|
+
const tasksContent = storage.read(join16(tasksDir, MISSION_TASKS_FILENAME));
|
|
100716
|
+
const agentTasksContent = storage.read(join16(tasksDir, AGENT_TASKS_FILENAME));
|
|
100276
100717
|
if (tasksContent === null && currentState.iteration > 0 && typeof opts.changeStore.listChanges === "function") {
|
|
100277
100718
|
let stillActive = true;
|
|
100278
100719
|
try {
|
|
@@ -100309,7 +100750,7 @@ function useLoop(opts) {
|
|
|
100309
100750
|
const agentDone = agentTasksContent === null || allCompleted(agentTasksContent);
|
|
100310
100751
|
if (missionDone && agentDone && tasksContent !== null) {
|
|
100311
100752
|
if (opts.reviewPhase?.enabled) {
|
|
100312
|
-
const reviewFindingsPath =
|
|
100753
|
+
const reviewFindingsPath = join16(tasksDir, "review-findings.md");
|
|
100313
100754
|
const reviewFindingsFile = Bun.file(reviewFindingsPath);
|
|
100314
100755
|
const findingsExists = await reviewFindingsFile.exists();
|
|
100315
100756
|
const findingsContent = findingsExists ? await reviewFindingsFile.text() : null;
|
|
@@ -100338,7 +100779,7 @@ function useLoop(opts) {
|
|
|
100338
100779
|
model: opts.reviewPhase.reviewerModel ?? opts.model,
|
|
100339
100780
|
prompt: reviewPrompt,
|
|
100340
100781
|
logFlag: opts.log,
|
|
100341
|
-
logFile:
|
|
100782
|
+
logFile: join16(stateDir, `log-review-${roundNum}.json`),
|
|
100342
100783
|
taskDir: tasksDir,
|
|
100343
100784
|
reviewerContextStrategy: opts.reviewPhase.reviewerContextStrategy ?? "fresh",
|
|
100344
100785
|
onFeedEvent: addFeedEvent
|
|
@@ -100412,8 +100853,8 @@ function useLoop(opts) {
|
|
|
100412
100853
|
const time3 = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
100413
100854
|
addIterationHeader(localIter, time3);
|
|
100414
100855
|
addInfo(`Iteration ${localIter} (total: ${currentState.iteration})`);
|
|
100415
|
-
const proposalContent = storage.read(
|
|
100416
|
-
const designContent = storage.read(
|
|
100856
|
+
const proposalContent = storage.read(join16(tasksDir, "proposal.md"));
|
|
100857
|
+
const designContent = storage.read(join16(tasksDir, "design.md"));
|
|
100417
100858
|
const routedPhase = routeTaskPhase(opts.phase, {
|
|
100418
100859
|
proposal: proposalContent,
|
|
100419
100860
|
design: designContent,
|
|
@@ -100433,7 +100874,7 @@ function useLoop(opts) {
|
|
|
100433
100874
|
model: opts.model,
|
|
100434
100875
|
prompt,
|
|
100435
100876
|
logFlag: opts.log,
|
|
100436
|
-
logFile:
|
|
100877
|
+
logFile: join16(stateDir, "log.json"),
|
|
100437
100878
|
taskDir: tasksDir,
|
|
100438
100879
|
interactive: false,
|
|
100439
100880
|
onFeedEvent: addFeedEvent,
|
|
@@ -100456,7 +100897,7 @@ function useLoop(opts) {
|
|
|
100456
100897
|
model: opts.model,
|
|
100457
100898
|
prompt: buildSteeringPrompt(steerMessage),
|
|
100458
100899
|
logFlag: opts.log,
|
|
100459
|
-
logFile:
|
|
100900
|
+
logFile: join16(stateDir, "log.json"),
|
|
100460
100901
|
taskDir: tasksDir,
|
|
100461
100902
|
onFeedEvent: addResumeFeedEvent,
|
|
100462
100903
|
signal: resumeController.signal,
|
|
@@ -100763,7 +101204,7 @@ var init_TaskLoop = __esm(async () => {
|
|
|
100763
101204
|
});
|
|
100764
101205
|
|
|
100765
101206
|
// apps/loop/src/components/App.tsx
|
|
100766
|
-
import { join as
|
|
101207
|
+
import { join as join17 } from "path";
|
|
100767
101208
|
function ExitAfterRender({ children }) {
|
|
100768
101209
|
const { exit } = use_app_default();
|
|
100769
101210
|
import_react59.useEffect(() => {
|
|
@@ -100816,7 +101257,7 @@ function App2({ args, taskPhase }) {
|
|
|
100816
101257
|
}
|
|
100817
101258
|
const layout = getLayout();
|
|
100818
101259
|
const stateDir = layout.taskStateDir(args.name);
|
|
100819
|
-
if (getStorage().read(
|
|
101260
|
+
if (getStorage().read(join17(stateDir, ".ralph-state.json")) === null) {
|
|
100820
101261
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
|
|
100821
101262
|
message: `Error: change '${args.name}' not found`
|
|
100822
101263
|
}, undefined, false, undefined, this);
|
|
@@ -100870,7 +101311,7 @@ var init_App2 = __esm(async () => {
|
|
|
100870
101311
|
|
|
100871
101312
|
// packages/log/src/log.ts
|
|
100872
101313
|
import { appendFile } from "fs/promises";
|
|
100873
|
-
import { join as
|
|
101314
|
+
import { join as join18, dirname as dirname7 } from "path";
|
|
100874
101315
|
import { homedir as homedir4 } from "os";
|
|
100875
101316
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
100876
101317
|
function fmt(type, text) {
|
|
@@ -100919,14 +101360,14 @@ var init_log = __esm(() => {
|
|
|
100919
101360
|
init_version();
|
|
100920
101361
|
jsonLogChains = new Map;
|
|
100921
101362
|
ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
100922
|
-
AGENT_LOG_PATH =
|
|
101363
|
+
AGENT_LOG_PATH = join18(homedir4(), ".ralph", "agent-mode.log");
|
|
100923
101364
|
mkdir5(dirname7(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
100924
101365
|
return;
|
|
100925
101366
|
});
|
|
100926
101367
|
});
|
|
100927
101368
|
|
|
100928
101369
|
// apps/loop/src/debug.ts
|
|
100929
|
-
import { join as
|
|
101370
|
+
import { join as join19 } from "path";
|
|
100930
101371
|
function fmtTs(d) {
|
|
100931
101372
|
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
100932
101373
|
}
|
|
@@ -101038,7 +101479,7 @@ function detectDebugStuck(lines) {
|
|
|
101038
101479
|
};
|
|
101039
101480
|
}
|
|
101040
101481
|
async function inspectBinary(projectRoot) {
|
|
101041
|
-
const binPath =
|
|
101482
|
+
const binPath = join19(projectRoot, ".ralph", "bin", "cli.js");
|
|
101042
101483
|
const file2 = Bun.file(binPath);
|
|
101043
101484
|
if (!await file2.exists())
|
|
101044
101485
|
return null;
|
|
@@ -101063,7 +101504,7 @@ async function inspectBinary(projectRoot) {
|
|
|
101063
101504
|
async function resolveDebugTarget(projectRoot, opts) {
|
|
101064
101505
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
101065
101506
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
101066
|
-
const jsonlLogFile = Bun.file(
|
|
101507
|
+
const jsonlLogFile = Bun.file(join19(projectRoot, ".ralph", "agent.log"));
|
|
101067
101508
|
const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
|
|
101068
101509
|
const allLines = [...textLines, ...jsonlLines];
|
|
101069
101510
|
if (opts.name && !opts.issue) {
|
|
@@ -101168,7 +101609,7 @@ async function runDebug(opts) {
|
|
|
101168
101609
|
`);
|
|
101169
101610
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
101170
101611
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
101171
|
-
const jsonlLogPath =
|
|
101612
|
+
const jsonlLogPath = join19(projectRoot, ".ralph", "agent.log");
|
|
101172
101613
|
const jsonlLogFile = Bun.file(jsonlLogPath);
|
|
101173
101614
|
const hasJsonlLog = await jsonlLogFile.exists();
|
|
101174
101615
|
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
|
|
@@ -101182,7 +101623,7 @@ async function runDebug(opts) {
|
|
|
101182
101623
|
}
|
|
101183
101624
|
const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
|
|
101184
101625
|
const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
101185
|
-
const workerLogFile = Bun.file(
|
|
101626
|
+
const workerLogFile = Bun.file(join19(projectRoot, ".ralph", "logs", `${changeName}.log`));
|
|
101186
101627
|
const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
|
|
101187
101628
|
const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
101188
101629
|
const seen = new Set;
|
|
@@ -101341,8 +101782,8 @@ async function runDebug(opts) {
|
|
|
101341
101782
|
out(" \u26A0 PR currently has merge conflicts");
|
|
101342
101783
|
if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
|
|
101343
101784
|
out(" \u26A0 PR has failing CI checks");
|
|
101344
|
-
const worktreePath =
|
|
101345
|
-
if (await Bun.file(
|
|
101785
|
+
const worktreePath = join19(projectRoot, ".ralph", "worktrees", changeName);
|
|
101786
|
+
if (await Bun.file(join19(worktreePath, ".git")).exists()) {
|
|
101346
101787
|
out(` Worktree : ${worktreePath}`);
|
|
101347
101788
|
}
|
|
101348
101789
|
if (!timeline.length)
|
|
@@ -101362,12 +101803,12 @@ __export(exports_src2, {
|
|
|
101362
101803
|
taskMain: () => taskMain,
|
|
101363
101804
|
main: () => main2
|
|
101364
101805
|
});
|
|
101365
|
-
import { join as
|
|
101806
|
+
import { join as join20 } from "path";
|
|
101366
101807
|
import { exists as exists2, mkdir as mkdir6, rm as rm2 } from "fs/promises";
|
|
101367
101808
|
async function ensureRalphGitignore(projectRoot) {
|
|
101368
|
-
const ralphDir =
|
|
101809
|
+
const ralphDir = join20(projectRoot, ".ralph");
|
|
101369
101810
|
await mkdir6(ralphDir, { recursive: true });
|
|
101370
|
-
const gitignorePath =
|
|
101811
|
+
const gitignorePath = join20(ralphDir, ".gitignore");
|
|
101371
101812
|
const file2 = Bun.file(gitignorePath);
|
|
101372
101813
|
if (await file2.exists()) {
|
|
101373
101814
|
const existing = await file2.text();
|
|
@@ -101416,7 +101857,7 @@ async function main2(argv) {
|
|
|
101416
101857
|
Bun.spawnSync({
|
|
101417
101858
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
101418
101859
|
stdio: ["inherit", "inherit", "inherit"],
|
|
101419
|
-
cwd:
|
|
101860
|
+
cwd: projectRoot
|
|
101420
101861
|
});
|
|
101421
101862
|
}
|
|
101422
101863
|
if (args.mode === "debug") {
|
|
@@ -101434,9 +101875,9 @@ async function main2(argv) {
|
|
|
101434
101875
|
`);
|
|
101435
101876
|
return 1;
|
|
101436
101877
|
}
|
|
101437
|
-
const worktreeDir =
|
|
101438
|
-
const changeDir =
|
|
101439
|
-
const stateDir =
|
|
101878
|
+
const worktreeDir = join20(worktreesDir(projectRoot), args.name);
|
|
101879
|
+
const changeDir = join20(tasksDir, args.name);
|
|
101880
|
+
const stateDir = join20(statesDir, args.name);
|
|
101440
101881
|
const branch = `ralph/${args.name}`;
|
|
101441
101882
|
const removed = [];
|
|
101442
101883
|
if (await exists2(worktreeDir)) {
|
|
@@ -101487,8 +101928,8 @@ async function main2(argv) {
|
|
|
101487
101928
|
return 0;
|
|
101488
101929
|
}
|
|
101489
101930
|
if (args.mode === "task" && args.name) {
|
|
101490
|
-
await mkdir6(
|
|
101491
|
-
await mkdir6(
|
|
101931
|
+
await mkdir6(join20(statesDir, args.name), { recursive: true });
|
|
101932
|
+
await mkdir6(join20(tasksDir, args.name), { recursive: true });
|
|
101492
101933
|
await ensureRalphGitignore(projectRoot);
|
|
101493
101934
|
}
|
|
101494
101935
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
@@ -101516,8 +101957,8 @@ async function taskMain(argv) {
|
|
|
101516
101957
|
const layout = projectLayout(projectRoot);
|
|
101517
101958
|
const statesDir = layout.statesDir;
|
|
101518
101959
|
const tasksDir = layout.tasksDir;
|
|
101519
|
-
await mkdir6(
|
|
101520
|
-
await mkdir6(
|
|
101960
|
+
await mkdir6(join20(statesDir, args.name), { recursive: true });
|
|
101961
|
+
await mkdir6(join20(tasksDir, args.name), { recursive: true });
|
|
101521
101962
|
await ensureRalphGitignore(projectRoot);
|
|
101522
101963
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
101523
101964
|
const { waitUntilExit } = render_default(import_react60.createElement(App2, {
|
|
@@ -101760,6 +102201,7 @@ async function parseAgentArgs(argv) {
|
|
|
101760
102201
|
}
|
|
101761
102202
|
}
|
|
101762
102203
|
await resolvePromptFile(result2, state);
|
|
102204
|
+
resolveWorkflowFile(result2, state);
|
|
101763
102205
|
if (result2.fixCi && !result2.createPr) {
|
|
101764
102206
|
throw new Error("--fix-ci requires --create-pr");
|
|
101765
102207
|
}
|
|
@@ -101917,7 +102359,7 @@ function formatError2(err) {
|
|
|
101917
102359
|
}
|
|
101918
102360
|
|
|
101919
102361
|
// apps/agent/src/shared/capabilities/fs-change.ts
|
|
101920
|
-
import { join as
|
|
102362
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
101921
102363
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
101922
102364
|
var scaffold, prependTask, appendSteering, fsChange;
|
|
101923
102365
|
var init_fs_change = __esm(() => {
|
|
@@ -101930,11 +102372,11 @@ var init_fs_change = __esm(() => {
|
|
|
101930
102372
|
errorFormatter: formatError2,
|
|
101931
102373
|
run: async (args) => {
|
|
101932
102374
|
await mkdir7(args.changeDir, { recursive: true });
|
|
101933
|
-
await mkdir7(
|
|
102375
|
+
await mkdir7(join21(args.changeDir, "specs"), { recursive: true });
|
|
101934
102376
|
await mkdir7(args.stateDir, { recursive: true });
|
|
101935
|
-
await Bun.write(
|
|
101936
|
-
await Bun.write(
|
|
101937
|
-
await Bun.write(
|
|
102377
|
+
await Bun.write(join21(args.changeDir, "proposal.md"), args.proposal);
|
|
102378
|
+
await Bun.write(join21(args.changeDir, "tasks.md"), args.tasks);
|
|
102379
|
+
await Bun.write(join21(args.changeDir, "design.md"), args.design);
|
|
101938
102380
|
}
|
|
101939
102381
|
};
|
|
101940
102382
|
prependTask = {
|
|
@@ -101952,7 +102394,7 @@ var init_fs_change = __esm(() => {
|
|
|
101952
102394
|
retryPolicy: NO_RETRY,
|
|
101953
102395
|
errorFormatter: formatError2,
|
|
101954
102396
|
run: async (args) => {
|
|
101955
|
-
const path =
|
|
102397
|
+
const path = join21(args.changeDir, "steering.md");
|
|
101956
102398
|
const f2 = Bun.file(path);
|
|
101957
102399
|
const existing = await f2.exists() ? await f2.text() : null;
|
|
101958
102400
|
const updated = existing ? `${args.message}
|
|
@@ -101967,11 +102409,11 @@ ${existing.trimStart()}` : `${args.message}
|
|
|
101967
102409
|
});
|
|
101968
102410
|
|
|
101969
102411
|
// apps/agent/src/agent/worktree.ts
|
|
101970
|
-
import { basename as basename2, join as
|
|
102412
|
+
import { basename as basename2, join as join22 } from "path";
|
|
101971
102413
|
import { homedir as homedir5 } from "os";
|
|
101972
102414
|
import { exists as exists3 } from "fs/promises";
|
|
101973
102415
|
function worktreesDir2(projectRoot) {
|
|
101974
|
-
return
|
|
102416
|
+
return join22(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
|
|
101975
102417
|
}
|
|
101976
102418
|
function branchForChange(changeName) {
|
|
101977
102419
|
return `ralph/${changeName}`;
|
|
@@ -101990,7 +102432,7 @@ function createWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
101990
102432
|
}
|
|
101991
102433
|
async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
101992
102434
|
const dir = worktreesDir2(projectRoot);
|
|
101993
|
-
const cwd2 =
|
|
102435
|
+
const cwd2 = join22(dir, changeName);
|
|
101994
102436
|
const branch = branchForChange(changeName);
|
|
101995
102437
|
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
101996
102438
|
if (list.stdout.includes(`worktree ${cwd2}
|
|
@@ -102015,7 +102457,7 @@ async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
102015
102457
|
return { cwd: cwd2, branch };
|
|
102016
102458
|
}
|
|
102017
102459
|
async function installPrePushHook(cwd2, runner) {
|
|
102018
|
-
const hookPath =
|
|
102460
|
+
const hookPath = join22(cwd2, ".ralph-hooks", "pre-push");
|
|
102019
102461
|
await Bun.write(hookPath, PRE_PUSH_HOOK_SCRIPT);
|
|
102020
102462
|
const chmod = Bun.spawn(["chmod", "+x", hookPath]);
|
|
102021
102463
|
await chmod.exited;
|
|
@@ -102061,8 +102503,8 @@ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
|
|
|
102061
102503
|
return { safe: true, dirty, unpushedCommits };
|
|
102062
102504
|
}
|
|
102063
102505
|
async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
102064
|
-
const dst =
|
|
102065
|
-
const src =
|
|
102506
|
+
const dst = join22(worktreeCwd, ".mcp.json");
|
|
102507
|
+
const src = join22(projectRoot, ".mcp.json");
|
|
102066
102508
|
const source = await exists3(dst) ? dst : await exists3(src) ? src : null;
|
|
102067
102509
|
if (!source)
|
|
102068
102510
|
return;
|
|
@@ -102076,7 +102518,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
|
102076
102518
|
if (servers && typeof servers === "object") {
|
|
102077
102519
|
for (const cfg of Object.values(servers)) {
|
|
102078
102520
|
if (Array.isArray(cfg.args)) {
|
|
102079
|
-
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ?
|
|
102521
|
+
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join22(projectRoot, a) : a);
|
|
102080
102522
|
}
|
|
102081
102523
|
}
|
|
102082
102524
|
}
|
|
@@ -103926,7 +104368,7 @@ function emitFeatureSkipped(bus, id, reason) {
|
|
|
103926
104368
|
var init_run_feature = () => {};
|
|
103927
104369
|
|
|
103928
104370
|
// apps/agent/src/agent/post-task.ts
|
|
103929
|
-
import { join as
|
|
104371
|
+
import { join as join23, dirname as dirname9 } from "path";
|
|
103930
104372
|
function summarizeUncommittedStatus(stdout) {
|
|
103931
104373
|
const lines = stdout.split(`
|
|
103932
104374
|
`).filter((line) => line.length > 0);
|
|
@@ -103998,7 +104440,7 @@ async function reactivateState(stateFilePath, log3, changeName) {
|
|
|
103998
104440
|
async function runWorkerWithFixTask(ctx, heading, body) {
|
|
103999
104441
|
try {
|
|
104000
104442
|
await runCapability(fsChange.prependTask, {
|
|
104001
|
-
tasksPath:
|
|
104443
|
+
tasksPath: join23(ctx.changeDir, AGENT_TASKS_FILENAME),
|
|
104002
104444
|
heading,
|
|
104003
104445
|
failureOutput: body
|
|
104004
104446
|
});
|
|
@@ -104549,7 +104991,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104549
104991
|
emit3("validate-fix", command);
|
|
104550
104992
|
log3(`! validation check failed: ${command}`, "yellow");
|
|
104551
104993
|
try {
|
|
104552
|
-
await prependFixTask(
|
|
104994
|
+
await prependFixTask(join23(changeDir, AGENT_TASKS_FILENAME), `Fix failing validation: ${command}`, output || `Command exited with code ${exitCode}`);
|
|
104553
104995
|
} catch (err) {
|
|
104554
104996
|
log3(`! could not prepend fix task: ${err.message}`, "red");
|
|
104555
104997
|
return 1;
|
|
@@ -104560,7 +105002,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104560
105002
|
}
|
|
104561
105003
|
}
|
|
104562
105004
|
try {
|
|
104563
|
-
await prependFixTask(
|
|
105005
|
+
await prependFixTask(join23(changeDir, AGENT_TASKS_FILENAME), "Run openspec validation", [
|
|
104564
105006
|
`Run \`bunx openspec validate ${changeName}\` to validate the change artifacts.`,
|
|
104565
105007
|
`Commit any pending changes before running the validation command.`
|
|
104566
105008
|
].join(`
|
|
@@ -104573,7 +105015,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104573
105015
|
return respawnWorker();
|
|
104574
105016
|
}
|
|
104575
105017
|
async function recordGaveUp(stateFilePath, log3, changeName) {
|
|
104576
|
-
const path =
|
|
105018
|
+
const path = join23(dirname9(stateFilePath), GAVEUP_COUNT_FILE);
|
|
104577
105019
|
try {
|
|
104578
105020
|
const file2 = Bun.file(path);
|
|
104579
105021
|
const current = await file2.exists() ? Number.parseInt(await file2.text(), 10) || 0 : 0;
|
|
@@ -105038,6 +105480,7 @@ class AgentCoordinator {
|
|
|
105038
105480
|
opts;
|
|
105039
105481
|
workers = [];
|
|
105040
105482
|
pendingIds = new Set;
|
|
105483
|
+
inFlight = new Set;
|
|
105041
105484
|
queue = [];
|
|
105042
105485
|
stopped = false;
|
|
105043
105486
|
paused = null;
|
|
@@ -105604,7 +106047,31 @@ class AgentCoordinator {
|
|
|
105604
106047
|
while (this.workers.length + this.pendingIds.size < this.opts.concurrency && this.queue.length > 0) {
|
|
105605
106048
|
const next = this.queue.shift();
|
|
105606
106049
|
this.pendingIds.add(next.issue.id);
|
|
105607
|
-
this.launchWorker(next.issue, next.trigger, next.mention);
|
|
106050
|
+
this.track(this.launchWorker(next.issue, next.trigger, next.mention));
|
|
106051
|
+
}
|
|
106052
|
+
}
|
|
106053
|
+
track(p) {
|
|
106054
|
+
this.inFlight.add(p);
|
|
106055
|
+
p.finally(() => {
|
|
106056
|
+
this.inFlight.delete(p);
|
|
106057
|
+
});
|
|
106058
|
+
return p;
|
|
106059
|
+
}
|
|
106060
|
+
async whenSettled() {
|
|
106061
|
+
let consecutiveEmpty = 0;
|
|
106062
|
+
for (let guard = 0;guard < 1000; guard++) {
|
|
106063
|
+
if (this.inFlight.size > 0) {
|
|
106064
|
+
await Promise.allSettled(this.inFlight);
|
|
106065
|
+
consecutiveEmpty = 0;
|
|
106066
|
+
}
|
|
106067
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
106068
|
+
if (this.inFlight.size === 0) {
|
|
106069
|
+
consecutiveEmpty += 1;
|
|
106070
|
+
if (consecutiveEmpty >= WHEN_SETTLED_STABLE_HOPS)
|
|
106071
|
+
return;
|
|
106072
|
+
} else {
|
|
106073
|
+
consecutiveEmpty = 0;
|
|
106074
|
+
}
|
|
105608
106075
|
}
|
|
105609
106076
|
}
|
|
105610
106077
|
async launchWorker(issue2, trigger, mention) {
|
|
@@ -105722,7 +106189,10 @@ class AgentCoordinator {
|
|
|
105722
106189
|
this.deps.onLog(`! sync-tasks (launch) failed for ${issue2.identifier}: ${err.message}`, "yellow");
|
|
105723
106190
|
}
|
|
105724
106191
|
}
|
|
105725
|
-
handle.exited.then(
|
|
106192
|
+
handle.exited.then((code) => this.track(this.finalizeWorkerExit(worker, issue2, prep, trigger, code)));
|
|
106193
|
+
}
|
|
106194
|
+
async finalizeWorkerExit(worker, issue2, prep, trigger, code) {
|
|
106195
|
+
{
|
|
105726
106196
|
const idx = this.workers.indexOf(worker);
|
|
105727
106197
|
if (idx >= 0)
|
|
105728
106198
|
this.workers.splice(idx, 1);
|
|
@@ -105770,7 +106240,7 @@ class AgentCoordinator {
|
|
|
105770
106240
|
await this.notifyExited(issue2, prep.changeName, code, trigger);
|
|
105771
106241
|
this.deps.onWorkersChanged();
|
|
105772
106242
|
this.spawnNext();
|
|
105773
|
-
}
|
|
106243
|
+
}
|
|
105774
106244
|
}
|
|
105775
106245
|
async restartWorker(changeName) {
|
|
105776
106246
|
if (this.stopped)
|
|
@@ -105946,7 +106416,7 @@ var emptyPrStatus = () => ({
|
|
|
105946
106416
|
conflicted: 0,
|
|
105947
106417
|
ciFailed: 0,
|
|
105948
106418
|
quarantined: 0
|
|
105949
|
-
}), emptyPollResult = () => ({
|
|
106419
|
+
}), WHEN_SETTLED_STABLE_HOPS = 3, emptyPollResult = () => ({
|
|
105950
106420
|
found: 0,
|
|
105951
106421
|
added: 0,
|
|
105952
106422
|
buckets: {
|
|
@@ -105980,15 +106450,15 @@ var init_coordinator2 = __esm(() => {
|
|
|
105980
106450
|
});
|
|
105981
106451
|
|
|
105982
106452
|
// apps/agent/src/agent/scaffold.ts
|
|
105983
|
-
import { join as
|
|
106453
|
+
import { join as join24 } from "path";
|
|
105984
106454
|
function changeNameForIssue(issue2) {
|
|
105985
106455
|
const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
|
|
105986
106456
|
return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
|
|
105987
106457
|
}
|
|
105988
106458
|
async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = [], appendPrompt = "", attachments = []) {
|
|
105989
106459
|
const name = changeNameForIssue(issue2);
|
|
105990
|
-
const changeDir =
|
|
105991
|
-
const stateDir =
|
|
106460
|
+
const changeDir = join24(tasksDir, name);
|
|
106461
|
+
const stateDir = join24(statesDir, name);
|
|
105992
106462
|
const commentsBlock = comments.length > 0 ? [
|
|
105993
106463
|
"",
|
|
105994
106464
|
"## Linear comments",
|
|
@@ -106124,7 +106594,7 @@ var init_detections = __esm(() => {
|
|
|
106124
106594
|
});
|
|
106125
106595
|
|
|
106126
106596
|
// apps/agent/src/features/confirmation/state.ts
|
|
106127
|
-
import { dirname as dirname10, join as
|
|
106597
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
106128
106598
|
async function readInlineConfirmation(statePath) {
|
|
106129
106599
|
const f2 = Bun.file(statePath);
|
|
106130
106600
|
if (!await f2.exists())
|
|
@@ -106163,8 +106633,8 @@ async function restartFromDesign(changeDir, changeName) {
|
|
|
106163
106633
|
""
|
|
106164
106634
|
].join(`
|
|
106165
106635
|
`);
|
|
106166
|
-
await Bun.write(
|
|
106167
|
-
const tasksPath =
|
|
106636
|
+
await Bun.write(join25(changeDir, "design.md"), designStub);
|
|
106637
|
+
const tasksPath = join25(changeDir, "tasks.md");
|
|
106168
106638
|
if (await Bun.file(tasksPath).exists()) {
|
|
106169
106639
|
await Bun.write(tasksPath, `# Tasks
|
|
106170
106640
|
|
|
@@ -106394,7 +106864,7 @@ var init_inspect = __esm(() => {
|
|
|
106394
106864
|
});
|
|
106395
106865
|
|
|
106396
106866
|
// apps/agent/src/features/confirmation/awaiting.ts
|
|
106397
|
-
import { join as
|
|
106867
|
+
import { join as join26 } from "path";
|
|
106398
106868
|
async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
106399
106869
|
const tracked = deps.cwdOf(changeName);
|
|
106400
106870
|
if (tracked)
|
|
@@ -106402,12 +106872,12 @@ async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
|
106402
106872
|
if (!deps.useWorktree)
|
|
106403
106873
|
return deps.projectRoot;
|
|
106404
106874
|
const root = worktreesDir2(deps.projectRoot);
|
|
106405
|
-
const canonical =
|
|
106406
|
-
if (await Bun.file(
|
|
106875
|
+
const canonical = join26(root, worktreeDirNameForIssue(issue2));
|
|
106876
|
+
if (await Bun.file(join26(canonical, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106407
106877
|
return canonical;
|
|
106408
106878
|
}
|
|
106409
|
-
const legacy =
|
|
106410
|
-
if (await Bun.file(
|
|
106879
|
+
const legacy = join26(root, changeName);
|
|
106880
|
+
if (await Bun.file(join26(legacy, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106411
106881
|
return legacy;
|
|
106412
106882
|
}
|
|
106413
106883
|
return deps.projectRoot;
|
|
@@ -106542,9 +107012,9 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
106542
107012
|
const layout = projectLayout(cwd2);
|
|
106543
107013
|
const changeDir = layout.changeDir(changeName);
|
|
106544
107014
|
const statePath = layout.stateFile(changeName);
|
|
106545
|
-
const tasks2 = await readTextOrNull(
|
|
106546
|
-
const proposal = await readTextOrNull(
|
|
106547
|
-
const design = await readTextOrNull(
|
|
107015
|
+
const tasks2 = await readTextOrNull(join26(changeDir, "tasks.md"));
|
|
107016
|
+
const proposal = await readTextOrNull(join26(changeDir, "proposal.md"));
|
|
107017
|
+
const design = await readTextOrNull(join26(changeDir, "design.md"));
|
|
106548
107018
|
let commentsCache = null;
|
|
106549
107019
|
const getComments = async () => {
|
|
106550
107020
|
if (commentsCache)
|
|
@@ -107088,7 +107558,12 @@ var init_linear_resolvers = __esm(() => {
|
|
|
107088
107558
|
|
|
107089
107559
|
// apps/agent/src/agent/wire/prepare.ts
|
|
107090
107560
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
107091
|
-
import { join as
|
|
107561
|
+
import { join as join27 } from "path";
|
|
107562
|
+
function composeAppendPrompt(promptArg, cfgAppendPrompt, workflowPrompt) {
|
|
107563
|
+
return [promptArg || cfgAppendPrompt || "", workflowPrompt].filter(Boolean).join(`
|
|
107564
|
+
|
|
107565
|
+
`);
|
|
107566
|
+
}
|
|
107092
107567
|
function createPrepareHelpers(input) {
|
|
107093
107568
|
const {
|
|
107094
107569
|
args,
|
|
@@ -107152,7 +107627,7 @@ function createPrepareHelpers(input) {
|
|
|
107152
107627
|
let changeName;
|
|
107153
107628
|
const wtLayoutPre = projectLayout(workerCwd);
|
|
107154
107629
|
const derivedName = changeNameForIssue(issue2);
|
|
107155
|
-
const tasksMdPath =
|
|
107630
|
+
const tasksMdPath = join27(wtLayoutPre.changeDir(derivedName), "tasks.md");
|
|
107156
107631
|
const tasksMdExists = await Bun.file(tasksMdPath).exists();
|
|
107157
107632
|
const isFresh = !tasksMdExists;
|
|
107158
107633
|
if (isFresh) {
|
|
@@ -107185,9 +107660,7 @@ function createPrepareHelpers(input) {
|
|
|
107185
107660
|
} catch (err) {
|
|
107186
107661
|
diag("workflow", `! workflow render failed: ${err.message}`, "yellow");
|
|
107187
107662
|
}
|
|
107188
|
-
const appendPrompt =
|
|
107189
|
-
|
|
107190
|
-
`);
|
|
107663
|
+
const appendPrompt = composeAppendPrompt(args.prompt ?? "", cfg.appendPrompt ?? "", workflowPrompt);
|
|
107191
107664
|
changeName = await scaffoldChangeForIssue(scaffoldTasksDir, scaffoldStatesDir, issue2, comments, appendPrompt, attachments);
|
|
107192
107665
|
} else {
|
|
107193
107666
|
changeName = derivedName;
|
|
@@ -107230,7 +107703,7 @@ function createPrepareHelpers(input) {
|
|
|
107230
107703
|
if (!workerCwd)
|
|
107231
107704
|
return;
|
|
107232
107705
|
const wtLayout = projectLayout(workerCwd);
|
|
107233
|
-
const tasksFile =
|
|
107706
|
+
const tasksFile = join27(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
107234
107707
|
if (trigger === "review") {
|
|
107235
107708
|
let body2;
|
|
107236
107709
|
let heading;
|
|
@@ -107523,14 +107996,14 @@ var init_pr_discovery = __esm(() => {
|
|
|
107523
107996
|
});
|
|
107524
107997
|
|
|
107525
107998
|
// apps/agent/src/features/review-followup/scan.ts
|
|
107526
|
-
import { dirname as dirname11, join as
|
|
107999
|
+
import { dirname as dirname11, join as join28 } from "path";
|
|
107527
108000
|
async function resolveReviewStateDir(changeName, deps) {
|
|
107528
108001
|
const root = deps.cwdOf(changeName);
|
|
107529
108002
|
if (root)
|
|
107530
108003
|
return dirname11(projectLayout(root).stateFile(changeName));
|
|
107531
108004
|
if (!deps.useWorktree)
|
|
107532
108005
|
return dirname11(projectLayout(deps.projectRoot).stateFile(changeName));
|
|
107533
|
-
const wtPath =
|
|
108006
|
+
const wtPath = join28(worktreesDir2(deps.projectRoot), changeName);
|
|
107534
108007
|
const statePath = projectLayout(wtPath).stateFile(changeName);
|
|
107535
108008
|
if (await Bun.file(statePath).exists())
|
|
107536
108009
|
return dirname11(statePath);
|
|
@@ -107540,7 +108013,7 @@ async function readReviewWatermark(stateDir) {
|
|
|
107540
108013
|
const sidecar = await readSlotSidecar(stateDir, "review");
|
|
107541
108014
|
if (sidecar)
|
|
107542
108015
|
return sidecar.lastConsumedCommentAt ?? null;
|
|
107543
|
-
const file2 = Bun.file(
|
|
108016
|
+
const file2 = Bun.file(join28(stateDir, ".ralph-state.json"));
|
|
107544
108017
|
if (!await file2.exists())
|
|
107545
108018
|
return null;
|
|
107546
108019
|
try {
|
|
@@ -107752,7 +108225,7 @@ var init_github = __esm(() => {
|
|
|
107752
108225
|
|
|
107753
108226
|
// apps/agent/src/agent/wire/mention-scan.ts
|
|
107754
108227
|
import { readdir as readdir2 } from "fs/promises";
|
|
107755
|
-
import { join as
|
|
108228
|
+
import { join as join29 } from "path";
|
|
107756
108229
|
function createMentionScanner(input) {
|
|
107757
108230
|
const {
|
|
107758
108231
|
apiKey,
|
|
@@ -107918,7 +108391,7 @@ function createMentionScanner(input) {
|
|
|
107918
108391
|
async function isChangeArchivedForIssue(issue2, cwdByChange, projectRoot) {
|
|
107919
108392
|
const changeName = changeNameForIssue(issue2);
|
|
107920
108393
|
const root = cwdByChange.get(changeName) ?? projectRoot;
|
|
107921
|
-
const archiveDir =
|
|
108394
|
+
const archiveDir = join29(projectLayout(root).tasksDir, "archive");
|
|
107922
108395
|
let entries;
|
|
107923
108396
|
try {
|
|
107924
108397
|
entries = await readdir2(archiveDir);
|
|
@@ -107942,9 +108415,9 @@ var init_mention_scan = __esm(() => {
|
|
|
107942
108415
|
});
|
|
107943
108416
|
|
|
107944
108417
|
// apps/agent/src/agent/wire/spawn/default.ts
|
|
107945
|
-
import { join as
|
|
108418
|
+
import { join as join30 } from "path";
|
|
107946
108419
|
function defaultSpawn(changeName, cmd, cwd2, logsDir, onWorkerOutput, note) {
|
|
107947
|
-
const logFilePath =
|
|
108420
|
+
const logFilePath = join30(logsDir, `${changeName}.log`);
|
|
107948
108421
|
const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
107949
108422
|
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
107950
108423
|
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
@@ -108005,16 +108478,16 @@ var init_default2 = __esm(() => {
|
|
|
108005
108478
|
});
|
|
108006
108479
|
|
|
108007
108480
|
// apps/agent/src/agent/state/agent-run-state.ts
|
|
108008
|
-
import { basename as basename3, join as
|
|
108481
|
+
import { basename as basename3, join as join31 } from "path";
|
|
108009
108482
|
import { homedir as homedir6 } from "os";
|
|
108010
108483
|
import { mkdir as mkdir9, writeFile } from "fs/promises";
|
|
108011
108484
|
function agentRunStatePath(projectRoot) {
|
|
108012
|
-
return
|
|
108485
|
+
return join31(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
|
|
108013
108486
|
}
|
|
108014
108487
|
async function writeAgentRunState(state) {
|
|
108015
108488
|
const path = agentRunStatePath(state.projectRoot);
|
|
108016
108489
|
try {
|
|
108017
|
-
await mkdir9(
|
|
108490
|
+
await mkdir9(join31(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
|
|
108018
108491
|
await writeFile(path, JSON.stringify(state, null, 2) + `
|
|
108019
108492
|
`, "utf-8");
|
|
108020
108493
|
} catch {}
|
|
@@ -108041,17 +108514,17 @@ var CI_FAILED_EXIT2 = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
|
|
|
108041
108514
|
// packages/retro/src/paths.ts
|
|
108042
108515
|
import { homedir as homedir7 } from "os";
|
|
108043
108516
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
108044
|
-
import { join as
|
|
108517
|
+
import { join as join32 } from "path";
|
|
108045
108518
|
function retroDir() {
|
|
108046
|
-
return
|
|
108519
|
+
return join32(homedir7(), ".ralph", "retro");
|
|
108047
108520
|
}
|
|
108048
108521
|
async function resolveRetroOutputPath(identifier, date5, dir = retroDir()) {
|
|
108049
108522
|
await mkdir10(dir, { recursive: true });
|
|
108050
|
-
const base2 =
|
|
108523
|
+
const base2 = join32(dir, `${identifier}-${date5}.md`);
|
|
108051
108524
|
if (!await Bun.file(base2).exists())
|
|
108052
108525
|
return base2;
|
|
108053
108526
|
for (let n = 2;; n++) {
|
|
108054
|
-
const candidate =
|
|
108527
|
+
const candidate = join32(dir, `${identifier}-${date5}-${n}.md`);
|
|
108055
108528
|
if (!await Bun.file(candidate).exists())
|
|
108056
108529
|
return candidate;
|
|
108057
108530
|
}
|
|
@@ -108165,7 +108638,7 @@ var init_retro = __esm(() => {
|
|
|
108165
108638
|
});
|
|
108166
108639
|
|
|
108167
108640
|
// apps/agent/src/agent/wire/spawn/worker.ts
|
|
108168
|
-
import { join as
|
|
108641
|
+
import { join as join33 } from "path";
|
|
108169
108642
|
function localDateStamp(d) {
|
|
108170
108643
|
const y = d.getFullYear();
|
|
108171
108644
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -108188,6 +108661,103 @@ function buildTicketDigest(issue2, comments) {
|
|
|
108188
108661
|
function retroDepEntry(agentDebug, hook) {
|
|
108189
108662
|
return agentDebug ? { runRetrospective: hook } : {};
|
|
108190
108663
|
}
|
|
108664
|
+
function computeWantPr(wantPrBase, isAwaiting, isAwaitingConfirmation) {
|
|
108665
|
+
return wantPrBase && !isAwaiting && !isAwaitingConfirmation;
|
|
108666
|
+
}
|
|
108667
|
+
function computeWantValidateOnly(hasValidateSpec, wantPrBase) {
|
|
108668
|
+
return hasValidateSpec && !wantPrBase;
|
|
108669
|
+
}
|
|
108670
|
+
function releaseWorkerMaps(maps, changeName) {
|
|
108671
|
+
maps.cwdByChange.delete(changeName);
|
|
108672
|
+
maps.statesDirByChange.delete(changeName);
|
|
108673
|
+
maps.branchByChange.delete(changeName);
|
|
108674
|
+
maps.issueByChange.delete(changeName);
|
|
108675
|
+
}
|
|
108676
|
+
function buildTaskCmd(args, cfg, changeName) {
|
|
108677
|
+
const engine = args.engineSet ? args.engine : cfg.engine;
|
|
108678
|
+
const model = args.engineSet ? args.model : cfg.model;
|
|
108679
|
+
const c = [
|
|
108680
|
+
process.execPath,
|
|
108681
|
+
process.argv[1] ?? "",
|
|
108682
|
+
"loop",
|
|
108683
|
+
"task",
|
|
108684
|
+
"--name",
|
|
108685
|
+
changeName,
|
|
108686
|
+
"--" + engine,
|
|
108687
|
+
"--model",
|
|
108688
|
+
model
|
|
108689
|
+
];
|
|
108690
|
+
const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
|
|
108691
|
+
if (maxIter > 0)
|
|
108692
|
+
c.push("--max-iterations", String(maxIter));
|
|
108693
|
+
const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
|
|
108694
|
+
if (maxCost > 0)
|
|
108695
|
+
c.push("--max-cost", String(maxCost));
|
|
108696
|
+
const maxRuntime = args.maxRuntimeMinutes || cfg.maxRuntimeMinutesPerTask;
|
|
108697
|
+
if (maxRuntime > 0)
|
|
108698
|
+
c.push("--max-runtime", String(maxRuntime));
|
|
108699
|
+
const maxFailures = args.maxConsecutiveFailures !== 5 ? args.maxConsecutiveFailures : cfg.maxConsecutiveFailuresPerTask;
|
|
108700
|
+
if (maxFailures !== 5)
|
|
108701
|
+
c.push("--max-failures", String(maxFailures));
|
|
108702
|
+
const delay2 = args.delay || cfg.iterationDelaySeconds;
|
|
108703
|
+
if (delay2 > 0)
|
|
108704
|
+
c.push("--delay", String(delay2));
|
|
108705
|
+
if (args.log || cfg.logRawStream)
|
|
108706
|
+
c.push("--log");
|
|
108707
|
+
if (args.verbose || cfg.taskVerbose)
|
|
108708
|
+
c.push("--verbose");
|
|
108709
|
+
if (args.manualTest || cfg.enableManualTest)
|
|
108710
|
+
c.push("--manual-test");
|
|
108711
|
+
const rp = cfg.openspec.reviewPhase;
|
|
108712
|
+
if (rp.enabled) {
|
|
108713
|
+
c.push("--review-enabled");
|
|
108714
|
+
if (rp.maxRounds !== 1)
|
|
108715
|
+
c.push("--review-max-rounds", String(rp.maxRounds));
|
|
108716
|
+
if (rp.reviewerModel !== undefined)
|
|
108717
|
+
c.push("--review-model", rp.reviewerModel);
|
|
108718
|
+
if (rp.reviewerContextStrategy !== "fresh")
|
|
108719
|
+
c.push("--review-context-strategy", rp.reviewerContextStrategy);
|
|
108720
|
+
}
|
|
108721
|
+
c.push("--from-agent");
|
|
108722
|
+
return c;
|
|
108723
|
+
}
|
|
108724
|
+
function buildPostTaskInput(input) {
|
|
108725
|
+
const { args, cfg } = input;
|
|
108726
|
+
return {
|
|
108727
|
+
...input.trigger ? { mode: input.trigger } : {},
|
|
108728
|
+
...input.prUrl ? { prUrl: input.prUrl } : {},
|
|
108729
|
+
changeName: input.changeName,
|
|
108730
|
+
cwd: input.cwd,
|
|
108731
|
+
projectRoot: input.projectRoot,
|
|
108732
|
+
changeDir: input.changeDir,
|
|
108733
|
+
stateFilePath: input.stateFilePath,
|
|
108734
|
+
branch: input.branch,
|
|
108735
|
+
issue: input.issue,
|
|
108736
|
+
exitCode: input.exitCode,
|
|
108737
|
+
useWorktree: input.useWorktree,
|
|
108738
|
+
wantPr: input.wantPr,
|
|
108739
|
+
wantFixCi: input.wantFixCi,
|
|
108740
|
+
wantAutoMerge: input.wantAutoMerge,
|
|
108741
|
+
wantValidateOnly: input.wantValidateOnly,
|
|
108742
|
+
cfg: {
|
|
108743
|
+
teardownScript: cfg.teardownScript ?? null,
|
|
108744
|
+
prBaseBranch: cfg.prBaseBranch,
|
|
108745
|
+
autoMergeStrategy: cfg.autoMergeStrategy,
|
|
108746
|
+
maxCiFixAttempts: cfg.maxCiFixAttempts,
|
|
108747
|
+
ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
|
|
108748
|
+
cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
|
|
108749
|
+
ignoreCiChecks: cfg.ignoreCiChecks,
|
|
108750
|
+
stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
|
|
108751
|
+
neverTouch: cfg.boundaries.never_touch,
|
|
108752
|
+
metaOnlyFiles: cfg.boundaries.meta_only_files,
|
|
108753
|
+
finalizeNoOpAsDone: cfg.finalizeNoOpAsDone,
|
|
108754
|
+
manualMergeWhenAutoMergeDisabled: cfg.manualMergeWhenAutoMergeDisabled,
|
|
108755
|
+
prDraft: cfg.prDraft,
|
|
108756
|
+
validateCommands: [cfg.commands.test, cfg.commands.lint, cfg.commands.typecheck].filter((c) => Boolean(c))
|
|
108757
|
+
},
|
|
108758
|
+
respawnWorker: input.respawnWorker
|
|
108759
|
+
};
|
|
108760
|
+
}
|
|
108191
108761
|
function createSpawnWorker(input) {
|
|
108192
108762
|
const {
|
|
108193
108763
|
args,
|
|
@@ -108220,54 +108790,8 @@ function createSpawnWorker(input) {
|
|
|
108220
108790
|
onWorkerOutput,
|
|
108221
108791
|
onWorkerCmd
|
|
108222
108792
|
} = input;
|
|
108223
|
-
|
|
108224
|
-
|
|
108225
|
-
const model = args.engineSet ? args.model : cfg.model;
|
|
108226
|
-
const c = [
|
|
108227
|
-
process.execPath,
|
|
108228
|
-
process.argv[1] ?? "",
|
|
108229
|
-
"loop",
|
|
108230
|
-
"task",
|
|
108231
|
-
"--name",
|
|
108232
|
-
changeName,
|
|
108233
|
-
"--" + engine,
|
|
108234
|
-
"--model",
|
|
108235
|
-
model
|
|
108236
|
-
];
|
|
108237
|
-
const maxIter = args.maxIterations || cfg.maxIterationsPerTask;
|
|
108238
|
-
if (maxIter > 0)
|
|
108239
|
-
c.push("--max-iterations", String(maxIter));
|
|
108240
|
-
const maxCost = args.maxCostUsd || cfg.maxCostUsdPerTask;
|
|
108241
|
-
if (maxCost > 0)
|
|
108242
|
-
c.push("--max-cost", String(maxCost));
|
|
108243
|
-
const maxRuntime = args.maxRuntimeMinutes || cfg.maxRuntimeMinutesPerTask;
|
|
108244
|
-
if (maxRuntime > 0)
|
|
108245
|
-
c.push("--max-runtime", String(maxRuntime));
|
|
108246
|
-
const maxFailures = args.maxConsecutiveFailures !== 5 ? args.maxConsecutiveFailures : cfg.maxConsecutiveFailuresPerTask;
|
|
108247
|
-
if (maxFailures !== 5)
|
|
108248
|
-
c.push("--max-failures", String(maxFailures));
|
|
108249
|
-
const delay2 = args.delay || cfg.iterationDelaySeconds;
|
|
108250
|
-
if (delay2 > 0)
|
|
108251
|
-
c.push("--delay", String(delay2));
|
|
108252
|
-
if (args.log || cfg.logRawStream)
|
|
108253
|
-
c.push("--log");
|
|
108254
|
-
if (args.verbose || cfg.taskVerbose)
|
|
108255
|
-
c.push("--verbose");
|
|
108256
|
-
if (args.manualTest || cfg.enableManualTest)
|
|
108257
|
-
c.push("--manual-test");
|
|
108258
|
-
const rp = cfg.openspec.reviewPhase;
|
|
108259
|
-
if (rp.enabled) {
|
|
108260
|
-
c.push("--review-enabled");
|
|
108261
|
-
if (rp.maxRounds !== 1)
|
|
108262
|
-
c.push("--review-max-rounds", String(rp.maxRounds));
|
|
108263
|
-
if (rp.reviewerModel !== undefined)
|
|
108264
|
-
c.push("--review-model", rp.reviewerModel);
|
|
108265
|
-
if (rp.reviewerContextStrategy !== "fresh")
|
|
108266
|
-
c.push("--review-context-strategy", rp.reviewerContextStrategy);
|
|
108267
|
-
}
|
|
108268
|
-
c.push("--from-agent");
|
|
108269
|
-
return c;
|
|
108270
|
-
}
|
|
108793
|
+
const doPostTask = input.runners?.runPostTask ?? runPostTask;
|
|
108794
|
+
const buildTaskCmdFor = (changeName) => buildTaskCmd(args, cfg, changeName);
|
|
108271
108795
|
const retroSeen = new Set;
|
|
108272
108796
|
const runRetrospectiveHook = async (info) => {
|
|
108273
108797
|
try {
|
|
@@ -108296,7 +108820,7 @@ function createSpawnWorker(input) {
|
|
|
108296
108820
|
paths: {
|
|
108297
108821
|
changeDir: info.changeDir,
|
|
108298
108822
|
stateFilePath: info.stateFilePath,
|
|
108299
|
-
logFile:
|
|
108823
|
+
logFile: join33(logsDir, `${info.changeName}.log`),
|
|
108300
108824
|
jsonLogFile: args.jsonLogFile ?? null,
|
|
108301
108825
|
agentStateFile: agentRunStatePath(projectRoot)
|
|
108302
108826
|
}
|
|
@@ -108313,7 +108837,7 @@ function createSpawnWorker(input) {
|
|
|
108313
108837
|
return function spawnWorker(changeName, _issue, trigger) {
|
|
108314
108838
|
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
108315
108839
|
const injected = runners?.spawnWorker;
|
|
108316
|
-
const missionTasksPath =
|
|
108840
|
+
const missionTasksPath = join33(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
|
|
108317
108841
|
const prevTasksPromise = (async () => {
|
|
108318
108842
|
const f2 = Bun.file(missionTasksPath);
|
|
108319
108843
|
return await f2.exists() ? await f2.text() : "";
|
|
@@ -108321,7 +108845,7 @@ function createSpawnWorker(input) {
|
|
|
108321
108845
|
let logFilePath;
|
|
108322
108846
|
let handle;
|
|
108323
108847
|
if (injected) {
|
|
108324
|
-
logFilePath =
|
|
108848
|
+
logFilePath = join33(logsDir, `${changeName}.log`);
|
|
108325
108849
|
handle = injected(buildTaskCmdFor(changeName), cwd2);
|
|
108326
108850
|
} else {
|
|
108327
108851
|
const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, logsDir, onWorkerOutput, `spawn at ${new Date().toISOString()}`);
|
|
@@ -108343,9 +108867,9 @@ function createSpawnWorker(input) {
|
|
|
108343
108867
|
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
108344
108868
|
const wrapped = handle.exited.then(async (code) => {
|
|
108345
108869
|
const workerLayout = projectLayout(cwd2);
|
|
108346
|
-
const validateSpecPath =
|
|
108870
|
+
const validateSpecPath = join33(workerLayout.changeDir(changeName), "specs", "validate.md");
|
|
108347
108871
|
const hasValidateSpec = await Bun.file(validateSpecPath).exists();
|
|
108348
|
-
const wantValidateOnly = hasValidateSpec
|
|
108872
|
+
const wantValidateOnly = computeWantValidateOnly(hasValidateSpec, wantPrBase);
|
|
108349
108873
|
if (hasValidateSpec) {
|
|
108350
108874
|
try {
|
|
108351
108875
|
const stateFile = workerLayout.stateFile(changeName);
|
|
@@ -108389,10 +108913,10 @@ function createSpawnWorker(input) {
|
|
|
108389
108913
|
} catch (err) {
|
|
108390
108914
|
diag("tasks", `! tasks.md normalization failed: ${err.message}`, "yellow");
|
|
108391
108915
|
}
|
|
108392
|
-
const wantPr = wantPrBase
|
|
108393
|
-
const effectiveCode = await
|
|
108394
|
-
|
|
108395
|
-
|
|
108916
|
+
const wantPr = computeWantPr(wantPrBase, awaitingChangeSet.has(changeName), coordRef.current?.isAwaitingConfirmation(changeName) ?? false);
|
|
108917
|
+
const effectiveCode = await doPostTask(buildPostTaskInput({
|
|
108918
|
+
args,
|
|
108919
|
+
cfg,
|
|
108396
108920
|
changeName,
|
|
108397
108921
|
cwd: cwd2,
|
|
108398
108922
|
projectRoot,
|
|
@@ -108406,24 +108930,10 @@ function createSpawnWorker(input) {
|
|
|
108406
108930
|
wantFixCi,
|
|
108407
108931
|
wantAutoMerge,
|
|
108408
108932
|
wantValidateOnly,
|
|
108409
|
-
|
|
108410
|
-
|
|
108411
|
-
prBaseBranch: cfg.prBaseBranch,
|
|
108412
|
-
autoMergeStrategy: cfg.autoMergeStrategy,
|
|
108413
|
-
maxCiFixAttempts: cfg.maxCiFixAttempts,
|
|
108414
|
-
ciPollIntervalSeconds: cfg.ciPollIntervalSeconds,
|
|
108415
|
-
cleanupWorktreeOnSuccess: cfg.cleanupWorktreeOnSuccess,
|
|
108416
|
-
ignoreCiChecks: cfg.ignoreCiChecks,
|
|
108417
|
-
stackPrsOnDependencies: args.stackPrs || cfg.stackPrsOnDependencies,
|
|
108418
|
-
neverTouch: cfg.boundaries.never_touch,
|
|
108419
|
-
metaOnlyFiles: cfg.boundaries.meta_only_files,
|
|
108420
|
-
finalizeNoOpAsDone: cfg.finalizeNoOpAsDone,
|
|
108421
|
-
manualMergeWhenAutoMergeDisabled: cfg.manualMergeWhenAutoMergeDisabled,
|
|
108422
|
-
prDraft: cfg.prDraft,
|
|
108423
|
-
validateCommands: [cfg.commands.test, cfg.commands.lint, cfg.commands.typecheck].filter((c) => Boolean(c))
|
|
108424
|
-
},
|
|
108933
|
+
...trigger ? { trigger } : {},
|
|
108934
|
+
...prByChange?.get(changeName) ? { prUrl: prByChange.get(changeName) } : {},
|
|
108425
108935
|
respawnWorker: respawn
|
|
108426
|
-
}, {
|
|
108936
|
+
}), {
|
|
108427
108937
|
cmd: tracedCmd,
|
|
108428
108938
|
git: gitRunner,
|
|
108429
108939
|
log: onLog,
|
|
@@ -108462,10 +108972,7 @@ function createSpawnWorker(input) {
|
|
|
108462
108972
|
},
|
|
108463
108973
|
resolveDependencyBaseBranch: (issue2) => resolveDependencyBaseBranchImpl(issue2, tracedCmd, cwd2, { apiKey, onLog })
|
|
108464
108974
|
});
|
|
108465
|
-
cwdByChange
|
|
108466
|
-
statesDirByChange.delete(changeName);
|
|
108467
|
-
branchByChange.delete(changeName);
|
|
108468
|
-
issueByChange.delete(changeName);
|
|
108975
|
+
releaseWorkerMaps({ cwdByChange, statesDirByChange, branchByChange, issueByChange }, changeName);
|
|
108469
108976
|
onWorkerExited(changeName);
|
|
108470
108977
|
return effectiveCode;
|
|
108471
108978
|
});
|
|
@@ -108827,7 +109334,7 @@ var init_linear_sync = __esm(() => {
|
|
|
108827
109334
|
});
|
|
108828
109335
|
|
|
108829
109336
|
// apps/agent/src/agent/linear-sync/comment-sync.ts
|
|
108830
|
-
import { dirname as dirname12, join as
|
|
109337
|
+
import { dirname as dirname12, join as join34 } from "path";
|
|
108831
109338
|
async function readInlineLinearComments(statePath) {
|
|
108832
109339
|
const file2 = Bun.file(statePath);
|
|
108833
109340
|
if (!await file2.exists())
|
|
@@ -108868,7 +109375,7 @@ function isCommentNotFoundError(err) {
|
|
|
108868
109375
|
return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
|
|
108869
109376
|
}
|
|
108870
109377
|
async function readTasksMd(changeDir, log3) {
|
|
108871
|
-
const file2 = Bun.file(
|
|
109378
|
+
const file2 = Bun.file(join34(changeDir, "tasks.md"));
|
|
108872
109379
|
if (!await file2.exists()) {
|
|
108873
109380
|
log3(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
|
|
108874
109381
|
return null;
|
|
@@ -108974,14 +109481,14 @@ async function postPlanCommentOnce(deps) {
|
|
|
108974
109481
|
const check2 = parsePlanningSection(tasksMd);
|
|
108975
109482
|
if (!check2.allChecked)
|
|
108976
109483
|
return null;
|
|
108977
|
-
const proposalPath =
|
|
109484
|
+
const proposalPath = join34(deps.changeDir, "proposal.md");
|
|
108978
109485
|
const why = await readSection(proposalPath, "Why");
|
|
108979
109486
|
const whatChanges = await readSection(proposalPath, "What Changes");
|
|
108980
109487
|
if (!why && !whatChanges) {
|
|
108981
109488
|
deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
|
|
108982
109489
|
return null;
|
|
108983
109490
|
}
|
|
108984
|
-
const designSummary = await readFirstParagraph(
|
|
109491
|
+
const designSummary = await readFirstParagraph(join34(deps.changeDir, "design.md"));
|
|
108985
109492
|
const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
|
|
108986
109493
|
if (why) {
|
|
108987
109494
|
parts.push("", "**Why**", "", why);
|
|
@@ -261257,7 +261764,7 @@ var init_render_pdf = __esm(() => {
|
|
|
261257
261764
|
});
|
|
261258
261765
|
|
|
261259
261766
|
// apps/agent/src/agent/linear-sync/spec-attachments.ts
|
|
261260
|
-
import { dirname as dirname13, join as
|
|
261767
|
+
import { dirname as dirname13, join as join35 } from "path";
|
|
261261
261768
|
function describeLinearError(err) {
|
|
261262
261769
|
const e = err;
|
|
261263
261770
|
const parts = [e.message ?? String(err)];
|
|
@@ -261293,6 +261800,25 @@ async function readSpecAttachmentsSubtree(statePath) {
|
|
|
261293
261800
|
const sidecar = await readSlotSidecar(dirname13(statePath), "specAttachments");
|
|
261294
261801
|
return sidecar ?? await readInlineSpecAttachments(statePath);
|
|
261295
261802
|
}
|
|
261803
|
+
function asRevisions(value) {
|
|
261804
|
+
if (!Array.isArray(value))
|
|
261805
|
+
return [];
|
|
261806
|
+
const out = [];
|
|
261807
|
+
for (const entry of value) {
|
|
261808
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
261809
|
+
const e = entry;
|
|
261810
|
+
if (typeof e.version === "number" && typeof e.attachmentId === "string" && typeof e.sha256 === "string" && typeof e.trigger === "string") {
|
|
261811
|
+
out.push({
|
|
261812
|
+
version: e.version,
|
|
261813
|
+
attachmentId: e.attachmentId,
|
|
261814
|
+
sha256: e.sha256,
|
|
261815
|
+
trigger: e.trigger
|
|
261816
|
+
});
|
|
261817
|
+
}
|
|
261818
|
+
}
|
|
261819
|
+
}
|
|
261820
|
+
return out;
|
|
261821
|
+
}
|
|
261296
261822
|
async function readSpecAttachments(statePath) {
|
|
261297
261823
|
const sa = await readSpecAttachmentsSubtree(statePath);
|
|
261298
261824
|
return {
|
|
@@ -261311,12 +261837,45 @@ async function readSpecAttachments(statePath) {
|
|
|
261311
261837
|
designPdf: {
|
|
261312
261838
|
attachmentId: sa.designPdf?.attachmentId ?? null,
|
|
261313
261839
|
sha256: sa.designPdf?.sha256 ?? null
|
|
261314
|
-
}
|
|
261840
|
+
},
|
|
261841
|
+
designRevisions: asRevisions(sa.designRevisions),
|
|
261842
|
+
designPdfRevisions: asRevisions(sa.designPdfRevisions)
|
|
261315
261843
|
};
|
|
261316
261844
|
}
|
|
261317
261845
|
async function persistSlot(statePath, slot, value) {
|
|
261318
261846
|
await writeField(stateDirOf(statePath), "linear-attachments", `specAttachments.${slot}`, value);
|
|
261319
261847
|
}
|
|
261848
|
+
async function persistRevision(statePath, slot, revisions) {
|
|
261849
|
+
await writeField(stateDirOf(statePath), "linear-attachments", `specAttachments.${REVISIONS_KEY[slot]}`, revisions);
|
|
261850
|
+
}
|
|
261851
|
+
async function isDesignSealed(stateDir) {
|
|
261852
|
+
try {
|
|
261853
|
+
const pr2 = await readSlotSidecar(stateDir, "pr");
|
|
261854
|
+
const url2 = pr2?.url;
|
|
261855
|
+
if (typeof url2 === "string" && url2.length > 0)
|
|
261856
|
+
return true;
|
|
261857
|
+
} catch {}
|
|
261858
|
+
try {
|
|
261859
|
+
const confirmation = await readSlotSidecar(stateDir, "confirmation");
|
|
261860
|
+
if (confirmation?.earlyDraftPrAt != null)
|
|
261861
|
+
return true;
|
|
261862
|
+
} catch {}
|
|
261863
|
+
return false;
|
|
261864
|
+
}
|
|
261865
|
+
async function resolveTriggerLabel(stateDir) {
|
|
261866
|
+
try {
|
|
261867
|
+
const flow2 = await readSlotSidecar(stateDir, "flow");
|
|
261868
|
+
const snapshot = flow2?.actorSnapshot;
|
|
261869
|
+
const value = snapshot?.value;
|
|
261870
|
+
if (typeof value === "string" && TRIGGER_LABELS[value])
|
|
261871
|
+
return TRIGGER_LABELS[value];
|
|
261872
|
+
} catch {}
|
|
261873
|
+
return "revision";
|
|
261874
|
+
}
|
|
261875
|
+
function versionedTitle(slot, n, label) {
|
|
261876
|
+
const base2 = `Ralph design #${n} (${label})`;
|
|
261877
|
+
return slot === "designPdf" ? `${base2} (PDF)` : base2;
|
|
261878
|
+
}
|
|
261320
261879
|
async function adopt(deps, slot) {
|
|
261321
261880
|
const spec = SLOT_SPECS[slot];
|
|
261322
261881
|
try {
|
|
@@ -261361,12 +261920,71 @@ function extractImplementationSection(tasksMarkdown) {
|
|
|
261361
261920
|
return captured.join(`
|
|
261362
261921
|
`).trim();
|
|
261363
261922
|
}
|
|
261923
|
+
async function syncSlotSealed(deps, slot, sourceBytes, hash2, state) {
|
|
261924
|
+
const spec = SLOT_SPECS[slot];
|
|
261925
|
+
const revisions = state[REVISIONS_KEY[slot]];
|
|
261926
|
+
const v1Sha = state[slot]?.sha256 ?? null;
|
|
261927
|
+
if (hash2 === v1Sha || revisions.some((r) => r.sha256 === hash2)) {
|
|
261928
|
+
deps.log(` spec-attachments: ${spec.uploadFilename} unchanged (sealed), skipping`, "gray");
|
|
261929
|
+
return;
|
|
261930
|
+
}
|
|
261931
|
+
const n = 2 + revisions.length;
|
|
261932
|
+
const label = await resolveTriggerLabel(stateDirOf(deps.statePath));
|
|
261933
|
+
const title = versionedTitle(slot, n, label);
|
|
261934
|
+
let uploadBytes;
|
|
261935
|
+
try {
|
|
261936
|
+
uploadBytes = await spec.renderBytes(sourceBytes);
|
|
261937
|
+
} catch (err) {
|
|
261938
|
+
deps.log(`! spec-attachments: render ${spec.uploadFilename} (sealed) failed: ${err.message}`, "yellow");
|
|
261939
|
+
return;
|
|
261940
|
+
}
|
|
261941
|
+
let assetUrl;
|
|
261942
|
+
try {
|
|
261943
|
+
const uploaded = await deps.mutations.uploadFileToLinear(deps.apiKey, {
|
|
261944
|
+
filename: spec.uploadFilename,
|
|
261945
|
+
contentType: spec.contentType,
|
|
261946
|
+
bytes: uploadBytes
|
|
261947
|
+
});
|
|
261948
|
+
assetUrl = uploaded.assetUrl;
|
|
261949
|
+
} catch (err) {
|
|
261950
|
+
deps.log(`! spec-attachments: upload ${spec.uploadFilename} (sealed) failed: ${describeLinearError(err)}`, "yellow");
|
|
261951
|
+
return;
|
|
261952
|
+
}
|
|
261953
|
+
let attachmentId = null;
|
|
261954
|
+
try {
|
|
261955
|
+
attachmentId = await deps.mutations.findIssueAttachmentByTitle(deps.apiKey, deps.issueId, title);
|
|
261956
|
+
if (attachmentId) {
|
|
261957
|
+
deps.log(` spec-attachments: adopted existing ${title} attachment ${attachmentId}`, "gray");
|
|
261958
|
+
}
|
|
261959
|
+
} catch (err) {
|
|
261960
|
+
deps.log(`! spec-attachments: findIssueAttachmentByTitle ${title} failed (treating as no match): ${describeLinearError(err)}`, "yellow");
|
|
261961
|
+
attachmentId = null;
|
|
261962
|
+
}
|
|
261963
|
+
if (!attachmentId) {
|
|
261964
|
+
try {
|
|
261965
|
+
attachmentId = await deps.mutations.createAttachmentForUrl(deps.apiKey, {
|
|
261966
|
+
issueId: deps.issueId,
|
|
261967
|
+
url: assetUrl,
|
|
261968
|
+
title,
|
|
261969
|
+
subtitle: `iteration ${deps.iteration}`
|
|
261970
|
+
});
|
|
261971
|
+
} catch (err) {
|
|
261972
|
+
deps.log(`! spec-attachments: createAttachmentForUrl ${title} failed: ${describeLinearError(err)}`, "yellow");
|
|
261973
|
+
return;
|
|
261974
|
+
}
|
|
261975
|
+
deps.log(` spec-attachments: created ${title} attachment`, "gray");
|
|
261976
|
+
}
|
|
261977
|
+
await persistRevision(deps.statePath, slot, [
|
|
261978
|
+
...revisions,
|
|
261979
|
+
{ version: n, attachmentId, sha256: hash2, trigger: label }
|
|
261980
|
+
]);
|
|
261981
|
+
}
|
|
261364
261982
|
async function syncSlot(deps, slot) {
|
|
261365
261983
|
const spec = SLOT_SPECS[slot];
|
|
261366
261984
|
const [primaryName, ...trailingNames] = spec.sourceFiles;
|
|
261367
261985
|
if (!primaryName)
|
|
261368
261986
|
return;
|
|
261369
|
-
const primary = Bun.file(
|
|
261987
|
+
const primary = Bun.file(join35(deps.changeDir, primaryName));
|
|
261370
261988
|
if (!await primary.exists()) {
|
|
261371
261989
|
deps.log(` spec-attachments: ${primaryName} missing, skipping`, "gray");
|
|
261372
261990
|
return;
|
|
@@ -261385,7 +262003,7 @@ async function syncSlot(deps, slot) {
|
|
|
261385
262003
|
const parts = [primaryBytes];
|
|
261386
262004
|
const enc = new TextEncoder;
|
|
261387
262005
|
for (const name of trailingNames) {
|
|
261388
|
-
const f2 = Bun.file(
|
|
262006
|
+
const f2 = Bun.file(join35(deps.changeDir, name));
|
|
261389
262007
|
if (!await f2.exists())
|
|
261390
262008
|
continue;
|
|
261391
262009
|
let raw;
|
|
@@ -261416,7 +262034,12 @@ ${body}
|
|
|
261416
262034
|
offset += p.length;
|
|
261417
262035
|
}
|
|
261418
262036
|
const hash2 = sha256Hex(sourceBytes);
|
|
261419
|
-
|
|
262037
|
+
const state = await readSpecAttachments(deps.statePath);
|
|
262038
|
+
if (await isDesignSealed(stateDirOf(deps.statePath))) {
|
|
262039
|
+
await syncSlotSealed(deps, slot, sourceBytes, hash2, state);
|
|
262040
|
+
return;
|
|
262041
|
+
}
|
|
262042
|
+
let current = state[slot] ?? EMPTY_SLOT;
|
|
261420
262043
|
if (!current.attachmentId) {
|
|
261421
262044
|
const { adoptedId } = await adopt(deps, slot);
|
|
261422
262045
|
if (adoptedId) {
|
|
@@ -261518,7 +262141,7 @@ async function syncSpecAttachments(deps) {
|
|
|
261518
262141
|
await syncSlot(deps, slot);
|
|
261519
262142
|
}
|
|
261520
262143
|
}
|
|
261521
|
-
var identityRender = async (b2) => b2, pdfRender = (title) => async (b2) => renderMarkdownToPdf(new TextDecoder().decode(b2), title), SLOT_SPECS, LEGACY_SLOT_TITLES, EMPTY_SLOT;
|
|
262144
|
+
var identityRender = async (b2) => b2, pdfRender = (title) => async (b2) => renderMarkdownToPdf(new TextDecoder().decode(b2), title), SLOT_SPECS, LEGACY_SLOT_TITLES, REVISIONS_KEY, EMPTY_SLOT, TRIGGER_LABELS;
|
|
261522
262145
|
var init_spec_attachments = __esm(() => {
|
|
261523
262146
|
init_store();
|
|
261524
262147
|
init_comment_sync();
|
|
@@ -261545,7 +262168,16 @@ var init_spec_attachments = __esm(() => {
|
|
|
261545
262168
|
proposal: "Ralph proposal",
|
|
261546
262169
|
proposalPdf: "Ralph proposal (PDF)"
|
|
261547
262170
|
};
|
|
262171
|
+
REVISIONS_KEY = {
|
|
262172
|
+
design: "designRevisions",
|
|
262173
|
+
designPdf: "designPdfRevisions"
|
|
262174
|
+
};
|
|
261548
262175
|
EMPTY_SLOT = { attachmentId: null, sha256: null };
|
|
262176
|
+
TRIGGER_LABELS = {
|
|
262177
|
+
review: "review follow-up",
|
|
262178
|
+
"ci-fix": "CI fix",
|
|
262179
|
+
"conflict-fix": "conflict fix"
|
|
262180
|
+
};
|
|
261549
262181
|
});
|
|
261550
262182
|
|
|
261551
262183
|
// apps/agent/src/agent/wire/comment-sync.ts
|
|
@@ -261653,9 +262285,9 @@ var init_comment_sync2 = __esm(() => {
|
|
|
261653
262285
|
});
|
|
261654
262286
|
|
|
261655
262287
|
// apps/agent/src/features/pr-tracker/state.ts
|
|
261656
|
-
import { join as
|
|
262288
|
+
import { join as join36 } from "path";
|
|
261657
262289
|
async function readState2(projectRoot) {
|
|
261658
|
-
const path =
|
|
262290
|
+
const path = join36(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261659
262291
|
const file2 = Bun.file(path);
|
|
261660
262292
|
if (!await file2.exists())
|
|
261661
262293
|
return {};
|
|
@@ -261671,7 +262303,7 @@ async function readState2(projectRoot) {
|
|
|
261671
262303
|
}
|
|
261672
262304
|
}
|
|
261673
262305
|
async function writeState2(projectRoot, state) {
|
|
261674
|
-
const path =
|
|
262306
|
+
const path = join36(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261675
262307
|
await Bun.write(path, JSON.stringify(state, null, 2));
|
|
261676
262308
|
}
|
|
261677
262309
|
var PR_TRACKER_STATE_RELPATH = ".ralph/pr-tracker-state.json";
|
|
@@ -261750,7 +262382,7 @@ var init_pr_tracker = __esm(() => {
|
|
|
261750
262382
|
});
|
|
261751
262383
|
|
|
261752
262384
|
// apps/agent/src/agent/wire.ts
|
|
261753
|
-
import { join as
|
|
262385
|
+
import { join as join37 } from "path";
|
|
261754
262386
|
function buildAgentCoordinator(input) {
|
|
261755
262387
|
const {
|
|
261756
262388
|
args,
|
|
@@ -261769,7 +262401,7 @@ function buildAgentCoordinator(input) {
|
|
|
261769
262401
|
onWorkerCmd,
|
|
261770
262402
|
onAwaitingTicket
|
|
261771
262403
|
} = input;
|
|
261772
|
-
const logsDir =
|
|
262404
|
+
const logsDir = join37(projectRoot, ".ralph", "logs");
|
|
261773
262405
|
const bus = createBus();
|
|
261774
262406
|
subscribeAgentDiag(bus, onLog);
|
|
261775
262407
|
const diag = (area, message, color) => {
|
|
@@ -261795,7 +262427,11 @@ function buildAgentCoordinator(input) {
|
|
|
261795
262427
|
const awaitingChangeSet = new Set;
|
|
261796
262428
|
const coordRef = { current: null };
|
|
261797
262429
|
let pollContext = new PollContext;
|
|
261798
|
-
|
|
262430
|
+
let useWorktree = args.worktree || cfg.useWorktree;
|
|
262431
|
+
if (concurrency > 1 && !useWorktree) {
|
|
262432
|
+
diag("config", `! concurrency is ${concurrency} but useWorktree is off \u2014 forcing worktrees on so parallel tasks get isolated working copies`, "yellow");
|
|
262433
|
+
useWorktree = true;
|
|
262434
|
+
}
|
|
261799
262435
|
const scriptRunner = input.runners?.runScript ?? (async (cmd, cwd2) => {
|
|
261800
262436
|
const proc = Bun.spawn({
|
|
261801
262437
|
cmd: ["sh", "-c", cmd],
|
|
@@ -262000,7 +262636,7 @@ function buildAgentCoordinator(input) {
|
|
|
262000
262636
|
const changeDir = projectLayout(root).changeDir(changeName);
|
|
262001
262637
|
const parts = [];
|
|
262002
262638
|
for (const name of ["tasks.md", "proposal.md", "design.md"]) {
|
|
262003
|
-
const file2 = Bun.file(
|
|
262639
|
+
const file2 = Bun.file(join37(changeDir, name));
|
|
262004
262640
|
if (!await file2.exists())
|
|
262005
262641
|
continue;
|
|
262006
262642
|
parts.push(`${name}:${file2.lastModified}:${file2.size}`);
|
|
@@ -262045,7 +262681,7 @@ function buildAgentCoordinator(input) {
|
|
|
262045
262681
|
getGaveUpTotal: async () => {
|
|
262046
262682
|
let total = 0;
|
|
262047
262683
|
for (const [changeName, root] of cwdByChange) {
|
|
262048
|
-
const file2 = Bun.file(
|
|
262684
|
+
const file2 = Bun.file(join37(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
|
|
262049
262685
|
if (!await file2.exists())
|
|
262050
262686
|
continue;
|
|
262051
262687
|
try {
|
|
@@ -262333,7 +262969,7 @@ var init_output_utils = __esm(() => {
|
|
|
262333
262969
|
});
|
|
262334
262970
|
|
|
262335
262971
|
// apps/agent/src/agent/state/worker-state-poll.ts
|
|
262336
|
-
import { join as
|
|
262972
|
+
import { join as join38 } from "path";
|
|
262337
262973
|
function parseSubtasks(tasksMd) {
|
|
262338
262974
|
const out = [];
|
|
262339
262975
|
let skipSection = false;
|
|
@@ -262366,7 +263002,7 @@ function initialWorkerSnapshot() {
|
|
|
262366
263002
|
async function readWorkerSnapshot(input) {
|
|
262367
263003
|
const next = { ...input.prev };
|
|
262368
263004
|
try {
|
|
262369
|
-
const file2 = Bun.file(
|
|
263005
|
+
const file2 = Bun.file(join38(input.statesDir, input.changeName, ".ralph-state.json"));
|
|
262370
263006
|
if (await file2.exists()) {
|
|
262371
263007
|
const json2 = await file2.json();
|
|
262372
263008
|
next.iter = json2.iteration ?? next.iter;
|
|
@@ -262375,10 +263011,10 @@ async function readWorkerSnapshot(input) {
|
|
|
262375
263011
|
} catch {}
|
|
262376
263012
|
if (input.changeDir) {
|
|
262377
263013
|
try {
|
|
262378
|
-
const tasksFile = Bun.file(
|
|
262379
|
-
const proposalFile = Bun.file(
|
|
262380
|
-
const designFile = Bun.file(
|
|
262381
|
-
const reviewFindingsFile = Bun.file(
|
|
263014
|
+
const tasksFile = Bun.file(join38(input.changeDir, "tasks.md"));
|
|
263015
|
+
const proposalFile = Bun.file(join38(input.changeDir, "proposal.md"));
|
|
263016
|
+
const designFile = Bun.file(join38(input.changeDir, "design.md"));
|
|
263017
|
+
const reviewFindingsFile = Bun.file(join38(input.changeDir, "review-findings.md"));
|
|
262382
263018
|
const [tasksText, proposalText, designText, reviewFindingsText] = await Promise.all([
|
|
262383
263019
|
tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
|
|
262384
263020
|
proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
|
|
@@ -262441,7 +263077,7 @@ var init_worker_state_poll = __esm(() => {
|
|
|
262441
263077
|
});
|
|
262442
263078
|
|
|
262443
263079
|
// apps/agent/src/components/AgentMode.tsx
|
|
262444
|
-
import { join as
|
|
263080
|
+
import { join as join39 } from "path";
|
|
262445
263081
|
async function appendSteeringImpl(changeDir, message) {
|
|
262446
263082
|
await runWithContext(createDefaultContext(), async () => {
|
|
262447
263083
|
appendSteeringMessage(changeDir, message);
|
|
@@ -264014,7 +264650,7 @@ function AgentMode({
|
|
|
264014
264650
|
},
|
|
264015
264651
|
onSubmit: async (message) => {
|
|
264016
264652
|
try {
|
|
264017
|
-
await appendSteering2(
|
|
264653
|
+
await appendSteering2(join39(tasksDir, w2.changeName), message);
|
|
264018
264654
|
fileEmit({ type: "steering_submitted", changeName: w2.changeName, message });
|
|
264019
264655
|
} catch (err) {
|
|
264020
264656
|
const text = err.message;
|
|
@@ -264318,7 +264954,7 @@ __export(exports_list, {
|
|
|
264318
264954
|
buildBuckets: () => buildBuckets,
|
|
264319
264955
|
backlogRankByIssueId: () => backlogRankByIssueId
|
|
264320
264956
|
});
|
|
264321
|
-
import { join as
|
|
264957
|
+
import { join as join40 } from "path";
|
|
264322
264958
|
function countTaskItems(content) {
|
|
264323
264959
|
const checked = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
264324
264960
|
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
@@ -264334,13 +264970,13 @@ function buildLocalRows() {
|
|
|
264334
264970
|
const sources = [{ dir: statesDir, label: "main" }];
|
|
264335
264971
|
const worktreesRoot = worktreesDir2(projectRoot);
|
|
264336
264972
|
for (const wt of storage.list(worktreesRoot)) {
|
|
264337
|
-
sources.push({ dir:
|
|
264973
|
+
sources.push({ dir: join40(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
|
|
264338
264974
|
}
|
|
264339
264975
|
for (const { dir, label } of sources) {
|
|
264340
264976
|
for (const entry of storage.list(dir)) {
|
|
264341
264977
|
if (seen.has(entry))
|
|
264342
264978
|
continue;
|
|
264343
|
-
const raw = storage.read(
|
|
264979
|
+
const raw = storage.read(join40(dir, entry, ".ralph-state.json"));
|
|
264344
264980
|
if (raw === null)
|
|
264345
264981
|
continue;
|
|
264346
264982
|
let state;
|
|
@@ -264355,7 +264991,7 @@ function buildLocalRows() {
|
|
|
264355
264991
|
const firstLine = promptRaw.split(`
|
|
264356
264992
|
`).find((l3) => l3.trim() !== "") ?? "";
|
|
264357
264993
|
let progress = "\u2014";
|
|
264358
|
-
const tasksContent = storage.read(
|
|
264994
|
+
const tasksContent = storage.read(join40(dir, entry, "tasks.md"));
|
|
264359
264995
|
if (tasksContent !== null) {
|
|
264360
264996
|
const { checked, unchecked } = countTaskItems(tasksContent);
|
|
264361
264997
|
const total = checked + unchecked;
|
|
@@ -264855,7 +265491,7 @@ var exports_json_runner = {};
|
|
|
264855
265491
|
__export(exports_json_runner, {
|
|
264856
265492
|
runAgentJson: () => runAgentJson
|
|
264857
265493
|
});
|
|
264858
|
-
import { join as
|
|
265494
|
+
import { join as join41 } from "path";
|
|
264859
265495
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
264860
265496
|
import { homedir as homedir8 } from "os";
|
|
264861
265497
|
function makeEmit(fileSink) {
|
|
@@ -264877,7 +265513,7 @@ async function runAgentJson({
|
|
|
264877
265513
|
tasksDir,
|
|
264878
265514
|
runPreflight: runPreflight2 = runPreflight
|
|
264879
265515
|
}) {
|
|
264880
|
-
await mkdir12(
|
|
265516
|
+
await mkdir12(join41(homedir8(), ".ralph"), { recursive: true }).catch(() => {
|
|
264881
265517
|
return;
|
|
264882
265518
|
});
|
|
264883
265519
|
const fileSink = createJsonLogFileSink(args.jsonLogFile);
|
|
@@ -265094,7 +265730,7 @@ __export(exports_src3, {
|
|
|
265094
265730
|
main: () => main3
|
|
265095
265731
|
});
|
|
265096
265732
|
import { mkdir as mkdir13 } from "fs/promises";
|
|
265097
|
-
import { join as
|
|
265733
|
+
import { join as join42 } from "path";
|
|
265098
265734
|
async function main3(argv) {
|
|
265099
265735
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
265100
265736
|
printAgentHelp();
|
|
@@ -265160,7 +265796,7 @@ async function main3(argv) {
|
|
|
265160
265796
|
}
|
|
265161
265797
|
await mkdir13(statesDir, { recursive: true });
|
|
265162
265798
|
await mkdir13(tasksDir, { recursive: true });
|
|
265163
|
-
await mkdir13(
|
|
265799
|
+
await mkdir13(join42(projectRoot, ".ralph"), { recursive: true });
|
|
265164
265800
|
if (shouldFallbackToJsonOutput(args, process.stdin.isTTY)) {
|
|
265165
265801
|
process.stderr.write(`agent: stdin is not a TTY \u2014 falling back to --json-output mode.
|
|
265166
265802
|
`);
|
|
@@ -265218,6 +265854,7 @@ var init_src8 = __esm(async () => {
|
|
|
265218
265854
|
init_src();
|
|
265219
265855
|
init_src2();
|
|
265220
265856
|
init_version();
|
|
265857
|
+
init_common_args();
|
|
265221
265858
|
if (typeof globalThis.Bun === "undefined") {
|
|
265222
265859
|
process.stderr.write(`ralphy requires the Bun runtime (https://bun.sh/). It is not compatible with plain Node.js.
|
|
265223
265860
|
` + "Install Bun and re-run with `bun` or `bunx ralphy`.\n");
|
|
@@ -265302,7 +265939,8 @@ ${HELP}
|
|
|
265302
265939
|
if (shouldOfferSetup(subcommand, argv.slice(1))) {
|
|
265303
265940
|
try {
|
|
265304
265941
|
const { maybeRunSetupWizard: maybeRunSetupWizard2 } = await init_src4().then(() => exports_src);
|
|
265305
|
-
|
|
265942
|
+
const { projectRoot, workflowFile } = parseWorkflowPathArgs(argv.slice(1));
|
|
265943
|
+
await maybeRunSetupWizard2(projectRoot, workflowFile);
|
|
265306
265944
|
} catch (setupErr) {
|
|
265307
265945
|
captureError("setup_wizard_error", setupErr, { subcommand });
|
|
265308
265946
|
}
|