@neriros/ralphy 3.10.9 → 3.10.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shell/index.js +1377 -939
- 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.10")
|
|
18932
|
+
return "3.10.10";
|
|
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)
|
|
@@ -89292,7 +89723,7 @@ function formatTaskName(name) {
|
|
|
89292
89723
|
}
|
|
89293
89724
|
|
|
89294
89725
|
// packages/core/src/state.ts
|
|
89295
|
-
import { join as
|
|
89726
|
+
import { join as join11 } from "path";
|
|
89296
89727
|
function stripOwnedSlots(state) {
|
|
89297
89728
|
const out = { ...state };
|
|
89298
89729
|
for (const slot of ALL_OWNED_SLOTS)
|
|
@@ -89300,7 +89731,7 @@ function stripOwnedSlots(state) {
|
|
|
89300
89731
|
return out;
|
|
89301
89732
|
}
|
|
89302
89733
|
function readState(changeDir) {
|
|
89303
|
-
const filePath =
|
|
89734
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89304
89735
|
const raw = getStorage().read(filePath);
|
|
89305
89736
|
if (raw === null)
|
|
89306
89737
|
throw new Error(".ralph-state.json not found");
|
|
@@ -89309,7 +89740,7 @@ function readState(changeDir) {
|
|
|
89309
89740
|
return StateSchema.parse(base2);
|
|
89310
89741
|
}
|
|
89311
89742
|
function tryReadStateRaw(changeDir) {
|
|
89312
|
-
const filePath =
|
|
89743
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89313
89744
|
const text = getStorage().read(filePath);
|
|
89314
89745
|
if (text === null)
|
|
89315
89746
|
return { state: null, raw: null };
|
|
@@ -89325,7 +89756,7 @@ function tryReadStateRaw(changeDir) {
|
|
|
89325
89756
|
return { state: result2.success ? result2.data : null, raw };
|
|
89326
89757
|
}
|
|
89327
89758
|
function writeState(changeDir, state) {
|
|
89328
|
-
const filePath =
|
|
89759
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89329
89760
|
const core2 = stripOwnedSlots(state);
|
|
89330
89761
|
getStorage().write(filePath, JSON.stringify(core2, null, 2) + `
|
|
89331
89762
|
`);
|
|
@@ -89366,7 +89797,7 @@ function buildInitialState(options) {
|
|
|
89366
89797
|
});
|
|
89367
89798
|
}
|
|
89368
89799
|
function ensureState(changeDir) {
|
|
89369
|
-
const filePath =
|
|
89800
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89370
89801
|
const storage = getStorage();
|
|
89371
89802
|
if (storage.read(filePath) !== null) {
|
|
89372
89803
|
return readState(changeDir);
|
|
@@ -89385,7 +89816,7 @@ var init_state = __esm(() => {
|
|
|
89385
89816
|
});
|
|
89386
89817
|
|
|
89387
89818
|
// packages/core/src/state/store.ts
|
|
89388
|
-
import { join as
|
|
89819
|
+
import { join as join12 } from "path";
|
|
89389
89820
|
async function readJson(filePath) {
|
|
89390
89821
|
const file2 = Bun.file(filePath);
|
|
89391
89822
|
if (!await file2.exists())
|
|
@@ -89409,7 +89840,7 @@ async function writeField(changeDir, featureName, path, value) {
|
|
|
89409
89840
|
if (!allowed.includes(topSlot)) {
|
|
89410
89841
|
throw new OwnershipError(featureName, path, `feature '${featureName}' may not write '${path}' (owns ${allowed.join(", ")})`);
|
|
89411
89842
|
}
|
|
89412
|
-
const inline = (await readJson(
|
|
89843
|
+
const inline = (await readJson(join12(changeDir, STATE_FILE3)))[topSlot];
|
|
89413
89844
|
const seed = inline && typeof inline === "object" && !Array.isArray(inline) ? inline : undefined;
|
|
89414
89845
|
await writeSlotField(changeDir, path, value, seed);
|
|
89415
89846
|
}
|
|
@@ -89432,14 +89863,14 @@ var init_store = __esm(() => {
|
|
|
89432
89863
|
});
|
|
89433
89864
|
|
|
89434
89865
|
// apps/loop/src/components/TaskStatus.tsx
|
|
89435
|
-
import { join as
|
|
89866
|
+
import { join as join13 } from "path";
|
|
89436
89867
|
function TaskStatus({ state, stateDir }) {
|
|
89437
89868
|
const storage = getStorage();
|
|
89438
89869
|
const cost = Math.round(state.usage.total_cost_usd * 100) / 100;
|
|
89439
89870
|
const time3 = Math.round(state.usage.total_duration_ms / 1000 * 10) / 10 + "s";
|
|
89440
89871
|
const artifacts = OPENSPEC_ARTIFACTS.map((name) => ({
|
|
89441
89872
|
name,
|
|
89442
|
-
exists: storage.read(
|
|
89873
|
+
exists: storage.read(join13(stateDir, name)) !== null
|
|
89443
89874
|
}));
|
|
89444
89875
|
const recent = state.history.slice(-10);
|
|
89445
89876
|
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
@@ -98157,7 +98588,7 @@ var init_rate_limit_detection = __esm(() => {
|
|
|
98157
98588
|
|
|
98158
98589
|
// packages/engine/src/agents/claude.ts
|
|
98159
98590
|
import { mkdtemp, unlink as unlink2 } from "fs/promises";
|
|
98160
|
-
import { join as
|
|
98591
|
+
import { join as join14 } from "path";
|
|
98161
98592
|
import { tmpdir } from "os";
|
|
98162
98593
|
function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, reviewerModel) {
|
|
98163
98594
|
const effectiveModel = reviewerModel ?? model;
|
|
@@ -98178,7 +98609,7 @@ function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, review
|
|
|
98178
98609
|
}
|
|
98179
98610
|
async function runInteractive(req) {
|
|
98180
98611
|
const { model, prompt, taskDir } = req;
|
|
98181
|
-
const promptFile = taskDir ?
|
|
98612
|
+
const promptFile = taskDir ? join14(taskDir, "_interactive_prompt.md") : join14(await mkdtemp(join14(tmpdir(), "ralph-")), "prompt.md");
|
|
98182
98613
|
await Bun.write(promptFile, prompt);
|
|
98183
98614
|
try {
|
|
98184
98615
|
const cmd = [
|
|
@@ -98205,7 +98636,7 @@ async function runInteractive(req) {
|
|
|
98205
98636
|
env: scrubClaudeEnv(process.env)
|
|
98206
98637
|
});
|
|
98207
98638
|
const exitCode = await proc.exited;
|
|
98208
|
-
const doneFile = taskDir ?
|
|
98639
|
+
const doneFile = taskDir ? join14(taskDir, "_interactive_done") : null;
|
|
98209
98640
|
if (doneFile && await Bun.file(doneFile).exists()) {
|
|
98210
98641
|
return { exitCode: 0, usage: null, sessionId: null, rateLimited: false };
|
|
98211
98642
|
}
|
|
@@ -99708,11 +100139,11 @@ var init_meta_prompt = __esm(() => {
|
|
|
99708
100139
|
});
|
|
99709
100140
|
|
|
99710
100141
|
// packages/core/src/loop.ts
|
|
99711
|
-
import { join as
|
|
100142
|
+
import { join as join15 } from "path";
|
|
99712
100143
|
function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
99713
100144
|
const storage = getStorage();
|
|
99714
100145
|
let prompt = "";
|
|
99715
|
-
const steeringContent = storage.read(
|
|
100146
|
+
const steeringContent = storage.read(join15(taskDir, "steering.md"));
|
|
99716
100147
|
if (steeringContent !== null) {
|
|
99717
100148
|
const steeringLines = steeringContent.split(`
|
|
99718
100149
|
`).filter((line) => !line.startsWith("#")).filter((line) => line.trim()).slice(0, STEERING_MAX_LINES);
|
|
@@ -99731,8 +100162,8 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99731
100162
|
`;
|
|
99732
100163
|
}
|
|
99733
100164
|
}
|
|
99734
|
-
const agentTasksPath =
|
|
99735
|
-
const missionTasksPath =
|
|
100165
|
+
const agentTasksPath = join15(taskDir, AGENT_TASKS_FILENAME);
|
|
100166
|
+
const missionTasksPath = join15(taskDir, MISSION_TASKS_FILENAME);
|
|
99736
100167
|
const agentTasksContent = storage.read(agentTasksPath);
|
|
99737
100168
|
const missionTasksContent = storage.read(missionTasksPath);
|
|
99738
100169
|
let activePath = null;
|
|
@@ -99808,7 +100239,7 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99808
100239
|
}
|
|
99809
100240
|
}
|
|
99810
100241
|
if (reviewPhase?.enabled) {
|
|
99811
|
-
const reviewFindingsPath =
|
|
100242
|
+
const reviewFindingsPath = join15(taskDir, "review-findings.md");
|
|
99812
100243
|
const reviewFindingsContent = storage.read(reviewFindingsPath);
|
|
99813
100244
|
const hasUncheckedMission = missionTasksContent !== null && /^- \[ \]/m.test(missionTasksContent);
|
|
99814
100245
|
const hasUncheckedAgent = agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent);
|
|
@@ -99882,7 +100313,7 @@ When all tasks are complete and all files are committed, push your branch and op
|
|
|
99882
100313
|
}
|
|
99883
100314
|
function buildSteeringBlock(taskDir) {
|
|
99884
100315
|
const storage = getStorage();
|
|
99885
|
-
const steeringContent = storage.read(
|
|
100316
|
+
const steeringContent = storage.read(join15(taskDir, "steering.md"));
|
|
99886
100317
|
if (steeringContent === null)
|
|
99887
100318
|
return "";
|
|
99888
100319
|
const steeringLines = steeringContent.split(`
|
|
@@ -99980,7 +100411,7 @@ function buildPlanPrompt(state, taskDir) {
|
|
|
99980
100411
|
return prompt;
|
|
99981
100412
|
}
|
|
99982
100413
|
function buildReviewPrompt(state, taskDir) {
|
|
99983
|
-
const reviewFindingsPath =
|
|
100414
|
+
const reviewFindingsPath = join15(taskDir, "review-findings.md");
|
|
99984
100415
|
let prompt = buildSteeringBlock(taskDir);
|
|
99985
100416
|
prompt += `---
|
|
99986
100417
|
|
|
@@ -100045,7 +100476,7 @@ function buildPhasePrompt(phase, state, taskDir, reviewPhase, metaPromptOptions)
|
|
|
100045
100476
|
}
|
|
100046
100477
|
function checkStopSignal(taskDir, stateDir) {
|
|
100047
100478
|
const storage = getStorage();
|
|
100048
|
-
const stopFile =
|
|
100479
|
+
const stopFile = join15(taskDir, "STOP");
|
|
100049
100480
|
const reason = storage.read(stopFile);
|
|
100050
100481
|
if (reason === null)
|
|
100051
100482
|
return null;
|
|
@@ -100105,7 +100536,7 @@ function updateStateIteration(stateDir, result2, startedAt, engine, model, usage
|
|
|
100105
100536
|
}
|
|
100106
100537
|
function appendSteeringMessage(taskDir, message) {
|
|
100107
100538
|
const storage = getStorage();
|
|
100108
|
-
const steeringPath =
|
|
100539
|
+
const steeringPath = join15(taskDir, "steering.md");
|
|
100109
100540
|
const existing = storage.read(steeringPath);
|
|
100110
100541
|
const updated = existing ? `${message}
|
|
100111
100542
|
|
|
@@ -100155,7 +100586,7 @@ var init_loop2 = __esm(() => {
|
|
|
100155
100586
|
});
|
|
100156
100587
|
|
|
100157
100588
|
// apps/loop/src/hooks/useLoop.ts
|
|
100158
|
-
import { join as
|
|
100589
|
+
import { join as join16 } from "path";
|
|
100159
100590
|
function sleep(seconds) {
|
|
100160
100591
|
return new Promise((resolve4) => setTimeout(resolve4, seconds * 1000));
|
|
100161
100592
|
}
|
|
@@ -100271,8 +100702,8 @@ function useLoop(opts) {
|
|
|
100271
100702
|
setState(currentState);
|
|
100272
100703
|
if (!actor.getSnapshot().matches("running"))
|
|
100273
100704
|
break;
|
|
100274
|
-
const tasksContent = storage.read(
|
|
100275
|
-
const agentTasksContent = storage.read(
|
|
100705
|
+
const tasksContent = storage.read(join16(tasksDir, MISSION_TASKS_FILENAME));
|
|
100706
|
+
const agentTasksContent = storage.read(join16(tasksDir, AGENT_TASKS_FILENAME));
|
|
100276
100707
|
if (tasksContent === null && currentState.iteration > 0 && typeof opts.changeStore.listChanges === "function") {
|
|
100277
100708
|
let stillActive = true;
|
|
100278
100709
|
try {
|
|
@@ -100309,7 +100740,7 @@ function useLoop(opts) {
|
|
|
100309
100740
|
const agentDone = agentTasksContent === null || allCompleted(agentTasksContent);
|
|
100310
100741
|
if (missionDone && agentDone && tasksContent !== null) {
|
|
100311
100742
|
if (opts.reviewPhase?.enabled) {
|
|
100312
|
-
const reviewFindingsPath =
|
|
100743
|
+
const reviewFindingsPath = join16(tasksDir, "review-findings.md");
|
|
100313
100744
|
const reviewFindingsFile = Bun.file(reviewFindingsPath);
|
|
100314
100745
|
const findingsExists = await reviewFindingsFile.exists();
|
|
100315
100746
|
const findingsContent = findingsExists ? await reviewFindingsFile.text() : null;
|
|
@@ -100338,7 +100769,7 @@ function useLoop(opts) {
|
|
|
100338
100769
|
model: opts.reviewPhase.reviewerModel ?? opts.model,
|
|
100339
100770
|
prompt: reviewPrompt,
|
|
100340
100771
|
logFlag: opts.log,
|
|
100341
|
-
logFile:
|
|
100772
|
+
logFile: join16(stateDir, `log-review-${roundNum}.json`),
|
|
100342
100773
|
taskDir: tasksDir,
|
|
100343
100774
|
reviewerContextStrategy: opts.reviewPhase.reviewerContextStrategy ?? "fresh",
|
|
100344
100775
|
onFeedEvent: addFeedEvent
|
|
@@ -100412,8 +100843,8 @@ function useLoop(opts) {
|
|
|
100412
100843
|
const time3 = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
100413
100844
|
addIterationHeader(localIter, time3);
|
|
100414
100845
|
addInfo(`Iteration ${localIter} (total: ${currentState.iteration})`);
|
|
100415
|
-
const proposalContent = storage.read(
|
|
100416
|
-
const designContent = storage.read(
|
|
100846
|
+
const proposalContent = storage.read(join16(tasksDir, "proposal.md"));
|
|
100847
|
+
const designContent = storage.read(join16(tasksDir, "design.md"));
|
|
100417
100848
|
const routedPhase = routeTaskPhase(opts.phase, {
|
|
100418
100849
|
proposal: proposalContent,
|
|
100419
100850
|
design: designContent,
|
|
@@ -100433,7 +100864,7 @@ function useLoop(opts) {
|
|
|
100433
100864
|
model: opts.model,
|
|
100434
100865
|
prompt,
|
|
100435
100866
|
logFlag: opts.log,
|
|
100436
|
-
logFile:
|
|
100867
|
+
logFile: join16(stateDir, "log.json"),
|
|
100437
100868
|
taskDir: tasksDir,
|
|
100438
100869
|
interactive: false,
|
|
100439
100870
|
onFeedEvent: addFeedEvent,
|
|
@@ -100456,7 +100887,7 @@ function useLoop(opts) {
|
|
|
100456
100887
|
model: opts.model,
|
|
100457
100888
|
prompt: buildSteeringPrompt(steerMessage),
|
|
100458
100889
|
logFlag: opts.log,
|
|
100459
|
-
logFile:
|
|
100890
|
+
logFile: join16(stateDir, "log.json"),
|
|
100460
100891
|
taskDir: tasksDir,
|
|
100461
100892
|
onFeedEvent: addResumeFeedEvent,
|
|
100462
100893
|
signal: resumeController.signal,
|
|
@@ -100763,7 +101194,7 @@ var init_TaskLoop = __esm(async () => {
|
|
|
100763
101194
|
});
|
|
100764
101195
|
|
|
100765
101196
|
// apps/loop/src/components/App.tsx
|
|
100766
|
-
import { join as
|
|
101197
|
+
import { join as join17 } from "path";
|
|
100767
101198
|
function ExitAfterRender({ children }) {
|
|
100768
101199
|
const { exit } = use_app_default();
|
|
100769
101200
|
import_react59.useEffect(() => {
|
|
@@ -100816,7 +101247,7 @@ function App2({ args, taskPhase }) {
|
|
|
100816
101247
|
}
|
|
100817
101248
|
const layout = getLayout();
|
|
100818
101249
|
const stateDir = layout.taskStateDir(args.name);
|
|
100819
|
-
if (getStorage().read(
|
|
101250
|
+
if (getStorage().read(join17(stateDir, ".ralph-state.json")) === null) {
|
|
100820
101251
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
|
|
100821
101252
|
message: `Error: change '${args.name}' not found`
|
|
100822
101253
|
}, undefined, false, undefined, this);
|
|
@@ -100870,7 +101301,7 @@ var init_App2 = __esm(async () => {
|
|
|
100870
101301
|
|
|
100871
101302
|
// packages/log/src/log.ts
|
|
100872
101303
|
import { appendFile } from "fs/promises";
|
|
100873
|
-
import { join as
|
|
101304
|
+
import { join as join18, dirname as dirname7 } from "path";
|
|
100874
101305
|
import { homedir as homedir4 } from "os";
|
|
100875
101306
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
100876
101307
|
function fmt(type, text) {
|
|
@@ -100919,14 +101350,14 @@ var init_log = __esm(() => {
|
|
|
100919
101350
|
init_version();
|
|
100920
101351
|
jsonLogChains = new Map;
|
|
100921
101352
|
ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
100922
|
-
AGENT_LOG_PATH =
|
|
101353
|
+
AGENT_LOG_PATH = join18(homedir4(), ".ralph", "agent-mode.log");
|
|
100923
101354
|
mkdir5(dirname7(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
100924
101355
|
return;
|
|
100925
101356
|
});
|
|
100926
101357
|
});
|
|
100927
101358
|
|
|
100928
101359
|
// apps/loop/src/debug.ts
|
|
100929
|
-
import { join as
|
|
101360
|
+
import { join as join19 } from "path";
|
|
100930
101361
|
function fmtTs(d) {
|
|
100931
101362
|
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
100932
101363
|
}
|
|
@@ -101038,7 +101469,7 @@ function detectDebugStuck(lines) {
|
|
|
101038
101469
|
};
|
|
101039
101470
|
}
|
|
101040
101471
|
async function inspectBinary(projectRoot) {
|
|
101041
|
-
const binPath =
|
|
101472
|
+
const binPath = join19(projectRoot, ".ralph", "bin", "cli.js");
|
|
101042
101473
|
const file2 = Bun.file(binPath);
|
|
101043
101474
|
if (!await file2.exists())
|
|
101044
101475
|
return null;
|
|
@@ -101063,7 +101494,7 @@ async function inspectBinary(projectRoot) {
|
|
|
101063
101494
|
async function resolveDebugTarget(projectRoot, opts) {
|
|
101064
101495
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
101065
101496
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
101066
|
-
const jsonlLogFile = Bun.file(
|
|
101497
|
+
const jsonlLogFile = Bun.file(join19(projectRoot, ".ralph", "agent.log"));
|
|
101067
101498
|
const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
|
|
101068
101499
|
const allLines = [...textLines, ...jsonlLines];
|
|
101069
101500
|
if (opts.name && !opts.issue) {
|
|
@@ -101168,7 +101599,7 @@ async function runDebug(opts) {
|
|
|
101168
101599
|
`);
|
|
101169
101600
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
101170
101601
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
101171
|
-
const jsonlLogPath =
|
|
101602
|
+
const jsonlLogPath = join19(projectRoot, ".ralph", "agent.log");
|
|
101172
101603
|
const jsonlLogFile = Bun.file(jsonlLogPath);
|
|
101173
101604
|
const hasJsonlLog = await jsonlLogFile.exists();
|
|
101174
101605
|
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
|
|
@@ -101182,7 +101613,7 @@ async function runDebug(opts) {
|
|
|
101182
101613
|
}
|
|
101183
101614
|
const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
|
|
101184
101615
|
const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
101185
|
-
const workerLogFile = Bun.file(
|
|
101616
|
+
const workerLogFile = Bun.file(join19(projectRoot, ".ralph", "logs", `${changeName}.log`));
|
|
101186
101617
|
const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
|
|
101187
101618
|
const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
101188
101619
|
const seen = new Set;
|
|
@@ -101341,8 +101772,8 @@ async function runDebug(opts) {
|
|
|
101341
101772
|
out(" \u26A0 PR currently has merge conflicts");
|
|
101342
101773
|
if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
|
|
101343
101774
|
out(" \u26A0 PR has failing CI checks");
|
|
101344
|
-
const worktreePath =
|
|
101345
|
-
if (await Bun.file(
|
|
101775
|
+
const worktreePath = join19(projectRoot, ".ralph", "worktrees", changeName);
|
|
101776
|
+
if (await Bun.file(join19(worktreePath, ".git")).exists()) {
|
|
101346
101777
|
out(` Worktree : ${worktreePath}`);
|
|
101347
101778
|
}
|
|
101348
101779
|
if (!timeline.length)
|
|
@@ -101362,12 +101793,12 @@ __export(exports_src2, {
|
|
|
101362
101793
|
taskMain: () => taskMain,
|
|
101363
101794
|
main: () => main2
|
|
101364
101795
|
});
|
|
101365
|
-
import { join as
|
|
101796
|
+
import { join as join20 } from "path";
|
|
101366
101797
|
import { exists as exists2, mkdir as mkdir6, rm as rm2 } from "fs/promises";
|
|
101367
101798
|
async function ensureRalphGitignore(projectRoot) {
|
|
101368
|
-
const ralphDir =
|
|
101799
|
+
const ralphDir = join20(projectRoot, ".ralph");
|
|
101369
101800
|
await mkdir6(ralphDir, { recursive: true });
|
|
101370
|
-
const gitignorePath =
|
|
101801
|
+
const gitignorePath = join20(ralphDir, ".gitignore");
|
|
101371
101802
|
const file2 = Bun.file(gitignorePath);
|
|
101372
101803
|
if (await file2.exists()) {
|
|
101373
101804
|
const existing = await file2.text();
|
|
@@ -101416,7 +101847,7 @@ async function main2(argv) {
|
|
|
101416
101847
|
Bun.spawnSync({
|
|
101417
101848
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
101418
101849
|
stdio: ["inherit", "inherit", "inherit"],
|
|
101419
|
-
cwd:
|
|
101850
|
+
cwd: projectRoot
|
|
101420
101851
|
});
|
|
101421
101852
|
}
|
|
101422
101853
|
if (args.mode === "debug") {
|
|
@@ -101434,9 +101865,9 @@ async function main2(argv) {
|
|
|
101434
101865
|
`);
|
|
101435
101866
|
return 1;
|
|
101436
101867
|
}
|
|
101437
|
-
const worktreeDir =
|
|
101438
|
-
const changeDir =
|
|
101439
|
-
const stateDir =
|
|
101868
|
+
const worktreeDir = join20(worktreesDir(projectRoot), args.name);
|
|
101869
|
+
const changeDir = join20(tasksDir, args.name);
|
|
101870
|
+
const stateDir = join20(statesDir, args.name);
|
|
101440
101871
|
const branch = `ralph/${args.name}`;
|
|
101441
101872
|
const removed = [];
|
|
101442
101873
|
if (await exists2(worktreeDir)) {
|
|
@@ -101487,8 +101918,8 @@ async function main2(argv) {
|
|
|
101487
101918
|
return 0;
|
|
101488
101919
|
}
|
|
101489
101920
|
if (args.mode === "task" && args.name) {
|
|
101490
|
-
await mkdir6(
|
|
101491
|
-
await mkdir6(
|
|
101921
|
+
await mkdir6(join20(statesDir, args.name), { recursive: true });
|
|
101922
|
+
await mkdir6(join20(tasksDir, args.name), { recursive: true });
|
|
101492
101923
|
await ensureRalphGitignore(projectRoot);
|
|
101493
101924
|
}
|
|
101494
101925
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
@@ -101516,8 +101947,8 @@ async function taskMain(argv) {
|
|
|
101516
101947
|
const layout = projectLayout(projectRoot);
|
|
101517
101948
|
const statesDir = layout.statesDir;
|
|
101518
101949
|
const tasksDir = layout.tasksDir;
|
|
101519
|
-
await mkdir6(
|
|
101520
|
-
await mkdir6(
|
|
101950
|
+
await mkdir6(join20(statesDir, args.name), { recursive: true });
|
|
101951
|
+
await mkdir6(join20(tasksDir, args.name), { recursive: true });
|
|
101521
101952
|
await ensureRalphGitignore(projectRoot);
|
|
101522
101953
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
101523
101954
|
const { waitUntilExit } = render_default(import_react60.createElement(App2, {
|
|
@@ -101760,6 +102191,7 @@ async function parseAgentArgs(argv) {
|
|
|
101760
102191
|
}
|
|
101761
102192
|
}
|
|
101762
102193
|
await resolvePromptFile(result2, state);
|
|
102194
|
+
resolveWorkflowFile(result2, state);
|
|
101763
102195
|
if (result2.fixCi && !result2.createPr) {
|
|
101764
102196
|
throw new Error("--fix-ci requires --create-pr");
|
|
101765
102197
|
}
|
|
@@ -101917,7 +102349,7 @@ function formatError2(err) {
|
|
|
101917
102349
|
}
|
|
101918
102350
|
|
|
101919
102351
|
// apps/agent/src/shared/capabilities/fs-change.ts
|
|
101920
|
-
import { join as
|
|
102352
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
101921
102353
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
101922
102354
|
var scaffold, prependTask, appendSteering, fsChange;
|
|
101923
102355
|
var init_fs_change = __esm(() => {
|
|
@@ -101930,11 +102362,11 @@ var init_fs_change = __esm(() => {
|
|
|
101930
102362
|
errorFormatter: formatError2,
|
|
101931
102363
|
run: async (args) => {
|
|
101932
102364
|
await mkdir7(args.changeDir, { recursive: true });
|
|
101933
|
-
await mkdir7(
|
|
102365
|
+
await mkdir7(join21(args.changeDir, "specs"), { recursive: true });
|
|
101934
102366
|
await mkdir7(args.stateDir, { recursive: true });
|
|
101935
|
-
await Bun.write(
|
|
101936
|
-
await Bun.write(
|
|
101937
|
-
await Bun.write(
|
|
102367
|
+
await Bun.write(join21(args.changeDir, "proposal.md"), args.proposal);
|
|
102368
|
+
await Bun.write(join21(args.changeDir, "tasks.md"), args.tasks);
|
|
102369
|
+
await Bun.write(join21(args.changeDir, "design.md"), args.design);
|
|
101938
102370
|
}
|
|
101939
102371
|
};
|
|
101940
102372
|
prependTask = {
|
|
@@ -101952,7 +102384,7 @@ var init_fs_change = __esm(() => {
|
|
|
101952
102384
|
retryPolicy: NO_RETRY,
|
|
101953
102385
|
errorFormatter: formatError2,
|
|
101954
102386
|
run: async (args) => {
|
|
101955
|
-
const path =
|
|
102387
|
+
const path = join21(args.changeDir, "steering.md");
|
|
101956
102388
|
const f2 = Bun.file(path);
|
|
101957
102389
|
const existing = await f2.exists() ? await f2.text() : null;
|
|
101958
102390
|
const updated = existing ? `${args.message}
|
|
@@ -101967,11 +102399,11 @@ ${existing.trimStart()}` : `${args.message}
|
|
|
101967
102399
|
});
|
|
101968
102400
|
|
|
101969
102401
|
// apps/agent/src/agent/worktree.ts
|
|
101970
|
-
import { basename as basename2, join as
|
|
102402
|
+
import { basename as basename2, join as join22 } from "path";
|
|
101971
102403
|
import { homedir as homedir5 } from "os";
|
|
101972
102404
|
import { exists as exists3 } from "fs/promises";
|
|
101973
102405
|
function worktreesDir2(projectRoot) {
|
|
101974
|
-
return
|
|
102406
|
+
return join22(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
|
|
101975
102407
|
}
|
|
101976
102408
|
function branchForChange(changeName) {
|
|
101977
102409
|
return `ralph/${changeName}`;
|
|
@@ -101990,7 +102422,7 @@ function createWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
101990
102422
|
}
|
|
101991
102423
|
async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
101992
102424
|
const dir = worktreesDir2(projectRoot);
|
|
101993
|
-
const cwd2 =
|
|
102425
|
+
const cwd2 = join22(dir, changeName);
|
|
101994
102426
|
const branch = branchForChange(changeName);
|
|
101995
102427
|
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
101996
102428
|
if (list.stdout.includes(`worktree ${cwd2}
|
|
@@ -102015,7 +102447,7 @@ async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
102015
102447
|
return { cwd: cwd2, branch };
|
|
102016
102448
|
}
|
|
102017
102449
|
async function installPrePushHook(cwd2, runner) {
|
|
102018
|
-
const hookPath =
|
|
102450
|
+
const hookPath = join22(cwd2, ".ralph-hooks", "pre-push");
|
|
102019
102451
|
await Bun.write(hookPath, PRE_PUSH_HOOK_SCRIPT);
|
|
102020
102452
|
const chmod = Bun.spawn(["chmod", "+x", hookPath]);
|
|
102021
102453
|
await chmod.exited;
|
|
@@ -102061,8 +102493,8 @@ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
|
|
|
102061
102493
|
return { safe: true, dirty, unpushedCommits };
|
|
102062
102494
|
}
|
|
102063
102495
|
async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
102064
|
-
const dst =
|
|
102065
|
-
const src =
|
|
102496
|
+
const dst = join22(worktreeCwd, ".mcp.json");
|
|
102497
|
+
const src = join22(projectRoot, ".mcp.json");
|
|
102066
102498
|
const source = await exists3(dst) ? dst : await exists3(src) ? src : null;
|
|
102067
102499
|
if (!source)
|
|
102068
102500
|
return;
|
|
@@ -102076,7 +102508,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
|
102076
102508
|
if (servers && typeof servers === "object") {
|
|
102077
102509
|
for (const cfg of Object.values(servers)) {
|
|
102078
102510
|
if (Array.isArray(cfg.args)) {
|
|
102079
|
-
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ?
|
|
102511
|
+
cfg.args = cfg.args.map((a) => typeof a === "string" && a.startsWith(".ralph/") ? join22(projectRoot, a) : a);
|
|
102080
102512
|
}
|
|
102081
102513
|
}
|
|
102082
102514
|
}
|
|
@@ -103926,7 +104358,7 @@ function emitFeatureSkipped(bus, id, reason) {
|
|
|
103926
104358
|
var init_run_feature = () => {};
|
|
103927
104359
|
|
|
103928
104360
|
// apps/agent/src/agent/post-task.ts
|
|
103929
|
-
import { join as
|
|
104361
|
+
import { join as join23, dirname as dirname9 } from "path";
|
|
103930
104362
|
function summarizeUncommittedStatus(stdout) {
|
|
103931
104363
|
const lines = stdout.split(`
|
|
103932
104364
|
`).filter((line) => line.length > 0);
|
|
@@ -103998,7 +104430,7 @@ async function reactivateState(stateFilePath, log3, changeName) {
|
|
|
103998
104430
|
async function runWorkerWithFixTask(ctx, heading, body) {
|
|
103999
104431
|
try {
|
|
104000
104432
|
await runCapability(fsChange.prependTask, {
|
|
104001
|
-
tasksPath:
|
|
104433
|
+
tasksPath: join23(ctx.changeDir, AGENT_TASKS_FILENAME),
|
|
104002
104434
|
heading,
|
|
104003
104435
|
failureOutput: body
|
|
104004
104436
|
});
|
|
@@ -104549,7 +104981,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104549
104981
|
emit3("validate-fix", command);
|
|
104550
104982
|
log3(`! validation check failed: ${command}`, "yellow");
|
|
104551
104983
|
try {
|
|
104552
|
-
await prependFixTask(
|
|
104984
|
+
await prependFixTask(join23(changeDir, AGENT_TASKS_FILENAME), `Fix failing validation: ${command}`, output || `Command exited with code ${exitCode}`);
|
|
104553
104985
|
} catch (err) {
|
|
104554
104986
|
log3(`! could not prepend fix task: ${err.message}`, "red");
|
|
104555
104987
|
return 1;
|
|
@@ -104560,7 +104992,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104560
104992
|
}
|
|
104561
104993
|
}
|
|
104562
104994
|
try {
|
|
104563
|
-
await prependFixTask(
|
|
104995
|
+
await prependFixTask(join23(changeDir, AGENT_TASKS_FILENAME), "Run openspec validation", [
|
|
104564
104996
|
`Run \`bunx openspec validate ${changeName}\` to validate the change artifacts.`,
|
|
104565
104997
|
`Commit any pending changes before running the validation command.`
|
|
104566
104998
|
].join(`
|
|
@@ -104573,7 +105005,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104573
105005
|
return respawnWorker();
|
|
104574
105006
|
}
|
|
104575
105007
|
async function recordGaveUp(stateFilePath, log3, changeName) {
|
|
104576
|
-
const path =
|
|
105008
|
+
const path = join23(dirname9(stateFilePath), GAVEUP_COUNT_FILE);
|
|
104577
105009
|
try {
|
|
104578
105010
|
const file2 = Bun.file(path);
|
|
104579
105011
|
const current = await file2.exists() ? Number.parseInt(await file2.text(), 10) || 0 : 0;
|
|
@@ -105980,15 +106412,15 @@ var init_coordinator2 = __esm(() => {
|
|
|
105980
106412
|
});
|
|
105981
106413
|
|
|
105982
106414
|
// apps/agent/src/agent/scaffold.ts
|
|
105983
|
-
import { join as
|
|
106415
|
+
import { join as join24 } from "path";
|
|
105984
106416
|
function changeNameForIssue(issue2) {
|
|
105985
106417
|
const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
|
|
105986
106418
|
return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
|
|
105987
106419
|
}
|
|
105988
106420
|
async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = [], appendPrompt = "", attachments = []) {
|
|
105989
106421
|
const name = changeNameForIssue(issue2);
|
|
105990
|
-
const changeDir =
|
|
105991
|
-
const stateDir =
|
|
106422
|
+
const changeDir = join24(tasksDir, name);
|
|
106423
|
+
const stateDir = join24(statesDir, name);
|
|
105992
106424
|
const commentsBlock = comments.length > 0 ? [
|
|
105993
106425
|
"",
|
|
105994
106426
|
"## Linear comments",
|
|
@@ -106124,7 +106556,7 @@ var init_detections = __esm(() => {
|
|
|
106124
106556
|
});
|
|
106125
106557
|
|
|
106126
106558
|
// apps/agent/src/features/confirmation/state.ts
|
|
106127
|
-
import { dirname as dirname10, join as
|
|
106559
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
106128
106560
|
async function readInlineConfirmation(statePath) {
|
|
106129
106561
|
const f2 = Bun.file(statePath);
|
|
106130
106562
|
if (!await f2.exists())
|
|
@@ -106163,8 +106595,8 @@ async function restartFromDesign(changeDir, changeName) {
|
|
|
106163
106595
|
""
|
|
106164
106596
|
].join(`
|
|
106165
106597
|
`);
|
|
106166
|
-
await Bun.write(
|
|
106167
|
-
const tasksPath =
|
|
106598
|
+
await Bun.write(join25(changeDir, "design.md"), designStub);
|
|
106599
|
+
const tasksPath = join25(changeDir, "tasks.md");
|
|
106168
106600
|
if (await Bun.file(tasksPath).exists()) {
|
|
106169
106601
|
await Bun.write(tasksPath, `# Tasks
|
|
106170
106602
|
|
|
@@ -106394,7 +106826,7 @@ var init_inspect = __esm(() => {
|
|
|
106394
106826
|
});
|
|
106395
106827
|
|
|
106396
106828
|
// apps/agent/src/features/confirmation/awaiting.ts
|
|
106397
|
-
import { join as
|
|
106829
|
+
import { join as join26 } from "path";
|
|
106398
106830
|
async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
106399
106831
|
const tracked = deps.cwdOf(changeName);
|
|
106400
106832
|
if (tracked)
|
|
@@ -106402,12 +106834,12 @@ async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
|
106402
106834
|
if (!deps.useWorktree)
|
|
106403
106835
|
return deps.projectRoot;
|
|
106404
106836
|
const root = worktreesDir2(deps.projectRoot);
|
|
106405
|
-
const canonical =
|
|
106406
|
-
if (await Bun.file(
|
|
106837
|
+
const canonical = join26(root, worktreeDirNameForIssue(issue2));
|
|
106838
|
+
if (await Bun.file(join26(canonical, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106407
106839
|
return canonical;
|
|
106408
106840
|
}
|
|
106409
|
-
const legacy =
|
|
106410
|
-
if (await Bun.file(
|
|
106841
|
+
const legacy = join26(root, changeName);
|
|
106842
|
+
if (await Bun.file(join26(legacy, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106411
106843
|
return legacy;
|
|
106412
106844
|
}
|
|
106413
106845
|
return deps.projectRoot;
|
|
@@ -106542,9 +106974,9 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
106542
106974
|
const layout = projectLayout(cwd2);
|
|
106543
106975
|
const changeDir = layout.changeDir(changeName);
|
|
106544
106976
|
const statePath = layout.stateFile(changeName);
|
|
106545
|
-
const tasks2 = await readTextOrNull(
|
|
106546
|
-
const proposal = await readTextOrNull(
|
|
106547
|
-
const design = await readTextOrNull(
|
|
106977
|
+
const tasks2 = await readTextOrNull(join26(changeDir, "tasks.md"));
|
|
106978
|
+
const proposal = await readTextOrNull(join26(changeDir, "proposal.md"));
|
|
106979
|
+
const design = await readTextOrNull(join26(changeDir, "design.md"));
|
|
106548
106980
|
let commentsCache = null;
|
|
106549
106981
|
const getComments = async () => {
|
|
106550
106982
|
if (commentsCache)
|
|
@@ -107088,7 +107520,7 @@ var init_linear_resolvers = __esm(() => {
|
|
|
107088
107520
|
|
|
107089
107521
|
// apps/agent/src/agent/wire/prepare.ts
|
|
107090
107522
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
107091
|
-
import { join as
|
|
107523
|
+
import { join as join27 } from "path";
|
|
107092
107524
|
function createPrepareHelpers(input) {
|
|
107093
107525
|
const {
|
|
107094
107526
|
args,
|
|
@@ -107152,7 +107584,7 @@ function createPrepareHelpers(input) {
|
|
|
107152
107584
|
let changeName;
|
|
107153
107585
|
const wtLayoutPre = projectLayout(workerCwd);
|
|
107154
107586
|
const derivedName = changeNameForIssue(issue2);
|
|
107155
|
-
const tasksMdPath =
|
|
107587
|
+
const tasksMdPath = join27(wtLayoutPre.changeDir(derivedName), "tasks.md");
|
|
107156
107588
|
const tasksMdExists = await Bun.file(tasksMdPath).exists();
|
|
107157
107589
|
const isFresh = !tasksMdExists;
|
|
107158
107590
|
if (isFresh) {
|
|
@@ -107230,7 +107662,7 @@ function createPrepareHelpers(input) {
|
|
|
107230
107662
|
if (!workerCwd)
|
|
107231
107663
|
return;
|
|
107232
107664
|
const wtLayout = projectLayout(workerCwd);
|
|
107233
|
-
const tasksFile =
|
|
107665
|
+
const tasksFile = join27(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
107234
107666
|
if (trigger === "review") {
|
|
107235
107667
|
let body2;
|
|
107236
107668
|
let heading;
|
|
@@ -107523,14 +107955,14 @@ var init_pr_discovery = __esm(() => {
|
|
|
107523
107955
|
});
|
|
107524
107956
|
|
|
107525
107957
|
// apps/agent/src/features/review-followup/scan.ts
|
|
107526
|
-
import { dirname as dirname11, join as
|
|
107958
|
+
import { dirname as dirname11, join as join28 } from "path";
|
|
107527
107959
|
async function resolveReviewStateDir(changeName, deps) {
|
|
107528
107960
|
const root = deps.cwdOf(changeName);
|
|
107529
107961
|
if (root)
|
|
107530
107962
|
return dirname11(projectLayout(root).stateFile(changeName));
|
|
107531
107963
|
if (!deps.useWorktree)
|
|
107532
107964
|
return dirname11(projectLayout(deps.projectRoot).stateFile(changeName));
|
|
107533
|
-
const wtPath =
|
|
107965
|
+
const wtPath = join28(worktreesDir2(deps.projectRoot), changeName);
|
|
107534
107966
|
const statePath = projectLayout(wtPath).stateFile(changeName);
|
|
107535
107967
|
if (await Bun.file(statePath).exists())
|
|
107536
107968
|
return dirname11(statePath);
|
|
@@ -107540,7 +107972,7 @@ async function readReviewWatermark(stateDir) {
|
|
|
107540
107972
|
const sidecar = await readSlotSidecar(stateDir, "review");
|
|
107541
107973
|
if (sidecar)
|
|
107542
107974
|
return sidecar.lastConsumedCommentAt ?? null;
|
|
107543
|
-
const file2 = Bun.file(
|
|
107975
|
+
const file2 = Bun.file(join28(stateDir, ".ralph-state.json"));
|
|
107544
107976
|
if (!await file2.exists())
|
|
107545
107977
|
return null;
|
|
107546
107978
|
try {
|
|
@@ -107752,7 +108184,7 @@ var init_github = __esm(() => {
|
|
|
107752
108184
|
|
|
107753
108185
|
// apps/agent/src/agent/wire/mention-scan.ts
|
|
107754
108186
|
import { readdir as readdir2 } from "fs/promises";
|
|
107755
|
-
import { join as
|
|
108187
|
+
import { join as join29 } from "path";
|
|
107756
108188
|
function createMentionScanner(input) {
|
|
107757
108189
|
const {
|
|
107758
108190
|
apiKey,
|
|
@@ -107918,7 +108350,7 @@ function createMentionScanner(input) {
|
|
|
107918
108350
|
async function isChangeArchivedForIssue(issue2, cwdByChange, projectRoot) {
|
|
107919
108351
|
const changeName = changeNameForIssue(issue2);
|
|
107920
108352
|
const root = cwdByChange.get(changeName) ?? projectRoot;
|
|
107921
|
-
const archiveDir =
|
|
108353
|
+
const archiveDir = join29(projectLayout(root).tasksDir, "archive");
|
|
107922
108354
|
let entries;
|
|
107923
108355
|
try {
|
|
107924
108356
|
entries = await readdir2(archiveDir);
|
|
@@ -107942,9 +108374,9 @@ var init_mention_scan = __esm(() => {
|
|
|
107942
108374
|
});
|
|
107943
108375
|
|
|
107944
108376
|
// apps/agent/src/agent/wire/spawn/default.ts
|
|
107945
|
-
import { join as
|
|
108377
|
+
import { join as join30 } from "path";
|
|
107946
108378
|
function defaultSpawn(changeName, cmd, cwd2, logsDir, onWorkerOutput, note) {
|
|
107947
|
-
const logFilePath =
|
|
108379
|
+
const logFilePath = join30(logsDir, `${changeName}.log`);
|
|
107948
108380
|
const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
107949
108381
|
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
107950
108382
|
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
@@ -108005,16 +108437,16 @@ var init_default2 = __esm(() => {
|
|
|
108005
108437
|
});
|
|
108006
108438
|
|
|
108007
108439
|
// apps/agent/src/agent/state/agent-run-state.ts
|
|
108008
|
-
import { basename as basename3, join as
|
|
108440
|
+
import { basename as basename3, join as join31 } from "path";
|
|
108009
108441
|
import { homedir as homedir6 } from "os";
|
|
108010
108442
|
import { mkdir as mkdir9, writeFile } from "fs/promises";
|
|
108011
108443
|
function agentRunStatePath(projectRoot) {
|
|
108012
|
-
return
|
|
108444
|
+
return join31(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
|
|
108013
108445
|
}
|
|
108014
108446
|
async function writeAgentRunState(state) {
|
|
108015
108447
|
const path = agentRunStatePath(state.projectRoot);
|
|
108016
108448
|
try {
|
|
108017
|
-
await mkdir9(
|
|
108449
|
+
await mkdir9(join31(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
|
|
108018
108450
|
await writeFile(path, JSON.stringify(state, null, 2) + `
|
|
108019
108451
|
`, "utf-8");
|
|
108020
108452
|
} catch {}
|
|
@@ -108041,17 +108473,17 @@ var CI_FAILED_EXIT2 = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
|
|
|
108041
108473
|
// packages/retro/src/paths.ts
|
|
108042
108474
|
import { homedir as homedir7 } from "os";
|
|
108043
108475
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
108044
|
-
import { join as
|
|
108476
|
+
import { join as join32 } from "path";
|
|
108045
108477
|
function retroDir() {
|
|
108046
|
-
return
|
|
108478
|
+
return join32(homedir7(), ".ralph", "retro");
|
|
108047
108479
|
}
|
|
108048
108480
|
async function resolveRetroOutputPath(identifier, date5, dir = retroDir()) {
|
|
108049
108481
|
await mkdir10(dir, { recursive: true });
|
|
108050
|
-
const base2 =
|
|
108482
|
+
const base2 = join32(dir, `${identifier}-${date5}.md`);
|
|
108051
108483
|
if (!await Bun.file(base2).exists())
|
|
108052
108484
|
return base2;
|
|
108053
108485
|
for (let n = 2;; n++) {
|
|
108054
|
-
const candidate =
|
|
108486
|
+
const candidate = join32(dir, `${identifier}-${date5}-${n}.md`);
|
|
108055
108487
|
if (!await Bun.file(candidate).exists())
|
|
108056
108488
|
return candidate;
|
|
108057
108489
|
}
|
|
@@ -108165,7 +108597,7 @@ var init_retro = __esm(() => {
|
|
|
108165
108597
|
});
|
|
108166
108598
|
|
|
108167
108599
|
// apps/agent/src/agent/wire/spawn/worker.ts
|
|
108168
|
-
import { join as
|
|
108600
|
+
import { join as join33 } from "path";
|
|
108169
108601
|
function localDateStamp(d) {
|
|
108170
108602
|
const y = d.getFullYear();
|
|
108171
108603
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -108296,7 +108728,7 @@ function createSpawnWorker(input) {
|
|
|
108296
108728
|
paths: {
|
|
108297
108729
|
changeDir: info.changeDir,
|
|
108298
108730
|
stateFilePath: info.stateFilePath,
|
|
108299
|
-
logFile:
|
|
108731
|
+
logFile: join33(logsDir, `${info.changeName}.log`),
|
|
108300
108732
|
jsonLogFile: args.jsonLogFile ?? null,
|
|
108301
108733
|
agentStateFile: agentRunStatePath(projectRoot)
|
|
108302
108734
|
}
|
|
@@ -108313,7 +108745,7 @@ function createSpawnWorker(input) {
|
|
|
108313
108745
|
return function spawnWorker(changeName, _issue, trigger) {
|
|
108314
108746
|
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
108315
108747
|
const injected = runners?.spawnWorker;
|
|
108316
|
-
const missionTasksPath =
|
|
108748
|
+
const missionTasksPath = join33(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
|
|
108317
108749
|
const prevTasksPromise = (async () => {
|
|
108318
108750
|
const f2 = Bun.file(missionTasksPath);
|
|
108319
108751
|
return await f2.exists() ? await f2.text() : "";
|
|
@@ -108321,7 +108753,7 @@ function createSpawnWorker(input) {
|
|
|
108321
108753
|
let logFilePath;
|
|
108322
108754
|
let handle;
|
|
108323
108755
|
if (injected) {
|
|
108324
|
-
logFilePath =
|
|
108756
|
+
logFilePath = join33(logsDir, `${changeName}.log`);
|
|
108325
108757
|
handle = injected(buildTaskCmdFor(changeName), cwd2);
|
|
108326
108758
|
} else {
|
|
108327
108759
|
const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, logsDir, onWorkerOutput, `spawn at ${new Date().toISOString()}`);
|
|
@@ -108343,7 +108775,7 @@ function createSpawnWorker(input) {
|
|
|
108343
108775
|
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
108344
108776
|
const wrapped = handle.exited.then(async (code) => {
|
|
108345
108777
|
const workerLayout = projectLayout(cwd2);
|
|
108346
|
-
const validateSpecPath =
|
|
108778
|
+
const validateSpecPath = join33(workerLayout.changeDir(changeName), "specs", "validate.md");
|
|
108347
108779
|
const hasValidateSpec = await Bun.file(validateSpecPath).exists();
|
|
108348
108780
|
const wantValidateOnly = hasValidateSpec && !wantPrBase;
|
|
108349
108781
|
if (hasValidateSpec) {
|
|
@@ -108827,7 +109259,7 @@ var init_linear_sync = __esm(() => {
|
|
|
108827
109259
|
});
|
|
108828
109260
|
|
|
108829
109261
|
// apps/agent/src/agent/linear-sync/comment-sync.ts
|
|
108830
|
-
import { dirname as dirname12, join as
|
|
109262
|
+
import { dirname as dirname12, join as join34 } from "path";
|
|
108831
109263
|
async function readInlineLinearComments(statePath) {
|
|
108832
109264
|
const file2 = Bun.file(statePath);
|
|
108833
109265
|
if (!await file2.exists())
|
|
@@ -108868,7 +109300,7 @@ function isCommentNotFoundError(err) {
|
|
|
108868
109300
|
return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
|
|
108869
109301
|
}
|
|
108870
109302
|
async function readTasksMd(changeDir, log3) {
|
|
108871
|
-
const file2 = Bun.file(
|
|
109303
|
+
const file2 = Bun.file(join34(changeDir, "tasks.md"));
|
|
108872
109304
|
if (!await file2.exists()) {
|
|
108873
109305
|
log3(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
|
|
108874
109306
|
return null;
|
|
@@ -108974,14 +109406,14 @@ async function postPlanCommentOnce(deps) {
|
|
|
108974
109406
|
const check2 = parsePlanningSection(tasksMd);
|
|
108975
109407
|
if (!check2.allChecked)
|
|
108976
109408
|
return null;
|
|
108977
|
-
const proposalPath =
|
|
109409
|
+
const proposalPath = join34(deps.changeDir, "proposal.md");
|
|
108978
109410
|
const why = await readSection(proposalPath, "Why");
|
|
108979
109411
|
const whatChanges = await readSection(proposalPath, "What Changes");
|
|
108980
109412
|
if (!why && !whatChanges) {
|
|
108981
109413
|
deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
|
|
108982
109414
|
return null;
|
|
108983
109415
|
}
|
|
108984
|
-
const designSummary = await readFirstParagraph(
|
|
109416
|
+
const designSummary = await readFirstParagraph(join34(deps.changeDir, "design.md"));
|
|
108985
109417
|
const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
|
|
108986
109418
|
if (why) {
|
|
108987
109419
|
parts.push("", "**Why**", "", why);
|
|
@@ -261257,7 +261689,7 @@ var init_render_pdf = __esm(() => {
|
|
|
261257
261689
|
});
|
|
261258
261690
|
|
|
261259
261691
|
// apps/agent/src/agent/linear-sync/spec-attachments.ts
|
|
261260
|
-
import { dirname as dirname13, join as
|
|
261692
|
+
import { dirname as dirname13, join as join35 } from "path";
|
|
261261
261693
|
function describeLinearError(err) {
|
|
261262
261694
|
const e = err;
|
|
261263
261695
|
const parts = [e.message ?? String(err)];
|
|
@@ -261366,7 +261798,7 @@ async function syncSlot(deps, slot) {
|
|
|
261366
261798
|
const [primaryName, ...trailingNames] = spec.sourceFiles;
|
|
261367
261799
|
if (!primaryName)
|
|
261368
261800
|
return;
|
|
261369
|
-
const primary = Bun.file(
|
|
261801
|
+
const primary = Bun.file(join35(deps.changeDir, primaryName));
|
|
261370
261802
|
if (!await primary.exists()) {
|
|
261371
261803
|
deps.log(` spec-attachments: ${primaryName} missing, skipping`, "gray");
|
|
261372
261804
|
return;
|
|
@@ -261385,7 +261817,7 @@ async function syncSlot(deps, slot) {
|
|
|
261385
261817
|
const parts = [primaryBytes];
|
|
261386
261818
|
const enc = new TextEncoder;
|
|
261387
261819
|
for (const name of trailingNames) {
|
|
261388
|
-
const f2 = Bun.file(
|
|
261820
|
+
const f2 = Bun.file(join35(deps.changeDir, name));
|
|
261389
261821
|
if (!await f2.exists())
|
|
261390
261822
|
continue;
|
|
261391
261823
|
let raw;
|
|
@@ -261653,9 +262085,9 @@ var init_comment_sync2 = __esm(() => {
|
|
|
261653
262085
|
});
|
|
261654
262086
|
|
|
261655
262087
|
// apps/agent/src/features/pr-tracker/state.ts
|
|
261656
|
-
import { join as
|
|
262088
|
+
import { join as join36 } from "path";
|
|
261657
262089
|
async function readState2(projectRoot) {
|
|
261658
|
-
const path =
|
|
262090
|
+
const path = join36(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261659
262091
|
const file2 = Bun.file(path);
|
|
261660
262092
|
if (!await file2.exists())
|
|
261661
262093
|
return {};
|
|
@@ -261671,7 +262103,7 @@ async function readState2(projectRoot) {
|
|
|
261671
262103
|
}
|
|
261672
262104
|
}
|
|
261673
262105
|
async function writeState2(projectRoot, state) {
|
|
261674
|
-
const path =
|
|
262106
|
+
const path = join36(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261675
262107
|
await Bun.write(path, JSON.stringify(state, null, 2));
|
|
261676
262108
|
}
|
|
261677
262109
|
var PR_TRACKER_STATE_RELPATH = ".ralph/pr-tracker-state.json";
|
|
@@ -261750,7 +262182,7 @@ var init_pr_tracker = __esm(() => {
|
|
|
261750
262182
|
});
|
|
261751
262183
|
|
|
261752
262184
|
// apps/agent/src/agent/wire.ts
|
|
261753
|
-
import { join as
|
|
262185
|
+
import { join as join37 } from "path";
|
|
261754
262186
|
function buildAgentCoordinator(input) {
|
|
261755
262187
|
const {
|
|
261756
262188
|
args,
|
|
@@ -261769,7 +262201,7 @@ function buildAgentCoordinator(input) {
|
|
|
261769
262201
|
onWorkerCmd,
|
|
261770
262202
|
onAwaitingTicket
|
|
261771
262203
|
} = input;
|
|
261772
|
-
const logsDir =
|
|
262204
|
+
const logsDir = join37(projectRoot, ".ralph", "logs");
|
|
261773
262205
|
const bus = createBus();
|
|
261774
262206
|
subscribeAgentDiag(bus, onLog);
|
|
261775
262207
|
const diag = (area, message, color) => {
|
|
@@ -261795,7 +262227,11 @@ function buildAgentCoordinator(input) {
|
|
|
261795
262227
|
const awaitingChangeSet = new Set;
|
|
261796
262228
|
const coordRef = { current: null };
|
|
261797
262229
|
let pollContext = new PollContext;
|
|
261798
|
-
|
|
262230
|
+
let useWorktree = args.worktree || cfg.useWorktree;
|
|
262231
|
+
if (concurrency > 1 && !useWorktree) {
|
|
262232
|
+
diag("config", `! concurrency is ${concurrency} but useWorktree is off \u2014 forcing worktrees on so parallel tasks get isolated working copies`, "yellow");
|
|
262233
|
+
useWorktree = true;
|
|
262234
|
+
}
|
|
261799
262235
|
const scriptRunner = input.runners?.runScript ?? (async (cmd, cwd2) => {
|
|
261800
262236
|
const proc = Bun.spawn({
|
|
261801
262237
|
cmd: ["sh", "-c", cmd],
|
|
@@ -262000,7 +262436,7 @@ function buildAgentCoordinator(input) {
|
|
|
262000
262436
|
const changeDir = projectLayout(root).changeDir(changeName);
|
|
262001
262437
|
const parts = [];
|
|
262002
262438
|
for (const name of ["tasks.md", "proposal.md", "design.md"]) {
|
|
262003
|
-
const file2 = Bun.file(
|
|
262439
|
+
const file2 = Bun.file(join37(changeDir, name));
|
|
262004
262440
|
if (!await file2.exists())
|
|
262005
262441
|
continue;
|
|
262006
262442
|
parts.push(`${name}:${file2.lastModified}:${file2.size}`);
|
|
@@ -262045,7 +262481,7 @@ function buildAgentCoordinator(input) {
|
|
|
262045
262481
|
getGaveUpTotal: async () => {
|
|
262046
262482
|
let total = 0;
|
|
262047
262483
|
for (const [changeName, root] of cwdByChange) {
|
|
262048
|
-
const file2 = Bun.file(
|
|
262484
|
+
const file2 = Bun.file(join37(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
|
|
262049
262485
|
if (!await file2.exists())
|
|
262050
262486
|
continue;
|
|
262051
262487
|
try {
|
|
@@ -262333,7 +262769,7 @@ var init_output_utils = __esm(() => {
|
|
|
262333
262769
|
});
|
|
262334
262770
|
|
|
262335
262771
|
// apps/agent/src/agent/state/worker-state-poll.ts
|
|
262336
|
-
import { join as
|
|
262772
|
+
import { join as join38 } from "path";
|
|
262337
262773
|
function parseSubtasks(tasksMd) {
|
|
262338
262774
|
const out = [];
|
|
262339
262775
|
let skipSection = false;
|
|
@@ -262366,7 +262802,7 @@ function initialWorkerSnapshot() {
|
|
|
262366
262802
|
async function readWorkerSnapshot(input) {
|
|
262367
262803
|
const next = { ...input.prev };
|
|
262368
262804
|
try {
|
|
262369
|
-
const file2 = Bun.file(
|
|
262805
|
+
const file2 = Bun.file(join38(input.statesDir, input.changeName, ".ralph-state.json"));
|
|
262370
262806
|
if (await file2.exists()) {
|
|
262371
262807
|
const json2 = await file2.json();
|
|
262372
262808
|
next.iter = json2.iteration ?? next.iter;
|
|
@@ -262375,10 +262811,10 @@ async function readWorkerSnapshot(input) {
|
|
|
262375
262811
|
} catch {}
|
|
262376
262812
|
if (input.changeDir) {
|
|
262377
262813
|
try {
|
|
262378
|
-
const tasksFile = Bun.file(
|
|
262379
|
-
const proposalFile = Bun.file(
|
|
262380
|
-
const designFile = Bun.file(
|
|
262381
|
-
const reviewFindingsFile = Bun.file(
|
|
262814
|
+
const tasksFile = Bun.file(join38(input.changeDir, "tasks.md"));
|
|
262815
|
+
const proposalFile = Bun.file(join38(input.changeDir, "proposal.md"));
|
|
262816
|
+
const designFile = Bun.file(join38(input.changeDir, "design.md"));
|
|
262817
|
+
const reviewFindingsFile = Bun.file(join38(input.changeDir, "review-findings.md"));
|
|
262382
262818
|
const [tasksText, proposalText, designText, reviewFindingsText] = await Promise.all([
|
|
262383
262819
|
tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
|
|
262384
262820
|
proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
|
|
@@ -262441,7 +262877,7 @@ var init_worker_state_poll = __esm(() => {
|
|
|
262441
262877
|
});
|
|
262442
262878
|
|
|
262443
262879
|
// apps/agent/src/components/AgentMode.tsx
|
|
262444
|
-
import { join as
|
|
262880
|
+
import { join as join39 } from "path";
|
|
262445
262881
|
async function appendSteeringImpl(changeDir, message) {
|
|
262446
262882
|
await runWithContext(createDefaultContext(), async () => {
|
|
262447
262883
|
appendSteeringMessage(changeDir, message);
|
|
@@ -264014,7 +264450,7 @@ function AgentMode({
|
|
|
264014
264450
|
},
|
|
264015
264451
|
onSubmit: async (message) => {
|
|
264016
264452
|
try {
|
|
264017
|
-
await appendSteering2(
|
|
264453
|
+
await appendSteering2(join39(tasksDir, w2.changeName), message);
|
|
264018
264454
|
fileEmit({ type: "steering_submitted", changeName: w2.changeName, message });
|
|
264019
264455
|
} catch (err) {
|
|
264020
264456
|
const text = err.message;
|
|
@@ -264318,7 +264754,7 @@ __export(exports_list, {
|
|
|
264318
264754
|
buildBuckets: () => buildBuckets,
|
|
264319
264755
|
backlogRankByIssueId: () => backlogRankByIssueId
|
|
264320
264756
|
});
|
|
264321
|
-
import { join as
|
|
264757
|
+
import { join as join40 } from "path";
|
|
264322
264758
|
function countTaskItems(content) {
|
|
264323
264759
|
const checked = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
264324
264760
|
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
@@ -264334,13 +264770,13 @@ function buildLocalRows() {
|
|
|
264334
264770
|
const sources = [{ dir: statesDir, label: "main" }];
|
|
264335
264771
|
const worktreesRoot = worktreesDir2(projectRoot);
|
|
264336
264772
|
for (const wt of storage.list(worktreesRoot)) {
|
|
264337
|
-
sources.push({ dir:
|
|
264773
|
+
sources.push({ dir: join40(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
|
|
264338
264774
|
}
|
|
264339
264775
|
for (const { dir, label } of sources) {
|
|
264340
264776
|
for (const entry of storage.list(dir)) {
|
|
264341
264777
|
if (seen.has(entry))
|
|
264342
264778
|
continue;
|
|
264343
|
-
const raw = storage.read(
|
|
264779
|
+
const raw = storage.read(join40(dir, entry, ".ralph-state.json"));
|
|
264344
264780
|
if (raw === null)
|
|
264345
264781
|
continue;
|
|
264346
264782
|
let state;
|
|
@@ -264355,7 +264791,7 @@ function buildLocalRows() {
|
|
|
264355
264791
|
const firstLine = promptRaw.split(`
|
|
264356
264792
|
`).find((l3) => l3.trim() !== "") ?? "";
|
|
264357
264793
|
let progress = "\u2014";
|
|
264358
|
-
const tasksContent = storage.read(
|
|
264794
|
+
const tasksContent = storage.read(join40(dir, entry, "tasks.md"));
|
|
264359
264795
|
if (tasksContent !== null) {
|
|
264360
264796
|
const { checked, unchecked } = countTaskItems(tasksContent);
|
|
264361
264797
|
const total = checked + unchecked;
|
|
@@ -264855,7 +265291,7 @@ var exports_json_runner = {};
|
|
|
264855
265291
|
__export(exports_json_runner, {
|
|
264856
265292
|
runAgentJson: () => runAgentJson
|
|
264857
265293
|
});
|
|
264858
|
-
import { join as
|
|
265294
|
+
import { join as join41 } from "path";
|
|
264859
265295
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
264860
265296
|
import { homedir as homedir8 } from "os";
|
|
264861
265297
|
function makeEmit(fileSink) {
|
|
@@ -264877,7 +265313,7 @@ async function runAgentJson({
|
|
|
264877
265313
|
tasksDir,
|
|
264878
265314
|
runPreflight: runPreflight2 = runPreflight
|
|
264879
265315
|
}) {
|
|
264880
|
-
await mkdir12(
|
|
265316
|
+
await mkdir12(join41(homedir8(), ".ralph"), { recursive: true }).catch(() => {
|
|
264881
265317
|
return;
|
|
264882
265318
|
});
|
|
264883
265319
|
const fileSink = createJsonLogFileSink(args.jsonLogFile);
|
|
@@ -265094,7 +265530,7 @@ __export(exports_src3, {
|
|
|
265094
265530
|
main: () => main3
|
|
265095
265531
|
});
|
|
265096
265532
|
import { mkdir as mkdir13 } from "fs/promises";
|
|
265097
|
-
import { join as
|
|
265533
|
+
import { join as join42 } from "path";
|
|
265098
265534
|
async function main3(argv) {
|
|
265099
265535
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
265100
265536
|
printAgentHelp();
|
|
@@ -265160,7 +265596,7 @@ async function main3(argv) {
|
|
|
265160
265596
|
}
|
|
265161
265597
|
await mkdir13(statesDir, { recursive: true });
|
|
265162
265598
|
await mkdir13(tasksDir, { recursive: true });
|
|
265163
|
-
await mkdir13(
|
|
265599
|
+
await mkdir13(join42(projectRoot, ".ralph"), { recursive: true });
|
|
265164
265600
|
if (shouldFallbackToJsonOutput(args, process.stdin.isTTY)) {
|
|
265165
265601
|
process.stderr.write(`agent: stdin is not a TTY \u2014 falling back to --json-output mode.
|
|
265166
265602
|
`);
|
|
@@ -265218,6 +265654,7 @@ var init_src8 = __esm(async () => {
|
|
|
265218
265654
|
init_src();
|
|
265219
265655
|
init_src2();
|
|
265220
265656
|
init_version();
|
|
265657
|
+
init_common_args();
|
|
265221
265658
|
if (typeof globalThis.Bun === "undefined") {
|
|
265222
265659
|
process.stderr.write(`ralphy requires the Bun runtime (https://bun.sh/). It is not compatible with plain Node.js.
|
|
265223
265660
|
` + "Install Bun and re-run with `bun` or `bunx ralphy`.\n");
|
|
@@ -265302,7 +265739,8 @@ ${HELP}
|
|
|
265302
265739
|
if (shouldOfferSetup(subcommand, argv.slice(1))) {
|
|
265303
265740
|
try {
|
|
265304
265741
|
const { maybeRunSetupWizard: maybeRunSetupWizard2 } = await init_src4().then(() => exports_src);
|
|
265305
|
-
|
|
265742
|
+
const { projectRoot, workflowFile } = parseWorkflowPathArgs(argv.slice(1));
|
|
265743
|
+
await maybeRunSetupWizard2(projectRoot, workflowFile);
|
|
265306
265744
|
} catch (setupErr) {
|
|
265307
265745
|
captureError("setup_wizard_error", setupErr, { subcommand });
|
|
265308
265746
|
}
|