@neriros/ralphy 3.10.8 → 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 +1430 -972
- 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}
|
|
@@ -81906,21 +82225,24 @@ function applyAliases(cfg) {
|
|
|
81906
82225
|
}
|
|
81907
82226
|
}
|
|
81908
82227
|
}
|
|
81909
|
-
function workflowPath(projectRoot) {
|
|
81910
|
-
return join5(projectRoot, WORKFLOW_FILE);
|
|
82228
|
+
function workflowPath(projectRoot, workflowFile) {
|
|
82229
|
+
return workflowFile ?? join5(projectRoot, WORKFLOW_FILE);
|
|
81911
82230
|
}
|
|
81912
|
-
async function loadWorkflow(projectRoot) {
|
|
81913
|
-
const path = workflowPath(projectRoot);
|
|
82231
|
+
async function loadWorkflow(projectRoot, workflowFile, options = {}) {
|
|
82232
|
+
const path = workflowPath(projectRoot, workflowFile);
|
|
81914
82233
|
const file2 = Bun.file(path);
|
|
81915
82234
|
if (!await file2.exists()) {
|
|
81916
82235
|
const { config: config2 } = parseWorkflow(DEFAULT_WORKFLOW_MD);
|
|
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
|
-
async function ensureWorkflow(projectRoot) {
|
|
81923
|
-
const path = workflowPath(projectRoot);
|
|
82244
|
+
async function ensureWorkflow(projectRoot, workflowFile) {
|
|
82245
|
+
const path = workflowPath(projectRoot, workflowFile);
|
|
81924
82246
|
const file2 = Bun.file(path);
|
|
81925
82247
|
if (await file2.exists())
|
|
81926
82248
|
return path;
|
|
@@ -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
|
});
|
|
@@ -84362,6 +84967,12 @@ function getLayout() {
|
|
|
84362
84967
|
throw new Error("No layout in context. Set layout when calling runWithContext().");
|
|
84363
84968
|
return ctx.layout;
|
|
84364
84969
|
}
|
|
84970
|
+
function getArgs() {
|
|
84971
|
+
const ctx = getContext();
|
|
84972
|
+
if (!ctx.args)
|
|
84973
|
+
throw new Error("No args in context. Set args when calling runWithContext().");
|
|
84974
|
+
return ctx.args;
|
|
84975
|
+
}
|
|
84365
84976
|
function runWithContext(ctx, fn) {
|
|
84366
84977
|
return contextStore.run(ctx, fn);
|
|
84367
84978
|
}
|
|
@@ -84374,18 +84985,18 @@ var init_context = __esm(() => {
|
|
|
84374
84985
|
});
|
|
84375
84986
|
|
|
84376
84987
|
// packages/core/src/layout.ts
|
|
84377
|
-
import { join as
|
|
84988
|
+
import { join as join7 } from "path";
|
|
84378
84989
|
function projectLayout(root) {
|
|
84379
|
-
const statesDir =
|
|
84380
|
-
const tasksDir =
|
|
84990
|
+
const statesDir = join7(root, ".ralph", "tasks");
|
|
84991
|
+
const tasksDir = join7(root, "openspec", "changes");
|
|
84381
84992
|
return {
|
|
84382
84993
|
root,
|
|
84383
84994
|
statesDir,
|
|
84384
84995
|
tasksDir,
|
|
84385
|
-
agentStateFile:
|
|
84386
|
-
changeDir: (name) =>
|
|
84387
|
-
taskStateDir: (name) =>
|
|
84388
|
-
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)
|
|
84389
85000
|
};
|
|
84390
85001
|
}
|
|
84391
85002
|
var STATE_FILE = ".ralph-state.json", GAVEUP_COUNT_FILE = ".ralph-gaveup-count";
|
|
@@ -84394,13 +85005,13 @@ var init_layout = __esm(() => {
|
|
|
84394
85005
|
});
|
|
84395
85006
|
|
|
84396
85007
|
// packages/openspec/src/openspec-bin.ts
|
|
84397
|
-
import { dirname as dirname3, join as
|
|
85008
|
+
import { dirname as dirname3, join as join8 } from "path";
|
|
84398
85009
|
function findPackageRoot(startDir) {
|
|
84399
85010
|
let dir = startDir;
|
|
84400
85011
|
for (let i = 0;i < 8; i++) {
|
|
84401
|
-
if (Bun.file(
|
|
85012
|
+
if (Bun.file(join8(dir, "package.json")).size >= 0) {
|
|
84402
85013
|
try {
|
|
84403
|
-
if (Bun.file(
|
|
85014
|
+
if (Bun.file(join8(dir, "package.json")).size > 0)
|
|
84404
85015
|
return dir;
|
|
84405
85016
|
} catch {}
|
|
84406
85017
|
}
|
|
@@ -84436,11 +85047,11 @@ function ensureOpenspecInstalled(fromDir, runner) {
|
|
|
84436
85047
|
function resolveOpenspecBin(fromDir, runner = bunInstallRunner) {
|
|
84437
85048
|
try {
|
|
84438
85049
|
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
84439
|
-
return
|
|
85050
|
+
return join8(dirname3(pkgJsonPath), "bin", "openspec.js");
|
|
84440
85051
|
} catch {
|
|
84441
85052
|
ensureOpenspecInstalled(fromDir, runner);
|
|
84442
85053
|
const pkgJsonPath = runner.resolveSync("@fission-ai/openspec/package.json", fromDir);
|
|
84443
|
-
return
|
|
85054
|
+
return join8(dirname3(pkgJsonPath), "bin", "openspec.js");
|
|
84444
85055
|
}
|
|
84445
85056
|
}
|
|
84446
85057
|
var bunInstallRunner;
|
|
@@ -84462,7 +85073,7 @@ var init_openspec_bin = __esm(() => {
|
|
|
84462
85073
|
});
|
|
84463
85074
|
|
|
84464
85075
|
// packages/openspec/src/openspec-change-store.ts
|
|
84465
|
-
import { dirname as dirname4, join as
|
|
85076
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
84466
85077
|
import { readdir, mkdir as mkdir2 } from "fs/promises";
|
|
84467
85078
|
function runOpenspec(args, options = {}) {
|
|
84468
85079
|
const stdio = options.inherit ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"];
|
|
@@ -84532,7 +85143,7 @@ class OpenSpecChangeStore {
|
|
|
84532
85143
|
}
|
|
84533
85144
|
}
|
|
84534
85145
|
getChangeDirectory(name) {
|
|
84535
|
-
return
|
|
85146
|
+
return join9("openspec", "changes", name);
|
|
84536
85147
|
}
|
|
84537
85148
|
async listChanges() {
|
|
84538
85149
|
const result2 = runOpenspec(["list", "--json"]);
|
|
@@ -84546,7 +85157,7 @@ class OpenSpecChangeStore {
|
|
|
84546
85157
|
}
|
|
84547
85158
|
} catch {}
|
|
84548
85159
|
}
|
|
84549
|
-
const changesDir =
|
|
85160
|
+
const changesDir = join9("openspec", "changes");
|
|
84550
85161
|
try {
|
|
84551
85162
|
const entries = await readdir(changesDir, { withFileTypes: true });
|
|
84552
85163
|
return entries.filter((entry) => entry.isDirectory() && entry.name !== "archive").map((entry) => entry.name);
|
|
@@ -84555,18 +85166,18 @@ class OpenSpecChangeStore {
|
|
|
84555
85166
|
}
|
|
84556
85167
|
}
|
|
84557
85168
|
async readTaskList(name) {
|
|
84558
|
-
const file2 = Bun.file(
|
|
85169
|
+
const file2 = Bun.file(join9("openspec", "changes", name, "tasks.md"));
|
|
84559
85170
|
if (!await file2.exists())
|
|
84560
85171
|
return "";
|
|
84561
85172
|
return await file2.text();
|
|
84562
85173
|
}
|
|
84563
85174
|
async writeTaskList(name, content) {
|
|
84564
|
-
const path =
|
|
85175
|
+
const path = join9("openspec", "changes", name, "tasks.md");
|
|
84565
85176
|
await mkdir2(dirname4(path), { recursive: true });
|
|
84566
85177
|
await Bun.write(path, content);
|
|
84567
85178
|
}
|
|
84568
85179
|
async appendSteering(name, message) {
|
|
84569
|
-
const path =
|
|
85180
|
+
const path = join9("openspec", "changes", name, "steering.md");
|
|
84570
85181
|
const file2 = Bun.file(path);
|
|
84571
85182
|
const existing = await file2.exists() ? await file2.text() : null;
|
|
84572
85183
|
const updated = existing ? `${message}
|
|
@@ -84578,7 +85189,7 @@ ${existing.trimStart()}` : `${message}
|
|
|
84578
85189
|
const firstLine = message.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? message.trim();
|
|
84579
85190
|
if (firstLine.length === 0)
|
|
84580
85191
|
return;
|
|
84581
|
-
const tasksPath =
|
|
85192
|
+
const tasksPath = join9("openspec", "changes", name, "tasks.md");
|
|
84582
85193
|
const tasksFile = Bun.file(tasksPath);
|
|
84583
85194
|
const existingTasks = await tasksFile.exists() ? await tasksFile.text() : "";
|
|
84584
85195
|
const taskLine = `- [ ] Address steering: ${firstLine}`;
|
|
@@ -84711,171 +85322,6 @@ var init_output2 = __esm(() => {
|
|
|
84711
85322
|
};
|
|
84712
85323
|
});
|
|
84713
85324
|
|
|
84714
|
-
// packages/cli-args/src/common-args.ts
|
|
84715
|
-
function initialCommonArgs() {
|
|
84716
|
-
return {
|
|
84717
|
-
engine: "claude",
|
|
84718
|
-
model: "opus",
|
|
84719
|
-
engineSet: false,
|
|
84720
|
-
maxIterations: 0,
|
|
84721
|
-
maxCostUsd: 0,
|
|
84722
|
-
maxRuntimeMinutes: 0,
|
|
84723
|
-
maxConsecutiveFailures: 5,
|
|
84724
|
-
delay: 0,
|
|
84725
|
-
log: false,
|
|
84726
|
-
verbose: false,
|
|
84727
|
-
projectRoot: undefined,
|
|
84728
|
-
name: "",
|
|
84729
|
-
prompt: "",
|
|
84730
|
-
fromAgent: false
|
|
84731
|
-
};
|
|
84732
|
-
}
|
|
84733
|
-
function applyValueOption(option, args, raw) {
|
|
84734
|
-
const setter = VALUE_SETTERS[option.argKey];
|
|
84735
|
-
if (!setter)
|
|
84736
|
-
throw new Error("no value setter registered for CLI option");
|
|
84737
|
-
setter(args, raw);
|
|
84738
|
-
}
|
|
84739
|
-
function applyBooleanOption(option, args) {
|
|
84740
|
-
const setter = BOOLEAN_SETTERS[option.argKey];
|
|
84741
|
-
if (!setter)
|
|
84742
|
-
throw new Error("no boolean setter registered for CLI option");
|
|
84743
|
-
setter(args);
|
|
84744
|
-
}
|
|
84745
|
-
function emptyParseState() {
|
|
84746
|
-
return {
|
|
84747
|
-
pendingOption: null,
|
|
84748
|
-
expectClaudeModel: false,
|
|
84749
|
-
expectProjectRoot: false,
|
|
84750
|
-
expectName: false,
|
|
84751
|
-
expectPrompt: false,
|
|
84752
|
-
expectPromptFile: false,
|
|
84753
|
-
promptFilePath: null
|
|
84754
|
-
};
|
|
84755
|
-
}
|
|
84756
|
-
function parseCommonArg(arg, args, state) {
|
|
84757
|
-
if (state.pendingOption) {
|
|
84758
|
-
applyValueOption(state.pendingOption, args, arg);
|
|
84759
|
-
state.pendingOption = null;
|
|
84760
|
-
return true;
|
|
84761
|
-
}
|
|
84762
|
-
if (state.expectClaudeModel) {
|
|
84763
|
-
state.expectClaudeModel = false;
|
|
84764
|
-
if (VALID_MODELS.has(arg)) {
|
|
84765
|
-
args.model = arg;
|
|
84766
|
-
return true;
|
|
84767
|
-
}
|
|
84768
|
-
}
|
|
84769
|
-
if (state.expectProjectRoot) {
|
|
84770
|
-
args.projectRoot = arg;
|
|
84771
|
-
state.expectProjectRoot = false;
|
|
84772
|
-
return true;
|
|
84773
|
-
}
|
|
84774
|
-
if (state.expectName) {
|
|
84775
|
-
args.name = arg;
|
|
84776
|
-
state.expectName = false;
|
|
84777
|
-
return true;
|
|
84778
|
-
}
|
|
84779
|
-
if (state.expectPrompt) {
|
|
84780
|
-
args.prompt = arg;
|
|
84781
|
-
state.promptFilePath = null;
|
|
84782
|
-
state.expectPrompt = false;
|
|
84783
|
-
return true;
|
|
84784
|
-
}
|
|
84785
|
-
if (state.expectPromptFile) {
|
|
84786
|
-
state.promptFilePath = arg;
|
|
84787
|
-
state.expectPromptFile = false;
|
|
84788
|
-
return true;
|
|
84789
|
-
}
|
|
84790
|
-
const option = OPTION_BY_FLAG.get(arg);
|
|
84791
|
-
if (option) {
|
|
84792
|
-
if (option.kind === "boolean")
|
|
84793
|
-
applyBooleanOption(option, args);
|
|
84794
|
-
else
|
|
84795
|
-
state.pendingOption = option;
|
|
84796
|
-
return true;
|
|
84797
|
-
}
|
|
84798
|
-
switch (arg) {
|
|
84799
|
-
case "--claude":
|
|
84800
|
-
if (args.engineSet && args.engine !== "claude") {
|
|
84801
|
-
throw new Error("Choose only one engine flag: --claude or --codex");
|
|
84802
|
-
}
|
|
84803
|
-
args.engine = "claude";
|
|
84804
|
-
args.engineSet = true;
|
|
84805
|
-
state.expectClaudeModel = true;
|
|
84806
|
-
return true;
|
|
84807
|
-
case "--codex":
|
|
84808
|
-
if (args.engineSet && args.engine !== "codex") {
|
|
84809
|
-
throw new Error("Choose only one engine flag: --claude or --codex");
|
|
84810
|
-
}
|
|
84811
|
-
args.engine = "codex";
|
|
84812
|
-
args.engineSet = true;
|
|
84813
|
-
return true;
|
|
84814
|
-
case "--unlimited":
|
|
84815
|
-
args.maxIterations = 0;
|
|
84816
|
-
return true;
|
|
84817
|
-
case "--project-root":
|
|
84818
|
-
state.expectProjectRoot = true;
|
|
84819
|
-
return true;
|
|
84820
|
-
case "--name":
|
|
84821
|
-
state.expectName = true;
|
|
84822
|
-
return true;
|
|
84823
|
-
case "--prompt":
|
|
84824
|
-
state.expectPrompt = true;
|
|
84825
|
-
return true;
|
|
84826
|
-
case "--prompt-file":
|
|
84827
|
-
state.expectPromptFile = true;
|
|
84828
|
-
return true;
|
|
84829
|
-
case "--from-agent":
|
|
84830
|
-
args.fromAgent = true;
|
|
84831
|
-
return true;
|
|
84832
|
-
default:
|
|
84833
|
-
return false;
|
|
84834
|
-
}
|
|
84835
|
-
}
|
|
84836
|
-
async function resolvePromptFile(args, state) {
|
|
84837
|
-
if (state.promptFilePath !== null) {
|
|
84838
|
-
args.prompt = await Bun.file(state.promptFilePath).text();
|
|
84839
|
-
}
|
|
84840
|
-
}
|
|
84841
|
-
var VALID_MODELS, OPTION_BY_FLAG, VALUE_FLAGS, VALUE_SETTERS, BOOLEAN_SETTERS;
|
|
84842
|
-
var init_common_args = __esm(() => {
|
|
84843
|
-
init_fields();
|
|
84844
|
-
VALID_MODELS = new Set(modelOptionValues());
|
|
84845
|
-
OPTION_BY_FLAG = new Map(COMMON_CLI_OPTIONS.map((option) => [option.flag, option]));
|
|
84846
|
-
VALUE_FLAGS = new Set(COMMON_CLI_OPTIONS.filter((option) => option.kind !== "boolean").map((option) => option.flag));
|
|
84847
|
-
VALUE_SETTERS = {
|
|
84848
|
-
model: (args, raw) => {
|
|
84849
|
-
if (!VALID_MODELS.has(raw))
|
|
84850
|
-
throw new Error("Invalid model");
|
|
84851
|
-
args.model = raw;
|
|
84852
|
-
},
|
|
84853
|
-
delay: (args, raw) => {
|
|
84854
|
-
args.delay = parseInt(raw, 10);
|
|
84855
|
-
},
|
|
84856
|
-
maxCostUsd: (args, raw) => {
|
|
84857
|
-
args.maxCostUsd = parseFloat(raw);
|
|
84858
|
-
},
|
|
84859
|
-
maxRuntimeMinutes: (args, raw) => {
|
|
84860
|
-
args.maxRuntimeMinutes = parseFloat(raw);
|
|
84861
|
-
},
|
|
84862
|
-
maxConsecutiveFailures: (args, raw) => {
|
|
84863
|
-
args.maxConsecutiveFailures = parseInt(raw, 10);
|
|
84864
|
-
},
|
|
84865
|
-
maxIterations: (args, raw) => {
|
|
84866
|
-
args.maxIterations = parseInt(raw, 10);
|
|
84867
|
-
}
|
|
84868
|
-
};
|
|
84869
|
-
BOOLEAN_SETTERS = {
|
|
84870
|
-
log: (args) => {
|
|
84871
|
-
args.log = true;
|
|
84872
|
-
},
|
|
84873
|
-
verbose: (args) => {
|
|
84874
|
-
args.verbose = true;
|
|
84875
|
-
}
|
|
84876
|
-
};
|
|
84877
|
-
});
|
|
84878
|
-
|
|
84879
85325
|
// apps/loop/src/cli.ts
|
|
84880
85326
|
function printLoopHelp() {
|
|
84881
85327
|
log(HELP_TEXT);
|
|
@@ -84944,6 +85390,7 @@ async function parseLoopArgs(argv) {
|
|
|
84944
85390
|
}
|
|
84945
85391
|
}
|
|
84946
85392
|
await resolvePromptFile(result2, state);
|
|
85393
|
+
resolveWorkflowFile(result2, state);
|
|
84947
85394
|
return result2;
|
|
84948
85395
|
}
|
|
84949
85396
|
var VALID_MODES, HELP_TEXT;
|
|
@@ -84966,6 +85413,7 @@ var init_cli = __esm(() => {
|
|
|
84966
85413
|
"",
|
|
84967
85414
|
"Options:",
|
|
84968
85415
|
" --name <name> Change name (required for most commands)",
|
|
85416
|
+
" --workflow <path> Path to an alternate WORKFLOW.md (default: <project>/WORKFLOW.md)",
|
|
84969
85417
|
" --prompt <text> Task description",
|
|
84970
85418
|
" --prompt-file <path> Read prompt from file",
|
|
84971
85419
|
" --model <model> Set model (haiku|sonnet|opus)",
|
|
@@ -85022,6 +85470,7 @@ async function parseTaskArgs(argv) {
|
|
|
85022
85470
|
}
|
|
85023
85471
|
}
|
|
85024
85472
|
await resolvePromptFile(result2, state);
|
|
85473
|
+
resolveWorkflowFile(result2, state);
|
|
85025
85474
|
if (!phaseSet) {
|
|
85026
85475
|
throw new Error(`Missing phase. Valid phases: research, plan, execute, review. Run 'ralphy task --help' for usage information.`);
|
|
85027
85476
|
}
|
|
@@ -85049,6 +85498,7 @@ var init_task_cli = __esm(() => {
|
|
|
85049
85498
|
"",
|
|
85050
85499
|
"Options:",
|
|
85051
85500
|
" --name <name> Change name (required)",
|
|
85501
|
+
" --workflow <path> Path to an alternate WORKFLOW.md (default: <project>/WORKFLOW.md)",
|
|
85052
85502
|
" --prompt <text> Task description",
|
|
85053
85503
|
" --prompt-file <path> Read prompt from file",
|
|
85054
85504
|
" --model <model> Set model (haiku|sonnet|opus)",
|
|
@@ -85088,10 +85538,10 @@ var init_schema2 = __esm(() => {
|
|
|
85088
85538
|
});
|
|
85089
85539
|
|
|
85090
85540
|
// packages/core/src/state/sidecar.ts
|
|
85091
|
-
import { dirname as dirname5, join as
|
|
85541
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
85092
85542
|
import { mkdir as mkdir3, rename, unlink } from "fs/promises";
|
|
85093
85543
|
function slotSidecarPath(changeDir, slot) {
|
|
85094
|
-
return
|
|
85544
|
+
return join10(changeDir, `${CORE_STATE_FILE.replace(/\.json$/, "")}.${slot}.json`);
|
|
85095
85545
|
}
|
|
85096
85546
|
function parseObject(text) {
|
|
85097
85547
|
if (text === null)
|
|
@@ -89273,7 +89723,7 @@ function formatTaskName(name) {
|
|
|
89273
89723
|
}
|
|
89274
89724
|
|
|
89275
89725
|
// packages/core/src/state.ts
|
|
89276
|
-
import { join as
|
|
89726
|
+
import { join as join11 } from "path";
|
|
89277
89727
|
function stripOwnedSlots(state) {
|
|
89278
89728
|
const out = { ...state };
|
|
89279
89729
|
for (const slot of ALL_OWNED_SLOTS)
|
|
@@ -89281,7 +89731,7 @@ function stripOwnedSlots(state) {
|
|
|
89281
89731
|
return out;
|
|
89282
89732
|
}
|
|
89283
89733
|
function readState(changeDir) {
|
|
89284
|
-
const filePath =
|
|
89734
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89285
89735
|
const raw = getStorage().read(filePath);
|
|
89286
89736
|
if (raw === null)
|
|
89287
89737
|
throw new Error(".ralph-state.json not found");
|
|
@@ -89290,7 +89740,7 @@ function readState(changeDir) {
|
|
|
89290
89740
|
return StateSchema.parse(base2);
|
|
89291
89741
|
}
|
|
89292
89742
|
function tryReadStateRaw(changeDir) {
|
|
89293
|
-
const filePath =
|
|
89743
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89294
89744
|
const text = getStorage().read(filePath);
|
|
89295
89745
|
if (text === null)
|
|
89296
89746
|
return { state: null, raw: null };
|
|
@@ -89306,7 +89756,7 @@ function tryReadStateRaw(changeDir) {
|
|
|
89306
89756
|
return { state: result2.success ? result2.data : null, raw };
|
|
89307
89757
|
}
|
|
89308
89758
|
function writeState(changeDir, state) {
|
|
89309
|
-
const filePath =
|
|
89759
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89310
89760
|
const core2 = stripOwnedSlots(state);
|
|
89311
89761
|
getStorage().write(filePath, JSON.stringify(core2, null, 2) + `
|
|
89312
89762
|
`);
|
|
@@ -89347,7 +89797,7 @@ function buildInitialState(options) {
|
|
|
89347
89797
|
});
|
|
89348
89798
|
}
|
|
89349
89799
|
function ensureState(changeDir) {
|
|
89350
|
-
const filePath =
|
|
89800
|
+
const filePath = join11(changeDir, STATE_FILE2);
|
|
89351
89801
|
const storage = getStorage();
|
|
89352
89802
|
if (storage.read(filePath) !== null) {
|
|
89353
89803
|
return readState(changeDir);
|
|
@@ -89366,7 +89816,7 @@ var init_state = __esm(() => {
|
|
|
89366
89816
|
});
|
|
89367
89817
|
|
|
89368
89818
|
// packages/core/src/state/store.ts
|
|
89369
|
-
import { join as
|
|
89819
|
+
import { join as join12 } from "path";
|
|
89370
89820
|
async function readJson(filePath) {
|
|
89371
89821
|
const file2 = Bun.file(filePath);
|
|
89372
89822
|
if (!await file2.exists())
|
|
@@ -89390,7 +89840,7 @@ async function writeField(changeDir, featureName, path, value) {
|
|
|
89390
89840
|
if (!allowed.includes(topSlot)) {
|
|
89391
89841
|
throw new OwnershipError(featureName, path, `feature '${featureName}' may not write '${path}' (owns ${allowed.join(", ")})`);
|
|
89392
89842
|
}
|
|
89393
|
-
const inline = (await readJson(
|
|
89843
|
+
const inline = (await readJson(join12(changeDir, STATE_FILE3)))[topSlot];
|
|
89394
89844
|
const seed = inline && typeof inline === "object" && !Array.isArray(inline) ? inline : undefined;
|
|
89395
89845
|
await writeSlotField(changeDir, path, value, seed);
|
|
89396
89846
|
}
|
|
@@ -89413,14 +89863,14 @@ var init_store = __esm(() => {
|
|
|
89413
89863
|
});
|
|
89414
89864
|
|
|
89415
89865
|
// apps/loop/src/components/TaskStatus.tsx
|
|
89416
|
-
import { join as
|
|
89866
|
+
import { join as join13 } from "path";
|
|
89417
89867
|
function TaskStatus({ state, stateDir }) {
|
|
89418
89868
|
const storage = getStorage();
|
|
89419
89869
|
const cost = Math.round(state.usage.total_cost_usd * 100) / 100;
|
|
89420
89870
|
const time3 = Math.round(state.usage.total_duration_ms / 1000 * 10) / 10 + "s";
|
|
89421
89871
|
const artifacts = OPENSPEC_ARTIFACTS.map((name) => ({
|
|
89422
89872
|
name,
|
|
89423
|
-
exists: storage.read(
|
|
89873
|
+
exists: storage.read(join13(stateDir, name)) !== null
|
|
89424
89874
|
}));
|
|
89425
89875
|
const recent = state.history.slice(-10);
|
|
89426
89876
|
return /* @__PURE__ */ jsx_dev_runtime2.jsxDEV(Box_default, {
|
|
@@ -97382,10 +97832,10 @@ var require_xstate_development_cjs = __commonJS((exports) => {
|
|
|
97382
97832
|
}
|
|
97383
97833
|
}
|
|
97384
97834
|
function toPromise(actor) {
|
|
97385
|
-
return new Promise((
|
|
97835
|
+
return new Promise((resolve4, reject2) => {
|
|
97386
97836
|
actor.subscribe({
|
|
97387
97837
|
complete: () => {
|
|
97388
|
-
|
|
97838
|
+
resolve4(actor.getSnapshot().output);
|
|
97389
97839
|
},
|
|
97390
97840
|
error: reject2
|
|
97391
97841
|
});
|
|
@@ -98138,7 +98588,7 @@ var init_rate_limit_detection = __esm(() => {
|
|
|
98138
98588
|
|
|
98139
98589
|
// packages/engine/src/agents/claude.ts
|
|
98140
98590
|
import { mkdtemp, unlink as unlink2 } from "fs/promises";
|
|
98141
|
-
import { join as
|
|
98591
|
+
import { join as join14 } from "path";
|
|
98142
98592
|
import { tmpdir } from "os";
|
|
98143
98593
|
function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, reviewerModel) {
|
|
98144
98594
|
const effectiveModel = reviewerModel ?? model;
|
|
@@ -98159,7 +98609,7 @@ function buildClaudeArgs(model, resumeSessionId, reviewerContextStrategy, review
|
|
|
98159
98609
|
}
|
|
98160
98610
|
async function runInteractive(req) {
|
|
98161
98611
|
const { model, prompt, taskDir } = req;
|
|
98162
|
-
const promptFile = taskDir ?
|
|
98612
|
+
const promptFile = taskDir ? join14(taskDir, "_interactive_prompt.md") : join14(await mkdtemp(join14(tmpdir(), "ralph-")), "prompt.md");
|
|
98163
98613
|
await Bun.write(promptFile, prompt);
|
|
98164
98614
|
try {
|
|
98165
98615
|
const cmd = [
|
|
@@ -98186,7 +98636,7 @@ async function runInteractive(req) {
|
|
|
98186
98636
|
env: scrubClaudeEnv(process.env)
|
|
98187
98637
|
});
|
|
98188
98638
|
const exitCode = await proc.exited;
|
|
98189
|
-
const doneFile = taskDir ?
|
|
98639
|
+
const doneFile = taskDir ? join14(taskDir, "_interactive_done") : null;
|
|
98190
98640
|
if (doneFile && await Bun.file(doneFile).exists()) {
|
|
98191
98641
|
return { exitCode: 0, usage: null, sessionId: null, rateLimited: false };
|
|
98192
98642
|
}
|
|
@@ -98816,10 +99266,10 @@ async function runEngine(opts) {
|
|
|
98816
99266
|
await mkdir4(dirname6(opts.logFile), { recursive: true });
|
|
98817
99267
|
rawWriter = createWriteStream(opts.logFile, { flags: "a" });
|
|
98818
99268
|
}
|
|
98819
|
-
const closeRaw = () => new Promise((
|
|
99269
|
+
const closeRaw = () => new Promise((resolve4) => {
|
|
98820
99270
|
if (!rawWriter)
|
|
98821
|
-
return
|
|
98822
|
-
rawWriter.end(
|
|
99271
|
+
return resolve4();
|
|
99272
|
+
rawWriter.end(resolve4);
|
|
98823
99273
|
});
|
|
98824
99274
|
const userOnFeedEvent = opts.onFeedEvent;
|
|
98825
99275
|
const onFeedEvent = (event) => {
|
|
@@ -98963,8 +99413,8 @@ var init_flow_machine = __esm(() => {
|
|
|
98963
99413
|
} catch {}
|
|
98964
99414
|
const exited = await Promise.race([
|
|
98965
99415
|
worker.exited.then(() => "exited"),
|
|
98966
|
-
new Promise((
|
|
98967
|
-
const t = setTimeout(() =>
|
|
99416
|
+
new Promise((resolve4) => {
|
|
99417
|
+
const t = setTimeout(() => resolve4("timeout"), graceMs);
|
|
98968
99418
|
t.unref();
|
|
98969
99419
|
})
|
|
98970
99420
|
]);
|
|
@@ -99689,11 +100139,11 @@ var init_meta_prompt = __esm(() => {
|
|
|
99689
100139
|
});
|
|
99690
100140
|
|
|
99691
100141
|
// packages/core/src/loop.ts
|
|
99692
|
-
import { join as
|
|
100142
|
+
import { join as join15 } from "path";
|
|
99693
100143
|
function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
99694
100144
|
const storage = getStorage();
|
|
99695
100145
|
let prompt = "";
|
|
99696
|
-
const steeringContent = storage.read(
|
|
100146
|
+
const steeringContent = storage.read(join15(taskDir, "steering.md"));
|
|
99697
100147
|
if (steeringContent !== null) {
|
|
99698
100148
|
const steeringLines = steeringContent.split(`
|
|
99699
100149
|
`).filter((line) => !line.startsWith("#")).filter((line) => line.trim()).slice(0, STEERING_MAX_LINES);
|
|
@@ -99712,8 +100162,8 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99712
100162
|
`;
|
|
99713
100163
|
}
|
|
99714
100164
|
}
|
|
99715
|
-
const agentTasksPath =
|
|
99716
|
-
const missionTasksPath =
|
|
100165
|
+
const agentTasksPath = join15(taskDir, AGENT_TASKS_FILENAME);
|
|
100166
|
+
const missionTasksPath = join15(taskDir, MISSION_TASKS_FILENAME);
|
|
99717
100167
|
const agentTasksContent = storage.read(agentTasksPath);
|
|
99718
100168
|
const missionTasksContent = storage.read(missionTasksPath);
|
|
99719
100169
|
let activePath = null;
|
|
@@ -99789,7 +100239,7 @@ function buildTaskPrompt(state, taskDir, reviewPhase) {
|
|
|
99789
100239
|
}
|
|
99790
100240
|
}
|
|
99791
100241
|
if (reviewPhase?.enabled) {
|
|
99792
|
-
const reviewFindingsPath =
|
|
100242
|
+
const reviewFindingsPath = join15(taskDir, "review-findings.md");
|
|
99793
100243
|
const reviewFindingsContent = storage.read(reviewFindingsPath);
|
|
99794
100244
|
const hasUncheckedMission = missionTasksContent !== null && /^- \[ \]/m.test(missionTasksContent);
|
|
99795
100245
|
const hasUncheckedAgent = agentTasksContent !== null && /^- \[ \]/m.test(agentTasksContent);
|
|
@@ -99863,7 +100313,7 @@ When all tasks are complete and all files are committed, push your branch and op
|
|
|
99863
100313
|
}
|
|
99864
100314
|
function buildSteeringBlock(taskDir) {
|
|
99865
100315
|
const storage = getStorage();
|
|
99866
|
-
const steeringContent = storage.read(
|
|
100316
|
+
const steeringContent = storage.read(join15(taskDir, "steering.md"));
|
|
99867
100317
|
if (steeringContent === null)
|
|
99868
100318
|
return "";
|
|
99869
100319
|
const steeringLines = steeringContent.split(`
|
|
@@ -99961,7 +100411,7 @@ function buildPlanPrompt(state, taskDir) {
|
|
|
99961
100411
|
return prompt;
|
|
99962
100412
|
}
|
|
99963
100413
|
function buildReviewPrompt(state, taskDir) {
|
|
99964
|
-
const reviewFindingsPath =
|
|
100414
|
+
const reviewFindingsPath = join15(taskDir, "review-findings.md");
|
|
99965
100415
|
let prompt = buildSteeringBlock(taskDir);
|
|
99966
100416
|
prompt += `---
|
|
99967
100417
|
|
|
@@ -100026,7 +100476,7 @@ function buildPhasePrompt(phase, state, taskDir, reviewPhase, metaPromptOptions)
|
|
|
100026
100476
|
}
|
|
100027
100477
|
function checkStopSignal(taskDir, stateDir) {
|
|
100028
100478
|
const storage = getStorage();
|
|
100029
|
-
const stopFile =
|
|
100479
|
+
const stopFile = join15(taskDir, "STOP");
|
|
100030
100480
|
const reason = storage.read(stopFile);
|
|
100031
100481
|
if (reason === null)
|
|
100032
100482
|
return null;
|
|
@@ -100086,7 +100536,7 @@ function updateStateIteration(stateDir, result2, startedAt, engine, model, usage
|
|
|
100086
100536
|
}
|
|
100087
100537
|
function appendSteeringMessage(taskDir, message) {
|
|
100088
100538
|
const storage = getStorage();
|
|
100089
|
-
const steeringPath =
|
|
100539
|
+
const steeringPath = join15(taskDir, "steering.md");
|
|
100090
100540
|
const existing = storage.read(steeringPath);
|
|
100091
100541
|
const updated = existing ? `${message}
|
|
100092
100542
|
|
|
@@ -100136,9 +100586,9 @@ var init_loop2 = __esm(() => {
|
|
|
100136
100586
|
});
|
|
100137
100587
|
|
|
100138
100588
|
// apps/loop/src/hooks/useLoop.ts
|
|
100139
|
-
import { join as
|
|
100589
|
+
import { join as join16 } from "path";
|
|
100140
100590
|
function sleep(seconds) {
|
|
100141
|
-
return new Promise((
|
|
100591
|
+
return new Promise((resolve4) => setTimeout(resolve4, seconds * 1000));
|
|
100142
100592
|
}
|
|
100143
100593
|
function useLoop(opts) {
|
|
100144
100594
|
const outerLayoutRef = import_react57.useRef(null);
|
|
@@ -100252,8 +100702,8 @@ function useLoop(opts) {
|
|
|
100252
100702
|
setState(currentState);
|
|
100253
100703
|
if (!actor.getSnapshot().matches("running"))
|
|
100254
100704
|
break;
|
|
100255
|
-
const tasksContent = storage.read(
|
|
100256
|
-
const agentTasksContent = storage.read(
|
|
100705
|
+
const tasksContent = storage.read(join16(tasksDir, MISSION_TASKS_FILENAME));
|
|
100706
|
+
const agentTasksContent = storage.read(join16(tasksDir, AGENT_TASKS_FILENAME));
|
|
100257
100707
|
if (tasksContent === null && currentState.iteration > 0 && typeof opts.changeStore.listChanges === "function") {
|
|
100258
100708
|
let stillActive = true;
|
|
100259
100709
|
try {
|
|
@@ -100290,7 +100740,7 @@ function useLoop(opts) {
|
|
|
100290
100740
|
const agentDone = agentTasksContent === null || allCompleted(agentTasksContent);
|
|
100291
100741
|
if (missionDone && agentDone && tasksContent !== null) {
|
|
100292
100742
|
if (opts.reviewPhase?.enabled) {
|
|
100293
|
-
const reviewFindingsPath =
|
|
100743
|
+
const reviewFindingsPath = join16(tasksDir, "review-findings.md");
|
|
100294
100744
|
const reviewFindingsFile = Bun.file(reviewFindingsPath);
|
|
100295
100745
|
const findingsExists = await reviewFindingsFile.exists();
|
|
100296
100746
|
const findingsContent = findingsExists ? await reviewFindingsFile.text() : null;
|
|
@@ -100319,7 +100769,7 @@ function useLoop(opts) {
|
|
|
100319
100769
|
model: opts.reviewPhase.reviewerModel ?? opts.model,
|
|
100320
100770
|
prompt: reviewPrompt,
|
|
100321
100771
|
logFlag: opts.log,
|
|
100322
|
-
logFile:
|
|
100772
|
+
logFile: join16(stateDir, `log-review-${roundNum}.json`),
|
|
100323
100773
|
taskDir: tasksDir,
|
|
100324
100774
|
reviewerContextStrategy: opts.reviewPhase.reviewerContextStrategy ?? "fresh",
|
|
100325
100775
|
onFeedEvent: addFeedEvent
|
|
@@ -100393,8 +100843,8 @@ function useLoop(opts) {
|
|
|
100393
100843
|
const time3 = new Date().toLocaleTimeString("en-US", { hour12: false });
|
|
100394
100844
|
addIterationHeader(localIter, time3);
|
|
100395
100845
|
addInfo(`Iteration ${localIter} (total: ${currentState.iteration})`);
|
|
100396
|
-
const proposalContent = storage.read(
|
|
100397
|
-
const designContent = storage.read(
|
|
100846
|
+
const proposalContent = storage.read(join16(tasksDir, "proposal.md"));
|
|
100847
|
+
const designContent = storage.read(join16(tasksDir, "design.md"));
|
|
100398
100848
|
const routedPhase = routeTaskPhase(opts.phase, {
|
|
100399
100849
|
proposal: proposalContent,
|
|
100400
100850
|
design: designContent,
|
|
@@ -100414,7 +100864,7 @@ function useLoop(opts) {
|
|
|
100414
100864
|
model: opts.model,
|
|
100415
100865
|
prompt,
|
|
100416
100866
|
logFlag: opts.log,
|
|
100417
|
-
logFile:
|
|
100867
|
+
logFile: join16(stateDir, "log.json"),
|
|
100418
100868
|
taskDir: tasksDir,
|
|
100419
100869
|
interactive: false,
|
|
100420
100870
|
onFeedEvent: addFeedEvent,
|
|
@@ -100437,7 +100887,7 @@ function useLoop(opts) {
|
|
|
100437
100887
|
model: opts.model,
|
|
100438
100888
|
prompt: buildSteeringPrompt(steerMessage),
|
|
100439
100889
|
logFlag: opts.log,
|
|
100440
|
-
logFile:
|
|
100890
|
+
logFile: join16(stateDir, "log.json"),
|
|
100441
100891
|
taskDir: tasksDir,
|
|
100442
100892
|
onFeedEvent: addResumeFeedEvent,
|
|
100443
100893
|
signal: resumeController.signal,
|
|
@@ -100744,7 +101194,7 @@ var init_TaskLoop = __esm(async () => {
|
|
|
100744
101194
|
});
|
|
100745
101195
|
|
|
100746
101196
|
// apps/loop/src/components/App.tsx
|
|
100747
|
-
import { join as
|
|
101197
|
+
import { join as join17 } from "path";
|
|
100748
101198
|
function ExitAfterRender({ children }) {
|
|
100749
101199
|
const { exit } = use_app_default();
|
|
100750
101200
|
import_react59.useEffect(() => {
|
|
@@ -100797,7 +101247,7 @@ function App2({ args, taskPhase }) {
|
|
|
100797
101247
|
}
|
|
100798
101248
|
const layout = getLayout();
|
|
100799
101249
|
const stateDir = layout.taskStateDir(args.name);
|
|
100800
|
-
if (getStorage().read(
|
|
101250
|
+
if (getStorage().read(join17(stateDir, ".ralph-state.json")) === null) {
|
|
100801
101251
|
return /* @__PURE__ */ jsx_dev_runtime9.jsxDEV(ErrorMessage, {
|
|
100802
101252
|
message: `Error: change '${args.name}' not found`
|
|
100803
101253
|
}, undefined, false, undefined, this);
|
|
@@ -100851,7 +101301,7 @@ var init_App2 = __esm(async () => {
|
|
|
100851
101301
|
|
|
100852
101302
|
// packages/log/src/log.ts
|
|
100853
101303
|
import { appendFile } from "fs/promises";
|
|
100854
|
-
import { join as
|
|
101304
|
+
import { join as join18, dirname as dirname7 } from "path";
|
|
100855
101305
|
import { homedir as homedir4 } from "os";
|
|
100856
101306
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
100857
101307
|
function fmt(type, text) {
|
|
@@ -100900,14 +101350,14 @@ var init_log = __esm(() => {
|
|
|
100900
101350
|
init_version();
|
|
100901
101351
|
jsonLogChains = new Map;
|
|
100902
101352
|
ANSI_RE = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
100903
|
-
AGENT_LOG_PATH =
|
|
101353
|
+
AGENT_LOG_PATH = join18(homedir4(), ".ralph", "agent-mode.log");
|
|
100904
101354
|
mkdir5(dirname7(AGENT_LOG_PATH), { recursive: true }).catch(() => {
|
|
100905
101355
|
return;
|
|
100906
101356
|
});
|
|
100907
101357
|
});
|
|
100908
101358
|
|
|
100909
101359
|
// apps/loop/src/debug.ts
|
|
100910
|
-
import { join as
|
|
101360
|
+
import { join as join19 } from "path";
|
|
100911
101361
|
function fmtTs(d) {
|
|
100912
101362
|
return d.toISOString().replace("T", " ").slice(0, 23);
|
|
100913
101363
|
}
|
|
@@ -101019,7 +101469,7 @@ function detectDebugStuck(lines) {
|
|
|
101019
101469
|
};
|
|
101020
101470
|
}
|
|
101021
101471
|
async function inspectBinary(projectRoot) {
|
|
101022
|
-
const binPath =
|
|
101472
|
+
const binPath = join19(projectRoot, ".ralph", "bin", "cli.js");
|
|
101023
101473
|
const file2 = Bun.file(binPath);
|
|
101024
101474
|
if (!await file2.exists())
|
|
101025
101475
|
return null;
|
|
@@ -101044,7 +101494,7 @@ async function inspectBinary(projectRoot) {
|
|
|
101044
101494
|
async function resolveDebugTarget(projectRoot, opts) {
|
|
101045
101495
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
101046
101496
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
101047
|
-
const jsonlLogFile = Bun.file(
|
|
101497
|
+
const jsonlLogFile = Bun.file(join19(projectRoot, ".ralph", "agent.log"));
|
|
101048
101498
|
const jsonlLines = await jsonlLogFile.exists() ? parseJsonlLog(await jsonlLogFile.text()) : [];
|
|
101049
101499
|
const allLines = [...textLines, ...jsonlLines];
|
|
101050
101500
|
if (opts.name && !opts.issue) {
|
|
@@ -101149,7 +101599,7 @@ async function runDebug(opts) {
|
|
|
101149
101599
|
`);
|
|
101150
101600
|
const agentLogFile = Bun.file(AGENT_LOG_PATH);
|
|
101151
101601
|
const textLines = await agentLogFile.exists() ? parseTextLog(await agentLogFile.text()) : [];
|
|
101152
|
-
const jsonlLogPath =
|
|
101602
|
+
const jsonlLogPath = join19(projectRoot, ".ralph", "agent.log");
|
|
101153
101603
|
const jsonlLogFile = Bun.file(jsonlLogPath);
|
|
101154
101604
|
const hasJsonlLog = await jsonlLogFile.exists();
|
|
101155
101605
|
let { changeName, identifier: issueIdentifier } = await resolveDebugTarget(projectRoot, {
|
|
@@ -101163,7 +101613,7 @@ async function runDebug(opts) {
|
|
|
101163
101613
|
}
|
|
101164
101614
|
const jsonlLines = hasJsonlLog ? parseJsonlLog(await jsonlLogFile.text(), changeName) : [];
|
|
101165
101615
|
const relevantText = textLines.filter((l) => l.text.includes(changeName) || issueIdentifier !== undefined && l.text.includes(issueIdentifier));
|
|
101166
|
-
const workerLogFile = Bun.file(
|
|
101616
|
+
const workerLogFile = Bun.file(join19(projectRoot, ".ralph", "logs", `${changeName}.log`));
|
|
101167
101617
|
const workerLines = await workerLogFile.exists() ? parseTextLog(await workerLogFile.text()) : [];
|
|
101168
101618
|
const merged = [...relevantText, ...jsonlLines, ...workerLines].sort((a, b) => +a.ts - +b.ts);
|
|
101169
101619
|
const seen = new Set;
|
|
@@ -101322,8 +101772,8 @@ async function runDebug(opts) {
|
|
|
101322
101772
|
out(" \u26A0 PR currently has merge conflicts");
|
|
101323
101773
|
if (pr?.checks.some((c) => c.conclusion === "FAILURE"))
|
|
101324
101774
|
out(" \u26A0 PR has failing CI checks");
|
|
101325
|
-
const worktreePath =
|
|
101326
|
-
if (await Bun.file(
|
|
101775
|
+
const worktreePath = join19(projectRoot, ".ralph", "worktrees", changeName);
|
|
101776
|
+
if (await Bun.file(join19(worktreePath, ".git")).exists()) {
|
|
101327
101777
|
out(` Worktree : ${worktreePath}`);
|
|
101328
101778
|
}
|
|
101329
101779
|
if (!timeline.length)
|
|
@@ -101343,12 +101793,12 @@ __export(exports_src2, {
|
|
|
101343
101793
|
taskMain: () => taskMain,
|
|
101344
101794
|
main: () => main2
|
|
101345
101795
|
});
|
|
101346
|
-
import { join as
|
|
101796
|
+
import { join as join20 } from "path";
|
|
101347
101797
|
import { exists as exists2, mkdir as mkdir6, rm as rm2 } from "fs/promises";
|
|
101348
101798
|
async function ensureRalphGitignore(projectRoot) {
|
|
101349
|
-
const ralphDir =
|
|
101799
|
+
const ralphDir = join20(projectRoot, ".ralph");
|
|
101350
101800
|
await mkdir6(ralphDir, { recursive: true });
|
|
101351
|
-
const gitignorePath =
|
|
101801
|
+
const gitignorePath = join20(ralphDir, ".gitignore");
|
|
101352
101802
|
const file2 = Bun.file(gitignorePath);
|
|
101353
101803
|
if (await file2.exists()) {
|
|
101354
101804
|
const existing = await file2.text();
|
|
@@ -101390,14 +101840,14 @@ async function main2(argv) {
|
|
|
101390
101840
|
await mkdir6(statesDir, { recursive: true });
|
|
101391
101841
|
await ensureRalphGitignore(projectRoot);
|
|
101392
101842
|
const { ensureWorkflow: ensureWorkflow2 } = await Promise.resolve().then(() => (init_workflow(), exports_workflow));
|
|
101393
|
-
const workflowPath2 = await ensureWorkflow2(projectRoot);
|
|
101843
|
+
const workflowPath2 = await ensureWorkflow2(projectRoot, args.workflowFile);
|
|
101394
101844
|
process.stdout.write(`Workflow config: ${workflowPath2}
|
|
101395
101845
|
`);
|
|
101396
101846
|
const openspecBin = resolveOpenspecBin(import.meta.dir);
|
|
101397
101847
|
Bun.spawnSync({
|
|
101398
101848
|
cmd: [process.execPath, openspecBin, "init", "--tools", "none", "--force"],
|
|
101399
101849
|
stdio: ["inherit", "inherit", "inherit"],
|
|
101400
|
-
cwd:
|
|
101850
|
+
cwd: projectRoot
|
|
101401
101851
|
});
|
|
101402
101852
|
}
|
|
101403
101853
|
if (args.mode === "debug") {
|
|
@@ -101415,9 +101865,9 @@ async function main2(argv) {
|
|
|
101415
101865
|
`);
|
|
101416
101866
|
return 1;
|
|
101417
101867
|
}
|
|
101418
|
-
const worktreeDir =
|
|
101419
|
-
const changeDir =
|
|
101420
|
-
const stateDir =
|
|
101868
|
+
const worktreeDir = join20(worktreesDir(projectRoot), args.name);
|
|
101869
|
+
const changeDir = join20(tasksDir, args.name);
|
|
101870
|
+
const stateDir = join20(statesDir, args.name);
|
|
101421
101871
|
const branch = `ralph/${args.name}`;
|
|
101422
101872
|
const removed = [];
|
|
101423
101873
|
if (await exists2(worktreeDir)) {
|
|
@@ -101468,8 +101918,8 @@ async function main2(argv) {
|
|
|
101468
101918
|
return 0;
|
|
101469
101919
|
}
|
|
101470
101920
|
if (args.mode === "task" && args.name) {
|
|
101471
|
-
await mkdir6(
|
|
101472
|
-
await mkdir6(
|
|
101921
|
+
await mkdir6(join20(statesDir, args.name), { recursive: true });
|
|
101922
|
+
await mkdir6(join20(tasksDir, args.name), { recursive: true });
|
|
101473
101923
|
await ensureRalphGitignore(projectRoot);
|
|
101474
101924
|
}
|
|
101475
101925
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
@@ -101497,8 +101947,8 @@ async function taskMain(argv) {
|
|
|
101497
101947
|
const layout = projectLayout(projectRoot);
|
|
101498
101948
|
const statesDir = layout.statesDir;
|
|
101499
101949
|
const tasksDir = layout.tasksDir;
|
|
101500
|
-
await mkdir6(
|
|
101501
|
-
await mkdir6(
|
|
101950
|
+
await mkdir6(join20(statesDir, args.name), { recursive: true });
|
|
101951
|
+
await mkdir6(join20(tasksDir, args.name), { recursive: true });
|
|
101502
101952
|
await ensureRalphGitignore(projectRoot);
|
|
101503
101953
|
await runWithContext(createDefaultContext({ layout, args }), async () => {
|
|
101504
101954
|
const { waitUntilExit } = render_default(import_react60.createElement(App2, {
|
|
@@ -101741,6 +102191,7 @@ async function parseAgentArgs(argv) {
|
|
|
101741
102191
|
}
|
|
101742
102192
|
}
|
|
101743
102193
|
await resolvePromptFile(result2, state);
|
|
102194
|
+
resolveWorkflowFile(result2, state);
|
|
101744
102195
|
if (result2.fixCi && !result2.createPr) {
|
|
101745
102196
|
throw new Error("--fix-ci requires --create-pr");
|
|
101746
102197
|
}
|
|
@@ -101778,6 +102229,7 @@ var init_cli2 = __esm(() => {
|
|
|
101778
102229
|
"",
|
|
101779
102230
|
"Options:",
|
|
101780
102231
|
" --name <id> Change name / ticket identifier (list / debug filter)",
|
|
102232
|
+
" --workflow <path> Path to an alternate WORKFLOW.md (default: <project>/WORKFLOW.md)",
|
|
101781
102233
|
" --prompt <text> Task description appended to every scaffolded proposal",
|
|
101782
102234
|
" --prompt-file <path> Read prompt from file",
|
|
101783
102235
|
" --model <model> Set model (haiku|sonnet|opus)",
|
|
@@ -101838,12 +102290,12 @@ __export(exports_config, {
|
|
|
101838
102290
|
loadRalphyConfig: () => loadRalphyConfig,
|
|
101839
102291
|
ensureRalphyConfig: () => ensureRalphyConfig
|
|
101840
102292
|
});
|
|
101841
|
-
async function loadRalphyConfig(projectRoot) {
|
|
101842
|
-
const { config: config2 } = await loadWorkflow(projectRoot);
|
|
102293
|
+
async function loadRalphyConfig(projectRoot, workflowFile) {
|
|
102294
|
+
const { config: config2 } = await loadWorkflow(projectRoot, workflowFile);
|
|
101843
102295
|
return config2;
|
|
101844
102296
|
}
|
|
101845
|
-
async function ensureRalphyConfig(projectRoot) {
|
|
101846
|
-
return ensureWorkflow(projectRoot);
|
|
102297
|
+
async function ensureRalphyConfig(projectRoot, workflowFile) {
|
|
102298
|
+
return ensureWorkflow(projectRoot, workflowFile);
|
|
101847
102299
|
}
|
|
101848
102300
|
var init_config = __esm(() => {
|
|
101849
102301
|
init_workflow();
|
|
@@ -101897,7 +102349,7 @@ function formatError2(err) {
|
|
|
101897
102349
|
}
|
|
101898
102350
|
|
|
101899
102351
|
// apps/agent/src/shared/capabilities/fs-change.ts
|
|
101900
|
-
import { join as
|
|
102352
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
101901
102353
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
101902
102354
|
var scaffold, prependTask, appendSteering, fsChange;
|
|
101903
102355
|
var init_fs_change = __esm(() => {
|
|
@@ -101910,11 +102362,11 @@ var init_fs_change = __esm(() => {
|
|
|
101910
102362
|
errorFormatter: formatError2,
|
|
101911
102363
|
run: async (args) => {
|
|
101912
102364
|
await mkdir7(args.changeDir, { recursive: true });
|
|
101913
|
-
await mkdir7(
|
|
102365
|
+
await mkdir7(join21(args.changeDir, "specs"), { recursive: true });
|
|
101914
102366
|
await mkdir7(args.stateDir, { recursive: true });
|
|
101915
|
-
await Bun.write(
|
|
101916
|
-
await Bun.write(
|
|
101917
|
-
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);
|
|
101918
102370
|
}
|
|
101919
102371
|
};
|
|
101920
102372
|
prependTask = {
|
|
@@ -101932,7 +102384,7 @@ var init_fs_change = __esm(() => {
|
|
|
101932
102384
|
retryPolicy: NO_RETRY,
|
|
101933
102385
|
errorFormatter: formatError2,
|
|
101934
102386
|
run: async (args) => {
|
|
101935
|
-
const path =
|
|
102387
|
+
const path = join21(args.changeDir, "steering.md");
|
|
101936
102388
|
const f2 = Bun.file(path);
|
|
101937
102389
|
const existing = await f2.exists() ? await f2.text() : null;
|
|
101938
102390
|
const updated = existing ? `${args.message}
|
|
@@ -101947,11 +102399,11 @@ ${existing.trimStart()}` : `${args.message}
|
|
|
101947
102399
|
});
|
|
101948
102400
|
|
|
101949
102401
|
// apps/agent/src/agent/worktree.ts
|
|
101950
|
-
import { basename as basename2, join as
|
|
102402
|
+
import { basename as basename2, join as join22 } from "path";
|
|
101951
102403
|
import { homedir as homedir5 } from "os";
|
|
101952
102404
|
import { exists as exists3 } from "fs/promises";
|
|
101953
102405
|
function worktreesDir2(projectRoot) {
|
|
101954
|
-
return
|
|
102406
|
+
return join22(homedir5(), ".ralph", basename2(projectRoot), "worktrees");
|
|
101955
102407
|
}
|
|
101956
102408
|
function branchForChange(changeName) {
|
|
101957
102409
|
return `ralph/${changeName}`;
|
|
@@ -101970,7 +102422,7 @@ function createWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
101970
102422
|
}
|
|
101971
102423
|
async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
101972
102424
|
const dir = worktreesDir2(projectRoot);
|
|
101973
|
-
const cwd2 =
|
|
102425
|
+
const cwd2 = join22(dir, changeName);
|
|
101974
102426
|
const branch = branchForChange(changeName);
|
|
101975
102427
|
const list = await runner.run(["worktree", "list", "--porcelain"], projectRoot);
|
|
101976
102428
|
if (list.stdout.includes(`worktree ${cwd2}
|
|
@@ -101995,7 +102447,7 @@ async function provisionWorktree(projectRoot, changeName, baseBranch, runner) {
|
|
|
101995
102447
|
return { cwd: cwd2, branch };
|
|
101996
102448
|
}
|
|
101997
102449
|
async function installPrePushHook(cwd2, runner) {
|
|
101998
|
-
const hookPath =
|
|
102450
|
+
const hookPath = join22(cwd2, ".ralph-hooks", "pre-push");
|
|
101999
102451
|
await Bun.write(hookPath, PRE_PUSH_HOOK_SCRIPT);
|
|
102000
102452
|
const chmod = Bun.spawn(["chmod", "+x", hookPath]);
|
|
102001
102453
|
await chmod.exited;
|
|
@@ -102041,8 +102493,8 @@ async function isWorktreeSafeToRemove(cwd2, base2, runner) {
|
|
|
102041
102493
|
return { safe: true, dirty, unpushedCommits };
|
|
102042
102494
|
}
|
|
102043
102495
|
async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
102044
|
-
const dst =
|
|
102045
|
-
const src =
|
|
102496
|
+
const dst = join22(worktreeCwd, ".mcp.json");
|
|
102497
|
+
const src = join22(projectRoot, ".mcp.json");
|
|
102046
102498
|
const source = await exists3(dst) ? dst : await exists3(src) ? src : null;
|
|
102047
102499
|
if (!source)
|
|
102048
102500
|
return;
|
|
@@ -102056,7 +102508,7 @@ async function seedWorktreeMcpConfig(projectRoot, worktreeCwd) {
|
|
|
102056
102508
|
if (servers && typeof servers === "object") {
|
|
102057
102509
|
for (const cfg of Object.values(servers)) {
|
|
102058
102510
|
if (Array.isArray(cfg.args)) {
|
|
102059
|
-
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);
|
|
102060
102512
|
}
|
|
102061
102513
|
}
|
|
102062
102514
|
}
|
|
@@ -102150,7 +102602,7 @@ async function runCapability(cap, args, ctx = {}) {
|
|
|
102150
102602
|
throw lastError;
|
|
102151
102603
|
}
|
|
102152
102604
|
function sleepMs(ms) {
|
|
102153
|
-
return new Promise((
|
|
102605
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
102154
102606
|
}
|
|
102155
102607
|
|
|
102156
102608
|
// packages/workflow/src/boundaries.ts
|
|
@@ -103906,7 +104358,7 @@ function emitFeatureSkipped(bus, id, reason) {
|
|
|
103906
104358
|
var init_run_feature = () => {};
|
|
103907
104359
|
|
|
103908
104360
|
// apps/agent/src/agent/post-task.ts
|
|
103909
|
-
import { join as
|
|
104361
|
+
import { join as join23, dirname as dirname9 } from "path";
|
|
103910
104362
|
function summarizeUncommittedStatus(stdout) {
|
|
103911
104363
|
const lines = stdout.split(`
|
|
103912
104364
|
`).filter((line) => line.length > 0);
|
|
@@ -103978,7 +104430,7 @@ async function reactivateState(stateFilePath, log3, changeName) {
|
|
|
103978
104430
|
async function runWorkerWithFixTask(ctx, heading, body) {
|
|
103979
104431
|
try {
|
|
103980
104432
|
await runCapability(fsChange.prependTask, {
|
|
103981
|
-
tasksPath:
|
|
104433
|
+
tasksPath: join23(ctx.changeDir, AGENT_TASKS_FILENAME),
|
|
103982
104434
|
heading,
|
|
103983
104435
|
failureOutput: body
|
|
103984
104436
|
});
|
|
@@ -104529,7 +104981,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104529
104981
|
emit3("validate-fix", command);
|
|
104530
104982
|
log3(`! validation check failed: ${command}`, "yellow");
|
|
104531
104983
|
try {
|
|
104532
|
-
await prependFixTask(
|
|
104984
|
+
await prependFixTask(join23(changeDir, AGENT_TASKS_FILENAME), `Fix failing validation: ${command}`, output || `Command exited with code ${exitCode}`);
|
|
104533
104985
|
} catch (err) {
|
|
104534
104986
|
log3(`! could not prepend fix task: ${err.message}`, "red");
|
|
104535
104987
|
return 1;
|
|
@@ -104540,7 +104992,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104540
104992
|
}
|
|
104541
104993
|
}
|
|
104542
104994
|
try {
|
|
104543
|
-
await prependFixTask(
|
|
104995
|
+
await prependFixTask(join23(changeDir, AGENT_TASKS_FILENAME), "Run openspec validation", [
|
|
104544
104996
|
`Run \`bunx openspec validate ${changeName}\` to validate the change artifacts.`,
|
|
104545
104997
|
`Commit any pending changes before running the validation command.`
|
|
104546
104998
|
].join(`
|
|
@@ -104553,7 +105005,7 @@ async function runValidateOnlyPhase(input, deps) {
|
|
|
104553
105005
|
return respawnWorker();
|
|
104554
105006
|
}
|
|
104555
105007
|
async function recordGaveUp(stateFilePath, log3, changeName) {
|
|
104556
|
-
const path =
|
|
105008
|
+
const path = join23(dirname9(stateFilePath), GAVEUP_COUNT_FILE);
|
|
104557
105009
|
try {
|
|
104558
105010
|
const file2 = Bun.file(path);
|
|
104559
105011
|
const current = await file2.exists() ? Number.parseInt(await file2.text(), 10) || 0 : 0;
|
|
@@ -105960,15 +106412,15 @@ var init_coordinator2 = __esm(() => {
|
|
|
105960
106412
|
});
|
|
105961
106413
|
|
|
105962
106414
|
// apps/agent/src/agent/scaffold.ts
|
|
105963
|
-
import { join as
|
|
106415
|
+
import { join as join24 } from "path";
|
|
105964
106416
|
function changeNameForIssue(issue2) {
|
|
105965
106417
|
const slug = issue2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40).replace(/^-+|-+$/g, "");
|
|
105966
106418
|
return slug ? `${issue2.identifier.toLowerCase()}-${slug}` : issue2.identifier.toLowerCase();
|
|
105967
106419
|
}
|
|
105968
106420
|
async function scaffoldChangeForIssue(tasksDir, statesDir, issue2, comments = [], appendPrompt = "", attachments = []) {
|
|
105969
106421
|
const name = changeNameForIssue(issue2);
|
|
105970
|
-
const changeDir =
|
|
105971
|
-
const stateDir =
|
|
106422
|
+
const changeDir = join24(tasksDir, name);
|
|
106423
|
+
const stateDir = join24(statesDir, name);
|
|
105972
106424
|
const commentsBlock = comments.length > 0 ? [
|
|
105973
106425
|
"",
|
|
105974
106426
|
"## Linear comments",
|
|
@@ -106104,7 +106556,7 @@ var init_detections = __esm(() => {
|
|
|
106104
106556
|
});
|
|
106105
106557
|
|
|
106106
106558
|
// apps/agent/src/features/confirmation/state.ts
|
|
106107
|
-
import { dirname as dirname10, join as
|
|
106559
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
106108
106560
|
async function readInlineConfirmation(statePath) {
|
|
106109
106561
|
const f2 = Bun.file(statePath);
|
|
106110
106562
|
if (!await f2.exists())
|
|
@@ -106143,8 +106595,8 @@ async function restartFromDesign(changeDir, changeName) {
|
|
|
106143
106595
|
""
|
|
106144
106596
|
].join(`
|
|
106145
106597
|
`);
|
|
106146
|
-
await Bun.write(
|
|
106147
|
-
const tasksPath =
|
|
106598
|
+
await Bun.write(join25(changeDir, "design.md"), designStub);
|
|
106599
|
+
const tasksPath = join25(changeDir, "tasks.md");
|
|
106148
106600
|
if (await Bun.file(tasksPath).exists()) {
|
|
106149
106601
|
await Bun.write(tasksPath, `# Tasks
|
|
106150
106602
|
|
|
@@ -106374,7 +106826,7 @@ var init_inspect = __esm(() => {
|
|
|
106374
106826
|
});
|
|
106375
106827
|
|
|
106376
106828
|
// apps/agent/src/features/confirmation/awaiting.ts
|
|
106377
|
-
import { join as
|
|
106829
|
+
import { join as join26 } from "path";
|
|
106378
106830
|
async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
106379
106831
|
const tracked = deps.cwdOf(changeName);
|
|
106380
106832
|
if (tracked)
|
|
@@ -106382,12 +106834,12 @@ async function resolveChangeCwdForIssue(issue2, changeName, deps) {
|
|
|
106382
106834
|
if (!deps.useWorktree)
|
|
106383
106835
|
return deps.projectRoot;
|
|
106384
106836
|
const root = worktreesDir2(deps.projectRoot);
|
|
106385
|
-
const canonical =
|
|
106386
|
-
if (await Bun.file(
|
|
106837
|
+
const canonical = join26(root, worktreeDirNameForIssue(issue2));
|
|
106838
|
+
if (await Bun.file(join26(canonical, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106387
106839
|
return canonical;
|
|
106388
106840
|
}
|
|
106389
|
-
const legacy =
|
|
106390
|
-
if (await Bun.file(
|
|
106841
|
+
const legacy = join26(root, changeName);
|
|
106842
|
+
if (await Bun.file(join26(legacy, "openspec", "changes", changeName, "tasks.md")).exists()) {
|
|
106391
106843
|
return legacy;
|
|
106392
106844
|
}
|
|
106393
106845
|
return deps.projectRoot;
|
|
@@ -106522,9 +106974,9 @@ async function processAwaitingForIssue(issue2, deps) {
|
|
|
106522
106974
|
const layout = projectLayout(cwd2);
|
|
106523
106975
|
const changeDir = layout.changeDir(changeName);
|
|
106524
106976
|
const statePath = layout.stateFile(changeName);
|
|
106525
|
-
const tasks2 = await readTextOrNull(
|
|
106526
|
-
const proposal = await readTextOrNull(
|
|
106527
|
-
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"));
|
|
106528
106980
|
let commentsCache = null;
|
|
106529
106981
|
const getComments = async () => {
|
|
106530
106982
|
if (commentsCache)
|
|
@@ -107068,7 +107520,7 @@ var init_linear_resolvers = __esm(() => {
|
|
|
107068
107520
|
|
|
107069
107521
|
// apps/agent/src/agent/wire/prepare.ts
|
|
107070
107522
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
107071
|
-
import { join as
|
|
107523
|
+
import { join as join27 } from "path";
|
|
107072
107524
|
function createPrepareHelpers(input) {
|
|
107073
107525
|
const {
|
|
107074
107526
|
args,
|
|
@@ -107132,7 +107584,7 @@ function createPrepareHelpers(input) {
|
|
|
107132
107584
|
let changeName;
|
|
107133
107585
|
const wtLayoutPre = projectLayout(workerCwd);
|
|
107134
107586
|
const derivedName = changeNameForIssue(issue2);
|
|
107135
|
-
const tasksMdPath =
|
|
107587
|
+
const tasksMdPath = join27(wtLayoutPre.changeDir(derivedName), "tasks.md");
|
|
107136
107588
|
const tasksMdExists = await Bun.file(tasksMdPath).exists();
|
|
107137
107589
|
const isFresh = !tasksMdExists;
|
|
107138
107590
|
if (isFresh) {
|
|
@@ -107150,7 +107602,7 @@ function createPrepareHelpers(input) {
|
|
|
107150
107602
|
}
|
|
107151
107603
|
let workflowPrompt = "";
|
|
107152
107604
|
try {
|
|
107153
|
-
const workflow = await loadWorkflow(projectRoot);
|
|
107605
|
+
const workflow = await loadWorkflow(projectRoot, args.workflowFile);
|
|
107154
107606
|
workflowPrompt = renderWorkflowPrompt(workflow, {
|
|
107155
107607
|
issue: {
|
|
107156
107608
|
identifier: issue2.identifier,
|
|
@@ -107210,7 +107662,7 @@ function createPrepareHelpers(input) {
|
|
|
107210
107662
|
if (!workerCwd)
|
|
107211
107663
|
return;
|
|
107212
107664
|
const wtLayout = projectLayout(workerCwd);
|
|
107213
|
-
const tasksFile =
|
|
107665
|
+
const tasksFile = join27(wtLayout.changeDir(changeName), AGENT_TASKS_FILENAME);
|
|
107214
107666
|
if (trigger === "review") {
|
|
107215
107667
|
let body2;
|
|
107216
107668
|
let heading;
|
|
@@ -107503,14 +107955,14 @@ var init_pr_discovery = __esm(() => {
|
|
|
107503
107955
|
});
|
|
107504
107956
|
|
|
107505
107957
|
// apps/agent/src/features/review-followup/scan.ts
|
|
107506
|
-
import { dirname as dirname11, join as
|
|
107958
|
+
import { dirname as dirname11, join as join28 } from "path";
|
|
107507
107959
|
async function resolveReviewStateDir(changeName, deps) {
|
|
107508
107960
|
const root = deps.cwdOf(changeName);
|
|
107509
107961
|
if (root)
|
|
107510
107962
|
return dirname11(projectLayout(root).stateFile(changeName));
|
|
107511
107963
|
if (!deps.useWorktree)
|
|
107512
107964
|
return dirname11(projectLayout(deps.projectRoot).stateFile(changeName));
|
|
107513
|
-
const wtPath =
|
|
107965
|
+
const wtPath = join28(worktreesDir2(deps.projectRoot), changeName);
|
|
107514
107966
|
const statePath = projectLayout(wtPath).stateFile(changeName);
|
|
107515
107967
|
if (await Bun.file(statePath).exists())
|
|
107516
107968
|
return dirname11(statePath);
|
|
@@ -107520,7 +107972,7 @@ async function readReviewWatermark(stateDir) {
|
|
|
107520
107972
|
const sidecar = await readSlotSidecar(stateDir, "review");
|
|
107521
107973
|
if (sidecar)
|
|
107522
107974
|
return sidecar.lastConsumedCommentAt ?? null;
|
|
107523
|
-
const file2 = Bun.file(
|
|
107975
|
+
const file2 = Bun.file(join28(stateDir, ".ralph-state.json"));
|
|
107524
107976
|
if (!await file2.exists())
|
|
107525
107977
|
return null;
|
|
107526
107978
|
try {
|
|
@@ -107732,7 +108184,7 @@ var init_github = __esm(() => {
|
|
|
107732
108184
|
|
|
107733
108185
|
// apps/agent/src/agent/wire/mention-scan.ts
|
|
107734
108186
|
import { readdir as readdir2 } from "fs/promises";
|
|
107735
|
-
import { join as
|
|
108187
|
+
import { join as join29 } from "path";
|
|
107736
108188
|
function createMentionScanner(input) {
|
|
107737
108189
|
const {
|
|
107738
108190
|
apiKey,
|
|
@@ -107898,7 +108350,7 @@ function createMentionScanner(input) {
|
|
|
107898
108350
|
async function isChangeArchivedForIssue(issue2, cwdByChange, projectRoot) {
|
|
107899
108351
|
const changeName = changeNameForIssue(issue2);
|
|
107900
108352
|
const root = cwdByChange.get(changeName) ?? projectRoot;
|
|
107901
|
-
const archiveDir =
|
|
108353
|
+
const archiveDir = join29(projectLayout(root).tasksDir, "archive");
|
|
107902
108354
|
let entries;
|
|
107903
108355
|
try {
|
|
107904
108356
|
entries = await readdir2(archiveDir);
|
|
@@ -107922,9 +108374,9 @@ var init_mention_scan = __esm(() => {
|
|
|
107922
108374
|
});
|
|
107923
108375
|
|
|
107924
108376
|
// apps/agent/src/agent/wire/spawn/default.ts
|
|
107925
|
-
import { join as
|
|
108377
|
+
import { join as join30 } from "path";
|
|
107926
108378
|
function defaultSpawn(changeName, cmd, cwd2, logsDir, onWorkerOutput, note) {
|
|
107927
|
-
const logFilePath =
|
|
108379
|
+
const logFilePath = join30(logsDir, `${changeName}.log`);
|
|
107928
108380
|
const ANSI_RE2 = /\x1b(?:\[[0-9;]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|.)/g;
|
|
107929
108381
|
const BOX_ONLY_RE = /^[\s\u2500\u2502\u256D\u256E\u2570\u256F\u254C\u2504\u2501\u2503]+$/;
|
|
107930
108382
|
const STATUS_BAR_LINE_RE = /^[\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F\u2713\u2717]\s+iter\s+\d+/;
|
|
@@ -107985,16 +108437,16 @@ var init_default2 = __esm(() => {
|
|
|
107985
108437
|
});
|
|
107986
108438
|
|
|
107987
108439
|
// apps/agent/src/agent/state/agent-run-state.ts
|
|
107988
|
-
import { basename as basename3, join as
|
|
108440
|
+
import { basename as basename3, join as join31 } from "path";
|
|
107989
108441
|
import { homedir as homedir6 } from "os";
|
|
107990
108442
|
import { mkdir as mkdir9, writeFile } from "fs/promises";
|
|
107991
108443
|
function agentRunStatePath(projectRoot) {
|
|
107992
|
-
return
|
|
108444
|
+
return join31(homedir6(), ".ralph", basename3(projectRoot), "agent-state.json");
|
|
107993
108445
|
}
|
|
107994
108446
|
async function writeAgentRunState(state) {
|
|
107995
108447
|
const path = agentRunStatePath(state.projectRoot);
|
|
107996
108448
|
try {
|
|
107997
|
-
await mkdir9(
|
|
108449
|
+
await mkdir9(join31(homedir6(), ".ralph", basename3(state.projectRoot)), { recursive: true });
|
|
107998
108450
|
await writeFile(path, JSON.stringify(state, null, 2) + `
|
|
107999
108451
|
`, "utf-8");
|
|
108000
108452
|
} catch {}
|
|
@@ -108021,17 +108473,17 @@ var CI_FAILED_EXIT2 = 70, PR_FAILED_EXIT2 = 71, NO_CHANGES_EXIT2 = 72;
|
|
|
108021
108473
|
// packages/retro/src/paths.ts
|
|
108022
108474
|
import { homedir as homedir7 } from "os";
|
|
108023
108475
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
108024
|
-
import { join as
|
|
108476
|
+
import { join as join32 } from "path";
|
|
108025
108477
|
function retroDir() {
|
|
108026
|
-
return
|
|
108478
|
+
return join32(homedir7(), ".ralph", "retro");
|
|
108027
108479
|
}
|
|
108028
108480
|
async function resolveRetroOutputPath(identifier, date5, dir = retroDir()) {
|
|
108029
108481
|
await mkdir10(dir, { recursive: true });
|
|
108030
|
-
const base2 =
|
|
108482
|
+
const base2 = join32(dir, `${identifier}-${date5}.md`);
|
|
108031
108483
|
if (!await Bun.file(base2).exists())
|
|
108032
108484
|
return base2;
|
|
108033
108485
|
for (let n = 2;; n++) {
|
|
108034
|
-
const candidate =
|
|
108486
|
+
const candidate = join32(dir, `${identifier}-${date5}-${n}.md`);
|
|
108035
108487
|
if (!await Bun.file(candidate).exists())
|
|
108036
108488
|
return candidate;
|
|
108037
108489
|
}
|
|
@@ -108145,7 +108597,7 @@ var init_retro = __esm(() => {
|
|
|
108145
108597
|
});
|
|
108146
108598
|
|
|
108147
108599
|
// apps/agent/src/agent/wire/spawn/worker.ts
|
|
108148
|
-
import { join as
|
|
108600
|
+
import { join as join33 } from "path";
|
|
108149
108601
|
function localDateStamp(d) {
|
|
108150
108602
|
const y = d.getFullYear();
|
|
108151
108603
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
@@ -108276,7 +108728,7 @@ function createSpawnWorker(input) {
|
|
|
108276
108728
|
paths: {
|
|
108277
108729
|
changeDir: info.changeDir,
|
|
108278
108730
|
stateFilePath: info.stateFilePath,
|
|
108279
|
-
logFile:
|
|
108731
|
+
logFile: join33(logsDir, `${info.changeName}.log`),
|
|
108280
108732
|
jsonLogFile: args.jsonLogFile ?? null,
|
|
108281
108733
|
agentStateFile: agentRunStatePath(projectRoot)
|
|
108282
108734
|
}
|
|
@@ -108293,7 +108745,7 @@ function createSpawnWorker(input) {
|
|
|
108293
108745
|
return function spawnWorker(changeName, _issue, trigger) {
|
|
108294
108746
|
const cwd2 = cwdByChange.get(changeName) ?? projectRoot;
|
|
108295
108747
|
const injected = runners?.spawnWorker;
|
|
108296
|
-
const missionTasksPath =
|
|
108748
|
+
const missionTasksPath = join33(projectLayout(cwd2).changeDir(changeName), MISSION_TASKS_FILENAME);
|
|
108297
108749
|
const prevTasksPromise = (async () => {
|
|
108298
108750
|
const f2 = Bun.file(missionTasksPath);
|
|
108299
108751
|
return await f2.exists() ? await f2.text() : "";
|
|
@@ -108301,7 +108753,7 @@ function createSpawnWorker(input) {
|
|
|
108301
108753
|
let logFilePath;
|
|
108302
108754
|
let handle;
|
|
108303
108755
|
if (injected) {
|
|
108304
|
-
logFilePath =
|
|
108756
|
+
logFilePath = join33(logsDir, `${changeName}.log`);
|
|
108305
108757
|
handle = injected(buildTaskCmdFor(changeName), cwd2);
|
|
108306
108758
|
} else {
|
|
108307
108759
|
const r = defaultSpawn(changeName, buildTaskCmdFor(changeName), cwd2, logsDir, onWorkerOutput, `spawn at ${new Date().toISOString()}`);
|
|
@@ -108323,7 +108775,7 @@ function createSpawnWorker(input) {
|
|
|
108323
108775
|
const wantAutoMerge = issueForChange ? issueMatchesGetIndicator(issueForChange, indicators.getAutoMerge) : false;
|
|
108324
108776
|
const wrapped = handle.exited.then(async (code) => {
|
|
108325
108777
|
const workerLayout = projectLayout(cwd2);
|
|
108326
|
-
const validateSpecPath =
|
|
108778
|
+
const validateSpecPath = join33(workerLayout.changeDir(changeName), "specs", "validate.md");
|
|
108327
108779
|
const hasValidateSpec = await Bun.file(validateSpecPath).exists();
|
|
108328
108780
|
const wantValidateOnly = hasValidateSpec && !wantPrBase;
|
|
108329
108781
|
if (hasValidateSpec) {
|
|
@@ -108807,7 +109259,7 @@ var init_linear_sync = __esm(() => {
|
|
|
108807
109259
|
});
|
|
108808
109260
|
|
|
108809
109261
|
// apps/agent/src/agent/linear-sync/comment-sync.ts
|
|
108810
|
-
import { dirname as dirname12, join as
|
|
109262
|
+
import { dirname as dirname12, join as join34 } from "path";
|
|
108811
109263
|
async function readInlineLinearComments(statePath) {
|
|
108812
109264
|
const file2 = Bun.file(statePath);
|
|
108813
109265
|
if (!await file2.exists())
|
|
@@ -108848,7 +109300,7 @@ function isCommentNotFoundError(err) {
|
|
|
108848
109300
|
return text.includes("not found") || text.includes("could not find") || text.includes("entity not found");
|
|
108849
109301
|
}
|
|
108850
109302
|
async function readTasksMd(changeDir, log3) {
|
|
108851
|
-
const file2 = Bun.file(
|
|
109303
|
+
const file2 = Bun.file(join34(changeDir, "tasks.md"));
|
|
108852
109304
|
if (!await file2.exists()) {
|
|
108853
109305
|
log3(` comment-sync: tasks.md missing in ${changeDir}, skipping`, "gray");
|
|
108854
109306
|
return null;
|
|
@@ -108954,14 +109406,14 @@ async function postPlanCommentOnce(deps) {
|
|
|
108954
109406
|
const check2 = parsePlanningSection(tasksMd);
|
|
108955
109407
|
if (!check2.allChecked)
|
|
108956
109408
|
return null;
|
|
108957
|
-
const proposalPath =
|
|
109409
|
+
const proposalPath = join34(deps.changeDir, "proposal.md");
|
|
108958
109410
|
const why = await readSection(proposalPath, "Why");
|
|
108959
109411
|
const whatChanges = await readSection(proposalPath, "What Changes");
|
|
108960
109412
|
if (!why && !whatChanges) {
|
|
108961
109413
|
deps.log(` comment-sync: proposal.md has no Why/What Changes, skipping plan comment`, "gray");
|
|
108962
109414
|
return null;
|
|
108963
109415
|
}
|
|
108964
|
-
const designSummary = await readFirstParagraph(
|
|
109416
|
+
const designSummary = await readFirstParagraph(join34(deps.changeDir, "design.md"));
|
|
108965
109417
|
const parts = [`### ${PLAN_COMMENT_TITLE} \u2014 \`${deps.changeName}\``];
|
|
108966
109418
|
if (why) {
|
|
108967
109419
|
parts.push("", "**Why**", "", why);
|
|
@@ -111615,11 +112067,11 @@ var require_tslib = __commonJS((exports, module) => {
|
|
|
111615
112067
|
};
|
|
111616
112068
|
__awaiter = function(thisArg, _arguments, P, generator) {
|
|
111617
112069
|
function adopt(value) {
|
|
111618
|
-
return value instanceof P ? value : new P(function(
|
|
111619
|
-
|
|
112070
|
+
return value instanceof P ? value : new P(function(resolve4) {
|
|
112071
|
+
resolve4(value);
|
|
111620
112072
|
});
|
|
111621
112073
|
}
|
|
111622
|
-
return new (P || (P = Promise))(function(
|
|
112074
|
+
return new (P || (P = Promise))(function(resolve4, reject2) {
|
|
111623
112075
|
function fulfilled(value) {
|
|
111624
112076
|
try {
|
|
111625
112077
|
step(generator.next(value));
|
|
@@ -111635,7 +112087,7 @@ var require_tslib = __commonJS((exports, module) => {
|
|
|
111635
112087
|
}
|
|
111636
112088
|
}
|
|
111637
112089
|
function step(result2) {
|
|
111638
|
-
result2.done ?
|
|
112090
|
+
result2.done ? resolve4(result2.value) : adopt(result2.value).then(fulfilled, rejected);
|
|
111639
112091
|
}
|
|
111640
112092
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
111641
112093
|
});
|
|
@@ -111864,14 +112316,14 @@ var require_tslib = __commonJS((exports, module) => {
|
|
|
111864
112316
|
}, i);
|
|
111865
112317
|
function verb(n) {
|
|
111866
112318
|
i[n] = o[n] && function(v) {
|
|
111867
|
-
return new Promise(function(
|
|
111868
|
-
v = o[n](v), settle(
|
|
112319
|
+
return new Promise(function(resolve4, reject2) {
|
|
112320
|
+
v = o[n](v), settle(resolve4, reject2, v.done, v.value);
|
|
111869
112321
|
});
|
|
111870
112322
|
};
|
|
111871
112323
|
}
|
|
111872
|
-
function settle(
|
|
112324
|
+
function settle(resolve4, reject2, d, v) {
|
|
111873
112325
|
Promise.resolve(v).then(function(v2) {
|
|
111874
|
-
|
|
112326
|
+
resolve4({ value: v2, done: d });
|
|
111875
112327
|
}, reject2);
|
|
111876
112328
|
}
|
|
111877
112329
|
};
|
|
@@ -112734,9 +113186,9 @@ var require_clone = __commonJS((exports, module) => {
|
|
|
112734
113186
|
} else if (_instanceof2(parent2, nativeSet)) {
|
|
112735
113187
|
child = new nativeSet;
|
|
112736
113188
|
} else if (_instanceof2(parent2, nativePromise)) {
|
|
112737
|
-
child = new nativePromise(function(
|
|
113189
|
+
child = new nativePromise(function(resolve4, reject2) {
|
|
112738
113190
|
parent2.then(function(value) {
|
|
112739
|
-
|
|
113191
|
+
resolve4(_clone(value, depth2 - 1));
|
|
112740
113192
|
}, function(err) {
|
|
112741
113193
|
reject2(_clone(err, depth2 - 1));
|
|
112742
113194
|
});
|
|
@@ -260785,7 +261237,7 @@ function toPdfSafe(text) {
|
|
|
260785
261237
|
return out;
|
|
260786
261238
|
}
|
|
260787
261239
|
function renderMarkdownToPdf(md, title) {
|
|
260788
|
-
return new Promise((
|
|
261240
|
+
return new Promise((resolve4, reject2) => {
|
|
260789
261241
|
try {
|
|
260790
261242
|
const doc2 = new PDFDocument({
|
|
260791
261243
|
size: PAGE_SIZE,
|
|
@@ -260794,7 +261246,7 @@ function renderMarkdownToPdf(md, title) {
|
|
|
260794
261246
|
});
|
|
260795
261247
|
const chunks = [];
|
|
260796
261248
|
doc2.on("data", (chunk2) => chunks.push(chunk2));
|
|
260797
|
-
doc2.on("end", () =>
|
|
261249
|
+
doc2.on("end", () => resolve4(new Uint8Array(Buffer.concat(chunks))));
|
|
260798
261250
|
doc2.on("error", reject2);
|
|
260799
261251
|
doc2.fillColor(COLOR_TEXT).font(FONT_BODY).fontSize(BODY_SIZE);
|
|
260800
261252
|
const tokens = g.lexer(md);
|
|
@@ -261237,7 +261689,7 @@ var init_render_pdf = __esm(() => {
|
|
|
261237
261689
|
});
|
|
261238
261690
|
|
|
261239
261691
|
// apps/agent/src/agent/linear-sync/spec-attachments.ts
|
|
261240
|
-
import { dirname as dirname13, join as
|
|
261692
|
+
import { dirname as dirname13, join as join35 } from "path";
|
|
261241
261693
|
function describeLinearError(err) {
|
|
261242
261694
|
const e = err;
|
|
261243
261695
|
const parts = [e.message ?? String(err)];
|
|
@@ -261346,7 +261798,7 @@ async function syncSlot(deps, slot) {
|
|
|
261346
261798
|
const [primaryName, ...trailingNames] = spec.sourceFiles;
|
|
261347
261799
|
if (!primaryName)
|
|
261348
261800
|
return;
|
|
261349
|
-
const primary = Bun.file(
|
|
261801
|
+
const primary = Bun.file(join35(deps.changeDir, primaryName));
|
|
261350
261802
|
if (!await primary.exists()) {
|
|
261351
261803
|
deps.log(` spec-attachments: ${primaryName} missing, skipping`, "gray");
|
|
261352
261804
|
return;
|
|
@@ -261365,7 +261817,7 @@ async function syncSlot(deps, slot) {
|
|
|
261365
261817
|
const parts = [primaryBytes];
|
|
261366
261818
|
const enc = new TextEncoder;
|
|
261367
261819
|
for (const name of trailingNames) {
|
|
261368
|
-
const f2 = Bun.file(
|
|
261820
|
+
const f2 = Bun.file(join35(deps.changeDir, name));
|
|
261369
261821
|
if (!await f2.exists())
|
|
261370
261822
|
continue;
|
|
261371
261823
|
let raw;
|
|
@@ -261633,9 +262085,9 @@ var init_comment_sync2 = __esm(() => {
|
|
|
261633
262085
|
});
|
|
261634
262086
|
|
|
261635
262087
|
// apps/agent/src/features/pr-tracker/state.ts
|
|
261636
|
-
import { join as
|
|
262088
|
+
import { join as join36 } from "path";
|
|
261637
262089
|
async function readState2(projectRoot) {
|
|
261638
|
-
const path =
|
|
262090
|
+
const path = join36(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261639
262091
|
const file2 = Bun.file(path);
|
|
261640
262092
|
if (!await file2.exists())
|
|
261641
262093
|
return {};
|
|
@@ -261651,7 +262103,7 @@ async function readState2(projectRoot) {
|
|
|
261651
262103
|
}
|
|
261652
262104
|
}
|
|
261653
262105
|
async function writeState2(projectRoot, state) {
|
|
261654
|
-
const path =
|
|
262106
|
+
const path = join36(projectRoot, PR_TRACKER_STATE_RELPATH);
|
|
261655
262107
|
await Bun.write(path, JSON.stringify(state, null, 2));
|
|
261656
262108
|
}
|
|
261657
262109
|
var PR_TRACKER_STATE_RELPATH = ".ralph/pr-tracker-state.json";
|
|
@@ -261730,7 +262182,7 @@ var init_pr_tracker = __esm(() => {
|
|
|
261730
262182
|
});
|
|
261731
262183
|
|
|
261732
262184
|
// apps/agent/src/agent/wire.ts
|
|
261733
|
-
import { join as
|
|
262185
|
+
import { join as join37 } from "path";
|
|
261734
262186
|
function buildAgentCoordinator(input) {
|
|
261735
262187
|
const {
|
|
261736
262188
|
args,
|
|
@@ -261749,7 +262201,7 @@ function buildAgentCoordinator(input) {
|
|
|
261749
262201
|
onWorkerCmd,
|
|
261750
262202
|
onAwaitingTicket
|
|
261751
262203
|
} = input;
|
|
261752
|
-
const logsDir =
|
|
262204
|
+
const logsDir = join37(projectRoot, ".ralph", "logs");
|
|
261753
262205
|
const bus = createBus();
|
|
261754
262206
|
subscribeAgentDiag(bus, onLog);
|
|
261755
262207
|
const diag = (area, message, color) => {
|
|
@@ -261775,7 +262227,11 @@ function buildAgentCoordinator(input) {
|
|
|
261775
262227
|
const awaitingChangeSet = new Set;
|
|
261776
262228
|
const coordRef = { current: null };
|
|
261777
262229
|
let pollContext = new PollContext;
|
|
261778
|
-
|
|
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
|
+
}
|
|
261779
262235
|
const scriptRunner = input.runners?.runScript ?? (async (cmd, cwd2) => {
|
|
261780
262236
|
const proc = Bun.spawn({
|
|
261781
262237
|
cmd: ["sh", "-c", cmd],
|
|
@@ -261980,7 +262436,7 @@ function buildAgentCoordinator(input) {
|
|
|
261980
262436
|
const changeDir = projectLayout(root).changeDir(changeName);
|
|
261981
262437
|
const parts = [];
|
|
261982
262438
|
for (const name of ["tasks.md", "proposal.md", "design.md"]) {
|
|
261983
|
-
const file2 = Bun.file(
|
|
262439
|
+
const file2 = Bun.file(join37(changeDir, name));
|
|
261984
262440
|
if (!await file2.exists())
|
|
261985
262441
|
continue;
|
|
261986
262442
|
parts.push(`${name}:${file2.lastModified}:${file2.size}`);
|
|
@@ -262025,7 +262481,7 @@ function buildAgentCoordinator(input) {
|
|
|
262025
262481
|
getGaveUpTotal: async () => {
|
|
262026
262482
|
let total = 0;
|
|
262027
262483
|
for (const [changeName, root] of cwdByChange) {
|
|
262028
|
-
const file2 = Bun.file(
|
|
262484
|
+
const file2 = Bun.file(join37(projectLayout(root).taskStateDir(changeName), GAVEUP_COUNT_FILE));
|
|
262029
262485
|
if (!await file2.exists())
|
|
262030
262486
|
continue;
|
|
262031
262487
|
try {
|
|
@@ -262092,7 +262548,7 @@ async function waitForActiveWorkers(deps) {
|
|
|
262092
262548
|
const budgetMs = deps.budgetMs ?? 1e4;
|
|
262093
262549
|
const warnAtMs = deps.warnAtMs ?? 5000;
|
|
262094
262550
|
deps.stop();
|
|
262095
|
-
await new Promise((
|
|
262551
|
+
await new Promise((resolve4) => {
|
|
262096
262552
|
const start = Date.now();
|
|
262097
262553
|
let warned = false;
|
|
262098
262554
|
const wait = setInterval(() => {
|
|
@@ -262100,7 +262556,7 @@ async function waitForActiveWorkers(deps) {
|
|
|
262100
262556
|
const elapsed = Date.now() - start;
|
|
262101
262557
|
if (active === 0) {
|
|
262102
262558
|
clearInterval(wait);
|
|
262103
|
-
|
|
262559
|
+
resolve4();
|
|
262104
262560
|
return;
|
|
262105
262561
|
}
|
|
262106
262562
|
if (!warned && elapsed >= warnAtMs) {
|
|
@@ -262110,7 +262566,7 @@ async function waitForActiveWorkers(deps) {
|
|
|
262110
262566
|
if (elapsed >= budgetMs) {
|
|
262111
262567
|
clearInterval(wait);
|
|
262112
262568
|
deps.onTimeout?.(active);
|
|
262113
|
-
|
|
262569
|
+
resolve4();
|
|
262114
262570
|
}
|
|
262115
262571
|
}, 100);
|
|
262116
262572
|
});
|
|
@@ -262313,7 +262769,7 @@ var init_output_utils = __esm(() => {
|
|
|
262313
262769
|
});
|
|
262314
262770
|
|
|
262315
262771
|
// apps/agent/src/agent/state/worker-state-poll.ts
|
|
262316
|
-
import { join as
|
|
262772
|
+
import { join as join38 } from "path";
|
|
262317
262773
|
function parseSubtasks(tasksMd) {
|
|
262318
262774
|
const out = [];
|
|
262319
262775
|
let skipSection = false;
|
|
@@ -262346,7 +262802,7 @@ function initialWorkerSnapshot() {
|
|
|
262346
262802
|
async function readWorkerSnapshot(input) {
|
|
262347
262803
|
const next = { ...input.prev };
|
|
262348
262804
|
try {
|
|
262349
|
-
const file2 = Bun.file(
|
|
262805
|
+
const file2 = Bun.file(join38(input.statesDir, input.changeName, ".ralph-state.json"));
|
|
262350
262806
|
if (await file2.exists()) {
|
|
262351
262807
|
const json2 = await file2.json();
|
|
262352
262808
|
next.iter = json2.iteration ?? next.iter;
|
|
@@ -262355,10 +262811,10 @@ async function readWorkerSnapshot(input) {
|
|
|
262355
262811
|
} catch {}
|
|
262356
262812
|
if (input.changeDir) {
|
|
262357
262813
|
try {
|
|
262358
|
-
const tasksFile = Bun.file(
|
|
262359
|
-
const proposalFile = Bun.file(
|
|
262360
|
-
const designFile = Bun.file(
|
|
262361
|
-
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"));
|
|
262362
262818
|
const [tasksText, proposalText, designText, reviewFindingsText] = await Promise.all([
|
|
262363
262819
|
tasksFile.exists().then((ok) => ok ? tasksFile.text() : null),
|
|
262364
262820
|
proposalFile.exists().then((ok) => ok ? proposalFile.text() : null),
|
|
@@ -262421,7 +262877,7 @@ var init_worker_state_poll = __esm(() => {
|
|
|
262421
262877
|
});
|
|
262422
262878
|
|
|
262423
262879
|
// apps/agent/src/components/AgentMode.tsx
|
|
262424
|
-
import { join as
|
|
262880
|
+
import { join as join39 } from "path";
|
|
262425
262881
|
async function appendSteeringImpl(changeDir, message) {
|
|
262426
262882
|
await runWithContext(createDefaultContext(), async () => {
|
|
262427
262883
|
appendSteeringMessage(changeDir, message);
|
|
@@ -262695,8 +263151,8 @@ function AgentMode({
|
|
|
262695
263151
|
let cancelled = false;
|
|
262696
263152
|
async function init2() {
|
|
262697
263153
|
logSession(`=== session start ${SESSION_START} ===`);
|
|
262698
|
-
const cfgPath = await ensureConfig(projectRoot);
|
|
262699
|
-
const cfg2 = await loadConfig(projectRoot);
|
|
263154
|
+
const cfgPath = await ensureConfig(projectRoot, args.workflowFile);
|
|
263155
|
+
const cfg2 = await loadConfig(projectRoot, args.workflowFile);
|
|
262700
263156
|
cfgRef.current = cfg2;
|
|
262701
263157
|
appendLog(`agent mode v${VERSION} \u2014 config: ${cfgPath}`, "gray");
|
|
262702
263158
|
const apiKey = process.env["LINEAR_API_KEY"];
|
|
@@ -263994,7 +264450,7 @@ function AgentMode({
|
|
|
263994
264450
|
},
|
|
263995
264451
|
onSubmit: async (message) => {
|
|
263996
264452
|
try {
|
|
263997
|
-
await appendSteering2(
|
|
264453
|
+
await appendSteering2(join39(tasksDir, w2.changeName), message);
|
|
263998
264454
|
fileEmit({ type: "steering_submitted", changeName: w2.changeName, message });
|
|
263999
264455
|
} catch (err) {
|
|
264000
264456
|
const text = err.message;
|
|
@@ -264298,7 +264754,7 @@ __export(exports_list, {
|
|
|
264298
264754
|
buildBuckets: () => buildBuckets,
|
|
264299
264755
|
backlogRankByIssueId: () => backlogRankByIssueId
|
|
264300
264756
|
});
|
|
264301
|
-
import { join as
|
|
264757
|
+
import { join as join40 } from "path";
|
|
264302
264758
|
function countTaskItems(content) {
|
|
264303
264759
|
const checked = (content.match(/^- \[x\]/gm) ?? []).length;
|
|
264304
264760
|
const unchecked = (content.match(/^- \[ \]/gm) ?? []).length;
|
|
@@ -264314,13 +264770,13 @@ function buildLocalRows() {
|
|
|
264314
264770
|
const sources = [{ dir: statesDir, label: "main" }];
|
|
264315
264771
|
const worktreesRoot = worktreesDir2(projectRoot);
|
|
264316
264772
|
for (const wt of storage.list(worktreesRoot)) {
|
|
264317
|
-
sources.push({ dir:
|
|
264773
|
+
sources.push({ dir: join40(worktreesRoot, wt, ".ralph", "tasks"), label: `wt:${wt}` });
|
|
264318
264774
|
}
|
|
264319
264775
|
for (const { dir, label } of sources) {
|
|
264320
264776
|
for (const entry of storage.list(dir)) {
|
|
264321
264777
|
if (seen.has(entry))
|
|
264322
264778
|
continue;
|
|
264323
|
-
const raw = storage.read(
|
|
264779
|
+
const raw = storage.read(join40(dir, entry, ".ralph-state.json"));
|
|
264324
264780
|
if (raw === null)
|
|
264325
264781
|
continue;
|
|
264326
264782
|
let state;
|
|
@@ -264335,7 +264791,7 @@ function buildLocalRows() {
|
|
|
264335
264791
|
const firstLine = promptRaw.split(`
|
|
264336
264792
|
`).find((l3) => l3.trim() !== "") ?? "";
|
|
264337
264793
|
let progress = "\u2014";
|
|
264338
|
-
const tasksContent = storage.read(
|
|
264794
|
+
const tasksContent = storage.read(join40(dir, entry, "tasks.md"));
|
|
264339
264795
|
if (tasksContent !== null) {
|
|
264340
264796
|
const { checked, unchecked } = countTaskItems(tasksContent);
|
|
264341
264797
|
const total = checked + unchecked;
|
|
@@ -264600,7 +265056,7 @@ async function runList(input) {
|
|
|
264600
265056
|
}
|
|
264601
265057
|
const rows = buildLocalRows();
|
|
264602
265058
|
printLocalRows(rows);
|
|
264603
|
-
const cfg = await loadRalphyConfig(projectRoot);
|
|
265059
|
+
const cfg = await loadRalphyConfig(projectRoot, getArgs().workflowFile);
|
|
264604
265060
|
const apiKey = process.env["LINEAR_API_KEY"];
|
|
264605
265061
|
const indicators = cfg.linear.indicators;
|
|
264606
265062
|
const team = input.linearTeamOverride || cfg.linear.team;
|
|
@@ -264724,7 +265180,7 @@ async function runListDebug(input) {
|
|
|
264724
265180
|
process.exitCode = 1;
|
|
264725
265181
|
return;
|
|
264726
265182
|
}
|
|
264727
|
-
const cfg = await loadRalphyConfig(projectRoot);
|
|
265183
|
+
const cfg = await loadRalphyConfig(projectRoot, getArgs().workflowFile);
|
|
264728
265184
|
const indicators = cfg.linear.indicators;
|
|
264729
265185
|
const team = input.linearTeamOverride || cfg.linear.team;
|
|
264730
265186
|
const { assignee, anyAssignee } = resolveLinearFilter(input.linearFilterOverride, input.linearAssigneeOverride, cfg.linear.filter);
|
|
@@ -264835,7 +265291,7 @@ var exports_json_runner = {};
|
|
|
264835
265291
|
__export(exports_json_runner, {
|
|
264836
265292
|
runAgentJson: () => runAgentJson
|
|
264837
265293
|
});
|
|
264838
|
-
import { join as
|
|
265294
|
+
import { join as join41 } from "path";
|
|
264839
265295
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
264840
265296
|
import { homedir as homedir8 } from "os";
|
|
264841
265297
|
function makeEmit(fileSink) {
|
|
@@ -264857,13 +265313,13 @@ async function runAgentJson({
|
|
|
264857
265313
|
tasksDir,
|
|
264858
265314
|
runPreflight: runPreflight2 = runPreflight
|
|
264859
265315
|
}) {
|
|
264860
|
-
await mkdir12(
|
|
265316
|
+
await mkdir12(join41(homedir8(), ".ralph"), { recursive: true }).catch(() => {
|
|
264861
265317
|
return;
|
|
264862
265318
|
});
|
|
264863
265319
|
const fileSink = createJsonLogFileSink(args.jsonLogFile);
|
|
264864
265320
|
const emit3 = makeEmit(fileSink);
|
|
264865
|
-
const cfgPath = await ensureRalphyConfig(projectRoot);
|
|
264866
|
-
const cfg = await loadRalphyConfig(projectRoot);
|
|
265321
|
+
const cfgPath = await ensureRalphyConfig(projectRoot, args.workflowFile);
|
|
265322
|
+
const cfg = await loadRalphyConfig(projectRoot, args.workflowFile);
|
|
264867
265323
|
await writeAgentRunState({
|
|
264868
265324
|
projectRoot,
|
|
264869
265325
|
configPath: cfgPath,
|
|
@@ -265020,7 +265476,7 @@ async function runAgentJson({
|
|
|
265020
265476
|
}
|
|
265021
265477
|
})();
|
|
265022
265478
|
}, 1000);
|
|
265023
|
-
await new Promise((
|
|
265479
|
+
await new Promise((resolve4) => {
|
|
265024
265480
|
let shuttingDown = false;
|
|
265025
265481
|
const onSig = () => {
|
|
265026
265482
|
if (shuttingDown) {
|
|
@@ -265050,7 +265506,7 @@ async function runAgentJson({
|
|
|
265050
265506
|
});
|
|
265051
265507
|
setTimeout(() => process.exit(1), 50);
|
|
265052
265508
|
}
|
|
265053
|
-
}).then(() =>
|
|
265509
|
+
}).then(() => resolve4());
|
|
265054
265510
|
};
|
|
265055
265511
|
process.once("SIGINT", onSig);
|
|
265056
265512
|
process.once("SIGTERM", onSig);
|
|
@@ -265074,7 +265530,7 @@ __export(exports_src3, {
|
|
|
265074
265530
|
main: () => main3
|
|
265075
265531
|
});
|
|
265076
265532
|
import { mkdir as mkdir13 } from "fs/promises";
|
|
265077
|
-
import { join as
|
|
265533
|
+
import { join as join42 } from "path";
|
|
265078
265534
|
async function main3(argv) {
|
|
265079
265535
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
265080
265536
|
printAgentHelp();
|
|
@@ -265128,7 +265584,7 @@ async function main3(argv) {
|
|
|
265128
265584
|
if (args.ticketTokens.length > 0) {
|
|
265129
265585
|
const { loadRalphyConfig: loadRalphyConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
|
|
265130
265586
|
const { resolveTicketNumbers: resolveTicketNumbers2, formatTicketError: formatTicketError2 } = await Promise.resolve().then(() => (init_linear_client(), exports_linear_client));
|
|
265131
|
-
const cfg = await loadRalphyConfig2(projectRoot);
|
|
265587
|
+
const cfg = await loadRalphyConfig2(projectRoot, args.workflowFile);
|
|
265132
265588
|
const team = args.linearTeam || cfg.linear.team;
|
|
265133
265589
|
try {
|
|
265134
265590
|
resolveTicketNumbers2(args.ticketTokens, team);
|
|
@@ -265140,7 +265596,7 @@ async function main3(argv) {
|
|
|
265140
265596
|
}
|
|
265141
265597
|
await mkdir13(statesDir, { recursive: true });
|
|
265142
265598
|
await mkdir13(tasksDir, { recursive: true });
|
|
265143
|
-
await mkdir13(
|
|
265599
|
+
await mkdir13(join42(projectRoot, ".ralph"), { recursive: true });
|
|
265144
265600
|
if (shouldFallbackToJsonOutput(args, process.stdin.isTTY)) {
|
|
265145
265601
|
process.stderr.write(`agent: stdin is not a TTY \u2014 falling back to --json-output mode.
|
|
265146
265602
|
`);
|
|
@@ -265198,6 +265654,7 @@ var init_src8 = __esm(async () => {
|
|
|
265198
265654
|
init_src();
|
|
265199
265655
|
init_src2();
|
|
265200
265656
|
init_version();
|
|
265657
|
+
init_common_args();
|
|
265201
265658
|
if (typeof globalThis.Bun === "undefined") {
|
|
265202
265659
|
process.stderr.write(`ralphy requires the Bun runtime (https://bun.sh/). It is not compatible with plain Node.js.
|
|
265203
265660
|
` + "Install Bun and re-run with `bun` or `bunx ralphy`.\n");
|
|
@@ -265282,7 +265739,8 @@ ${HELP}
|
|
|
265282
265739
|
if (shouldOfferSetup(subcommand, argv.slice(1))) {
|
|
265283
265740
|
try {
|
|
265284
265741
|
const { maybeRunSetupWizard: maybeRunSetupWizard2 } = await init_src4().then(() => exports_src);
|
|
265285
|
-
|
|
265742
|
+
const { projectRoot, workflowFile } = parseWorkflowPathArgs(argv.slice(1));
|
|
265743
|
+
await maybeRunSetupWizard2(projectRoot, workflowFile);
|
|
265286
265744
|
} catch (setupErr) {
|
|
265287
265745
|
captureError("setup_wizard_error", setupErr, { subcommand });
|
|
265288
265746
|
}
|