@tarcisiopgs/lisa 1.2.1 → 1.3.0
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/index.js +563 -153
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -498,6 +498,21 @@ function registerCleanup() {
|
|
|
498
498
|
});
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
+
// src/pr-body.ts
|
|
502
|
+
function sanitizePrBody(raw) {
|
|
503
|
+
let text2 = raw.trim();
|
|
504
|
+
if (!text2) return "";
|
|
505
|
+
text2 = text2.replace(/<[^>]*>/g, "");
|
|
506
|
+
text2 = text2.replace(/^(\s*)\* /gm, "$1- ");
|
|
507
|
+
if (!text2.includes("\n")) {
|
|
508
|
+
const sentences = text2.match(/[^.!?]+[.!?]+/g);
|
|
509
|
+
if (sentences && sentences.length > 1) {
|
|
510
|
+
text2 = sentences.map((s) => `- ${s.trim()}`).join("\n");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return text2.trim();
|
|
514
|
+
}
|
|
515
|
+
|
|
501
516
|
// src/prompt.ts
|
|
502
517
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
503
518
|
import { join, resolve as resolve3 } from "path";
|
|
@@ -564,98 +579,17 @@ Do NOT update README.md for:
|
|
|
564
579
|
If an update is needed, keep the existing README style and structure. Include the README change in the same commit as the implementation.
|
|
565
580
|
`;
|
|
566
581
|
}
|
|
567
|
-
function
|
|
568
|
-
|
|
569
|
-
const repoBlock = config2.repos.map((r) => {
|
|
570
|
-
const absPath = resolve3(workspace, r.path);
|
|
571
|
-
return [
|
|
572
|
-
`- **${r.name}**: \`${absPath}\``,
|
|
573
|
-
` - Base branch: \`${r.base_branch}\``,
|
|
574
|
-
` - Worktrees dir: \`${join(absPath, ".worktrees")}\``
|
|
575
|
-
].join("\n");
|
|
576
|
-
}).join("\n\n");
|
|
577
|
-
const readmeBlock = buildReadmeInstructions();
|
|
578
|
-
const hookBlock = buildPreCommitHookInstructions();
|
|
579
|
-
const manifestPath = join(workspace, ".lisa-manifest.json");
|
|
580
|
-
return `You are an autonomous implementation agent working in a multi-repository workspace.
|
|
581
|
-
Your job is to determine the correct repository, create an English-named branch, implement the issue, commit, and write a manifest file.
|
|
582
|
-
|
|
583
|
-
You are in the workspace: \`${workspace}\`
|
|
584
|
-
|
|
585
|
-
## Issue
|
|
586
|
-
|
|
587
|
-
- **ID:** ${issue.id}
|
|
588
|
-
- **Title:** ${issue.title}
|
|
589
|
-
- **URL:** ${issue.url}
|
|
590
|
-
|
|
591
|
-
### Description
|
|
592
|
-
|
|
593
|
-
${issue.description}
|
|
594
|
-
|
|
595
|
-
## Available Repositories
|
|
596
|
-
|
|
597
|
-
${repoBlock}
|
|
598
|
-
|
|
599
|
-
## Instructions
|
|
600
|
-
|
|
601
|
-
1. **Identify the correct repository**: Read the issue title and description carefully.
|
|
602
|
-
Determine which single repository above is the right target. Consider:
|
|
603
|
-
- File paths or module names mentioned in the description
|
|
604
|
-
- Technologies and frameworks referenced
|
|
605
|
-
- The nature of the change (e.g., API endpoint \u2192 api repo, UI component \u2192 frontend repo)
|
|
606
|
-
|
|
607
|
-
2. **Choose an English branch name**: Create a slug in English following:
|
|
608
|
-
\`feat/${issue.id.toLowerCase()}-short-english-description\`
|
|
609
|
-
The description part MUST be in English regardless of the issue title language.
|
|
610
|
-
Example: for "${issue.id} Implementar rate limiting na API" \u2192 \`feat/${issue.id.toLowerCase()}-add-rate-limiting-to-api\`
|
|
611
|
-
|
|
612
|
-
3. **Set up the worktree**: In the chosen repo, run:
|
|
613
|
-
\`\`\`
|
|
614
|
-
git fetch origin <base_branch>
|
|
615
|
-
git worktree add -b <your-english-branch> <repoPath>/.worktrees/<your-english-branch> origin/<base_branch>
|
|
616
|
-
cd <repoPath>/.worktrees/<your-english-branch>
|
|
582
|
+
function buildPrBodyInstructions() {
|
|
583
|
+
return `The \`prBody\` MUST follow this exact markdown structure:
|
|
617
584
|
\`\`\`
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
-
|
|
624
|
-
${readmeBlock}${hookBlock}
|
|
625
|
-
5. **Validate**: Run the project's linter/typecheck/tests if available:
|
|
626
|
-
- Check \`package.json\` for lint, typecheck, check, or test scripts.
|
|
627
|
-
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`, \`npm run test\`).
|
|
628
|
-
- Fix any errors before proceeding.
|
|
629
|
-
|
|
630
|
-
6. **Commit (do NOT push)**: Make atomic commits with conventional commit messages.
|
|
631
|
-
Do NOT run \`git push\` \u2014 the caller handles pushing.
|
|
632
|
-
**IMPORTANT \u2014 Language rules:**
|
|
633
|
-
- All commit messages MUST be in English.
|
|
634
|
-
- Use conventional commits format: \`feat: ...\`, \`fix: ...\`, \`refactor: ...\`, \`chore: ...\`
|
|
635
|
-
|
|
636
|
-
7. **Write the manifest**: After committing, create \`${manifestPath}\` with JSON:
|
|
637
|
-
\`\`\`json
|
|
638
|
-
{
|
|
639
|
-
"repoPath": "<absolute path to the chosen repo>",
|
|
640
|
-
"branch": "<your English branch name>",
|
|
641
|
-
"prTitle": "<PR title in English, conventional commit format>",
|
|
642
|
-
"prBody": "<markdown-formatted English summary>"
|
|
643
|
-
}
|
|
585
|
+
- **What**: one-line summary of the change
|
|
586
|
+
- **Why**: motivation or issue context
|
|
587
|
+
- **Key changes**:
|
|
588
|
+
- \`src/foo.ts\` \u2014 added X functionality
|
|
589
|
+
- \`src/bar.ts\` \u2014 refactored Y to support Z
|
|
590
|
+
- **Testing**: what was validated (e.g. "all unit tests pass", "manually tested endpoint")
|
|
644
591
|
\`\`\`
|
|
645
|
-
|
|
646
|
-
Do NOT commit this file.
|
|
647
|
-
|
|
648
|
-
## Rules
|
|
649
|
-
|
|
650
|
-
- **ALL git commits, branch names, PR titles, and PR descriptions MUST be in English.**
|
|
651
|
-
- The issue description may be in any language \u2014 read it for context but write all code artifacts in English.
|
|
652
|
-
- Do NOT push \u2014 the caller handles that.
|
|
653
|
-
- Do NOT create pull requests \u2014 the caller handles that.
|
|
654
|
-
- Do NOT update the issue tracker \u2014 the caller handles that.
|
|
655
|
-
- Do NOT install new dependencies unless the issue explicitly requires it.
|
|
656
|
-
- If you get stuck or the issue is unclear, STOP and explain why.
|
|
657
|
-
- One issue only. Do not pick up additional issues.
|
|
658
|
-
- If the repo has a CLAUDE.md, read it first and follow its conventions.`;
|
|
592
|
+
Write in English. Do NOT write a wall of text \u2014 structure the summary using the template above.`;
|
|
659
593
|
}
|
|
660
594
|
function buildWorktreePrompt(issue, testRunner) {
|
|
661
595
|
const testBlock = buildTestInstructions(testRunner ?? null);
|
|
@@ -703,7 +637,7 @@ ${testBlock}${readmeBlock}${hookBlock}
|
|
|
703
637
|
\`\`\`json
|
|
704
638
|
{"branch": "<final English branch name>", "prTitle": "<English PR title, conventional commit format>", "prBody": "<markdown-formatted English summary>"}
|
|
705
639
|
\`\`\`
|
|
706
|
-
|
|
640
|
+
${buildPrBodyInstructions()}
|
|
707
641
|
Do NOT commit this file.
|
|
708
642
|
|
|
709
643
|
## Rules
|
|
@@ -773,7 +707,7 @@ ${testBlock}${readmeBlock}${hookBlock}
|
|
|
773
707
|
\`\`\`json
|
|
774
708
|
{"repoPath": "<absolute path to this repo>", "branch": "<branch name>", "prTitle": "<English PR title, conventional commit format>", "prBody": "<markdown-formatted English summary>"}
|
|
775
709
|
\`\`\`
|
|
776
|
-
|
|
710
|
+
${buildPrBodyInstructions()}
|
|
777
711
|
Do NOT commit this file.
|
|
778
712
|
|
|
779
713
|
## Rules
|
|
@@ -814,6 +748,198 @@ ${hookErrors}
|
|
|
814
748
|
|
|
815
749
|
Focus only on fixing the hook errors. Do not make unrelated changes.`;
|
|
816
750
|
}
|
|
751
|
+
function buildNativeWorktreePrompt(issue, repoPath, testRunner) {
|
|
752
|
+
const testBlock = buildTestInstructions(testRunner ?? null);
|
|
753
|
+
const readmeBlock = buildReadmeInstructions();
|
|
754
|
+
const hookBlock = buildPreCommitHookInstructions();
|
|
755
|
+
const prBodyBlock = buildPrBodyInstructions();
|
|
756
|
+
const manifestLocation = repoPath ? `\`${join(repoPath, ".lisa-manifest.json")}\`` : "`.lisa-manifest.json` in the **current directory**";
|
|
757
|
+
return `You are an autonomous implementation agent. Your job is to implement a single
|
|
758
|
+
issue, validate it, and commit.
|
|
759
|
+
|
|
760
|
+
You are working inside a git worktree that was automatically created for this task.
|
|
761
|
+
Work on the current branch \u2014 it was created for you.
|
|
762
|
+
|
|
763
|
+
## Issue
|
|
764
|
+
|
|
765
|
+
- **ID:** ${issue.id}
|
|
766
|
+
- **Title:** ${issue.title}
|
|
767
|
+
- **URL:** ${issue.url}
|
|
768
|
+
|
|
769
|
+
### Description
|
|
770
|
+
|
|
771
|
+
${issue.description}
|
|
772
|
+
|
|
773
|
+
## Instructions
|
|
774
|
+
|
|
775
|
+
1. **Implement**: Follow the issue description exactly:
|
|
776
|
+
- Read all relevant files listed in the description first (if present)
|
|
777
|
+
- Follow the implementation instructions exactly
|
|
778
|
+
- Verify each acceptance criteria (if present)
|
|
779
|
+
- Respect any stack or technical constraints (if present)
|
|
780
|
+
${testBlock}${readmeBlock}${hookBlock}
|
|
781
|
+
2. **Validate**: Run the project's linter/typecheck/tests if available:
|
|
782
|
+
- Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
|
|
783
|
+
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
784
|
+
- Fix any errors before proceeding.
|
|
785
|
+
|
|
786
|
+
3. **Commit**: Make atomic commits with conventional commit messages.
|
|
787
|
+
**Branch name must be in English.** If the current branch name contains non-English words,
|
|
788
|
+
rename it: \`git branch -m <current-name> feat/${issue.id.toLowerCase()}-short-english-slug\`
|
|
789
|
+
Do NOT push \u2014 the caller handles pushing.
|
|
790
|
+
**IMPORTANT \u2014 Language rules:**
|
|
791
|
+
- All commit messages MUST be in English.
|
|
792
|
+
- Use conventional commits format: \`feat: ...\`, \`fix: ...\`, \`refactor: ...\`, \`chore: ...\`
|
|
793
|
+
|
|
794
|
+
4. **Write manifest**: Create ${manifestLocation} with JSON:
|
|
795
|
+
\`\`\`json
|
|
796
|
+
{"branch": "<final English branch name>", "prTitle": "<English PR title, conventional commit format>", "prBody": "<markdown-formatted English summary>"}
|
|
797
|
+
\`\`\`
|
|
798
|
+
${prBodyBlock}
|
|
799
|
+
Do NOT commit this file.
|
|
800
|
+
|
|
801
|
+
## Rules
|
|
802
|
+
|
|
803
|
+
- **ALL git commits, branch names, PR titles, and PR descriptions MUST be in English.**
|
|
804
|
+
- The issue description may be in any language \u2014 read it for context but write all code artifacts in English.
|
|
805
|
+
- Do NOT push \u2014 the caller handles that.
|
|
806
|
+
- Do NOT install new dependencies unless the issue explicitly requires it.
|
|
807
|
+
- If you get stuck or the issue is unclear, STOP and explain why.
|
|
808
|
+
- One issue only. Do not pick up additional issues.
|
|
809
|
+
- If the repo has a CLAUDE.md, read it first and follow its conventions.
|
|
810
|
+
- Do NOT create pull requests \u2014 the caller handles that.
|
|
811
|
+
- Do NOT update the issue tracker \u2014 the caller handles that.`;
|
|
812
|
+
}
|
|
813
|
+
function buildPlanningPrompt(issue, config2) {
|
|
814
|
+
const workspace = resolve3(config2.workspace);
|
|
815
|
+
const repoBlock = config2.repos.map((r) => {
|
|
816
|
+
const absPath = resolve3(workspace, r.path);
|
|
817
|
+
return `- **${r.name}**: \`${absPath}\` (base branch: \`${r.base_branch}\`)`;
|
|
818
|
+
}).join("\n");
|
|
819
|
+
const planPath = join(workspace, ".lisa-plan.json");
|
|
820
|
+
return `You are an issue analysis agent. Your job is to read the issue below, determine which repositories are affected, and produce an execution plan.
|
|
821
|
+
|
|
822
|
+
**Do NOT implement anything.** Only analyze the issue and produce the plan file.
|
|
823
|
+
|
|
824
|
+
## Issue
|
|
825
|
+
|
|
826
|
+
- **ID:** ${issue.id}
|
|
827
|
+
- **Title:** ${issue.title}
|
|
828
|
+
- **URL:** ${issue.url}
|
|
829
|
+
|
|
830
|
+
### Description
|
|
831
|
+
|
|
832
|
+
${issue.description}
|
|
833
|
+
|
|
834
|
+
## Available Repositories
|
|
835
|
+
|
|
836
|
+
${repoBlock}
|
|
837
|
+
|
|
838
|
+
## Instructions
|
|
839
|
+
|
|
840
|
+
1. **Analyze the issue**: Read the title and description carefully. Determine which repositories above are affected by this change.
|
|
841
|
+
Consider:
|
|
842
|
+
- File paths or module names mentioned in the description
|
|
843
|
+
- Technologies and frameworks referenced
|
|
844
|
+
- Dependencies between repos (e.g., backend API changes needed before frontend can consume them)
|
|
845
|
+
|
|
846
|
+
2. **Determine execution order**: If multiple repos are affected, decide the order. Repos that produce APIs, schemas, or shared libraries should come first. Repos that consume them should come later.
|
|
847
|
+
|
|
848
|
+
3. **Write the plan**: Create \`${planPath}\` with JSON:
|
|
849
|
+
\`\`\`json
|
|
850
|
+
{
|
|
851
|
+
"steps": [
|
|
852
|
+
{ "repoPath": "<absolute path to repo>", "scope": "<what to implement in this repo>", "order": 1 },
|
|
853
|
+
{ "repoPath": "<absolute path to repo>", "scope": "<what to implement in this repo>", "order": 2 }
|
|
854
|
+
]
|
|
855
|
+
}
|
|
856
|
+
\`\`\`
|
|
857
|
+
|
|
858
|
+
## Rules
|
|
859
|
+
|
|
860
|
+
- Only include repos that are actually affected by the issue. Do NOT include repos that don't need changes.
|
|
861
|
+
- The \`scope\` field should be a concise English description of what needs to be done in that specific repo.
|
|
862
|
+
- Order matters: lower order numbers execute first.
|
|
863
|
+
- Do NOT implement anything. Do NOT create branches, write code, or commit.
|
|
864
|
+
- Do NOT push, create pull requests, or update the issue tracker.
|
|
865
|
+
- If only one repo is affected, the plan should have a single step.`;
|
|
866
|
+
}
|
|
867
|
+
function buildScopedImplementPrompt(issue, step, previousResults, testRunner) {
|
|
868
|
+
const testBlock = buildTestInstructions(testRunner ?? null);
|
|
869
|
+
const readmeBlock = buildReadmeInstructions();
|
|
870
|
+
const hookBlock = buildPreCommitHookInstructions();
|
|
871
|
+
const prBodyBlock = buildPrBodyInstructions();
|
|
872
|
+
const previousBlock = previousResults.length > 0 ? `
|
|
873
|
+
## Previous Steps
|
|
874
|
+
|
|
875
|
+
The following repos have already been implemented as part of this issue:
|
|
876
|
+
|
|
877
|
+
${previousResults.map((r) => `- **${r.repoPath}**: branch \`${r.branch}\`${r.prUrl ? ` \u2014 PR: ${r.prUrl}` : ""}`).join("\n")}
|
|
878
|
+
|
|
879
|
+
Use this context if the current step depends on changes from previous steps.
|
|
880
|
+
` : "";
|
|
881
|
+
return `You are an autonomous implementation agent. Your job is to implement a specific part of an issue in a single repository.
|
|
882
|
+
|
|
883
|
+
You are working inside a git worktree that was automatically created for this task.
|
|
884
|
+
Work on the current branch \u2014 it was created for you.
|
|
885
|
+
|
|
886
|
+
## Issue
|
|
887
|
+
|
|
888
|
+
- **ID:** ${issue.id}
|
|
889
|
+
- **Title:** ${issue.title}
|
|
890
|
+
- **URL:** ${issue.url}
|
|
891
|
+
|
|
892
|
+
### Description
|
|
893
|
+
|
|
894
|
+
${issue.description}
|
|
895
|
+
|
|
896
|
+
## Your Scope
|
|
897
|
+
|
|
898
|
+
You are responsible for **this specific part** of the issue:
|
|
899
|
+
|
|
900
|
+
> ${step.scope}
|
|
901
|
+
|
|
902
|
+
Focus only on this scope. Do NOT implement changes outside this scope.
|
|
903
|
+
${previousBlock}
|
|
904
|
+
## Instructions
|
|
905
|
+
|
|
906
|
+
1. **Implement**: Follow the scope above. Read the full issue description for context, but only implement what is described in "Your Scope":
|
|
907
|
+
- Read all relevant files first
|
|
908
|
+
- Follow the implementation instructions exactly
|
|
909
|
+
- Verify each acceptance criteria relevant to your scope
|
|
910
|
+
${testBlock}${readmeBlock}${hookBlock}
|
|
911
|
+
2. **Validate**: Run the project's linter/typecheck/tests if available:
|
|
912
|
+
- Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
|
|
913
|
+
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
914
|
+
- Fix any errors before proceeding.
|
|
915
|
+
|
|
916
|
+
3. **Commit**: Make atomic commits with conventional commit messages.
|
|
917
|
+
**Branch name must be in English.** If the current branch name contains non-English words,
|
|
918
|
+
rename it: \`git branch -m <current-name> feat/${issue.id.toLowerCase()}-short-english-slug\`
|
|
919
|
+
Do NOT push \u2014 the caller handles pushing.
|
|
920
|
+
**IMPORTANT \u2014 Language rules:**
|
|
921
|
+
- All commit messages MUST be in English.
|
|
922
|
+
- Use conventional commits format: \`feat: ...\`, \`fix: ...\`, \`refactor: ...\`, \`chore: ...\`
|
|
923
|
+
|
|
924
|
+
4. **Write manifest**: Create \`.lisa-manifest.json\` in the **current directory** with JSON:
|
|
925
|
+
\`\`\`json
|
|
926
|
+
{"branch": "<final English branch name>", "prTitle": "<English PR title, conventional commit format>", "prBody": "<markdown-formatted English summary>"}
|
|
927
|
+
\`\`\`
|
|
928
|
+
${prBodyBlock}
|
|
929
|
+
Do NOT commit this file.
|
|
930
|
+
|
|
931
|
+
## Rules
|
|
932
|
+
|
|
933
|
+
- **ALL git commits, branch names, PR titles, and PR descriptions MUST be in English.**
|
|
934
|
+
- The issue description may be in any language \u2014 read it for context but write all code artifacts in English.
|
|
935
|
+
- Do NOT push \u2014 the caller handles that.
|
|
936
|
+
- Do NOT install new dependencies unless the issue explicitly requires it.
|
|
937
|
+
- If you get stuck or the issue is unclear, STOP and explain why.
|
|
938
|
+
- One scope only. Do not pick up additional work outside your scope.
|
|
939
|
+
- If the repo has a CLAUDE.md, read it first and follow its conventions.
|
|
940
|
+
- Do NOT create pull requests \u2014 the caller handles that.
|
|
941
|
+
- Do NOT update the issue tracker \u2014 the caller handles that.`;
|
|
942
|
+
}
|
|
817
943
|
|
|
818
944
|
// src/guardrails.ts
|
|
819
945
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -986,6 +1112,7 @@ function startOverseer(proc, cwd, config2, getSnapshot = getGitSnapshot) {
|
|
|
986
1112
|
// src/providers/claude.ts
|
|
987
1113
|
var ClaudeProvider = class {
|
|
988
1114
|
name = "claude";
|
|
1115
|
+
supportsNativeWorktree = true;
|
|
989
1116
|
async isAvailable() {
|
|
990
1117
|
try {
|
|
991
1118
|
execSync("claude --version", { stdio: "ignore" });
|
|
@@ -1000,15 +1127,15 @@ var ClaudeProvider = class {
|
|
|
1000
1127
|
const promptFile = join3(tmpDir, "prompt.md");
|
|
1001
1128
|
writeFileSync4(promptFile, prompt, "utf-8");
|
|
1002
1129
|
try {
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
);
|
|
1130
|
+
const flags = ["-p", "--dangerously-skip-permissions"];
|
|
1131
|
+
if (opts.useNativeWorktree) {
|
|
1132
|
+
flags.push("--worktree");
|
|
1133
|
+
}
|
|
1134
|
+
const proc = spawn2("sh", ["-c", `claude ${flags.join(" ")} "$(cat '${promptFile}')"`], {
|
|
1135
|
+
cwd: opts.cwd,
|
|
1136
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1137
|
+
env: { ...process.env, CLAUDECODE: void 0 }
|
|
1138
|
+
});
|
|
1012
1139
|
const overseer = opts.overseer?.enabled ? startOverseer(proc, opts.cwd, opts.overseer) : null;
|
|
1013
1140
|
const chunks = [];
|
|
1014
1141
|
proc.stdout.on("data", (chunk) => {
|
|
@@ -1277,6 +1404,7 @@ async function runWithFallback(models, prompt, opts) {
|
|
|
1277
1404
|
output: result.output,
|
|
1278
1405
|
duration: result.duration,
|
|
1279
1406
|
providerUsed: model,
|
|
1407
|
+
provider,
|
|
1280
1408
|
attempts
|
|
1281
1409
|
};
|
|
1282
1410
|
}
|
|
@@ -1302,6 +1430,7 @@ async function runWithFallback(models, prompt, opts) {
|
|
|
1302
1430
|
output: result.output,
|
|
1303
1431
|
duration: result.duration,
|
|
1304
1432
|
providerUsed: model,
|
|
1433
|
+
provider,
|
|
1305
1434
|
attempts
|
|
1306
1435
|
};
|
|
1307
1436
|
}
|
|
@@ -1902,7 +2031,10 @@ function resolveModels(config2) {
|
|
|
1902
2031
|
function buildPrBody(providerUsed, description) {
|
|
1903
2032
|
const lines = [];
|
|
1904
2033
|
if (description) {
|
|
1905
|
-
|
|
2034
|
+
const sanitized = sanitizePrBody(description);
|
|
2035
|
+
if (sanitized) {
|
|
2036
|
+
lines.push("## Summary", "", sanitized, "");
|
|
2037
|
+
}
|
|
1906
2038
|
}
|
|
1907
2039
|
lines.push(
|
|
1908
2040
|
"---",
|
|
@@ -1926,6 +2058,22 @@ function cleanupPrTitle(cwd) {
|
|
|
1926
2058
|
} catch {
|
|
1927
2059
|
}
|
|
1928
2060
|
}
|
|
2061
|
+
var PLAN_FILE = ".lisa-plan.json";
|
|
2062
|
+
function readLisaPlan(dir) {
|
|
2063
|
+
const planPath = join7(dir, PLAN_FILE);
|
|
2064
|
+
if (!existsSync6(planPath)) return null;
|
|
2065
|
+
try {
|
|
2066
|
+
return JSON.parse(readFileSync5(planPath, "utf-8").trim());
|
|
2067
|
+
} catch {
|
|
2068
|
+
return null;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
function cleanupPlan(dir) {
|
|
2072
|
+
try {
|
|
2073
|
+
unlinkSync4(join7(dir, PLAN_FILE));
|
|
2074
|
+
} catch {
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
1929
2077
|
var MANIFEST_FILE = ".lisa-manifest.json";
|
|
1930
2078
|
function readLisaManifest(dir) {
|
|
1931
2079
|
const manifestPath = join7(dir, MANIFEST_FILE);
|
|
@@ -2271,6 +2419,24 @@ async function runTestValidation(cwd) {
|
|
|
2271
2419
|
return false;
|
|
2272
2420
|
}
|
|
2273
2421
|
}
|
|
2422
|
+
async function findWorktreeForBranch(repoRoot, branch) {
|
|
2423
|
+
try {
|
|
2424
|
+
const { stdout } = await execa3("git", ["worktree", "list", "--porcelain"], { cwd: repoRoot });
|
|
2425
|
+
const lines = stdout.split("\n");
|
|
2426
|
+
let currentPath = null;
|
|
2427
|
+
for (const line of lines) {
|
|
2428
|
+
if (line.startsWith("worktree ")) {
|
|
2429
|
+
currentPath = line.slice("worktree ".length);
|
|
2430
|
+
}
|
|
2431
|
+
if (line.startsWith("branch ") && line.endsWith(`/${branch}`)) {
|
|
2432
|
+
return currentPath;
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return null;
|
|
2436
|
+
} catch {
|
|
2437
|
+
return null;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2274
2440
|
async function runWorktreeSession(config2, issue, logFile, session, models) {
|
|
2275
2441
|
if (config2.repos.length > 1) {
|
|
2276
2442
|
return runWorktreeMultiRepoSession(config2, issue, logFile, session, models);
|
|
@@ -2278,6 +2444,140 @@ async function runWorktreeSession(config2, issue, logFile, session, models) {
|
|
|
2278
2444
|
const workspace = resolve5(config2.workspace);
|
|
2279
2445
|
const repoPath = determineRepoPath(config2.repos, issue, workspace) ?? workspace;
|
|
2280
2446
|
const defaultBranch = resolveBaseBranch(config2, repoPath);
|
|
2447
|
+
const primaryProvider = createProvider(models[0] ?? "claude");
|
|
2448
|
+
const useNativeWorktree = primaryProvider.supportsNativeWorktree === true;
|
|
2449
|
+
if (useNativeWorktree) {
|
|
2450
|
+
return runNativeWorktreeSession(
|
|
2451
|
+
config2,
|
|
2452
|
+
issue,
|
|
2453
|
+
logFile,
|
|
2454
|
+
session,
|
|
2455
|
+
models,
|
|
2456
|
+
repoPath,
|
|
2457
|
+
defaultBranch
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
return runManualWorktreeSession(config2, issue, logFile, session, models, repoPath, defaultBranch);
|
|
2461
|
+
}
|
|
2462
|
+
async function runNativeWorktreeSession(config2, issue, logFile, session, models, repoPath, defaultBranch) {
|
|
2463
|
+
const failResult = (providerUsed, fallback) => ({
|
|
2464
|
+
success: false,
|
|
2465
|
+
providerUsed,
|
|
2466
|
+
prUrls: [],
|
|
2467
|
+
fallback: fallback ?? { success: false, output: "", duration: 0, providerUsed, attempts: [] }
|
|
2468
|
+
});
|
|
2469
|
+
const repo = findRepoConfig(config2, issue);
|
|
2470
|
+
if (repo?.lifecycle) {
|
|
2471
|
+
startSpinner(`${issue.id} \u2014 starting resources...`);
|
|
2472
|
+
const started = await startResources(repo, repoPath);
|
|
2473
|
+
stopSpinner();
|
|
2474
|
+
if (!started) {
|
|
2475
|
+
error(`Lifecycle startup failed for ${issue.id}. Aborting session.`);
|
|
2476
|
+
return failResult(models[0] ?? "claude");
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
const testRunner = detectTestRunner(repoPath);
|
|
2480
|
+
if (testRunner) log(`Detected test runner: ${testRunner}`);
|
|
2481
|
+
cleanupManifest(repoPath);
|
|
2482
|
+
const prompt = buildNativeWorktreePrompt(issue, repoPath, testRunner);
|
|
2483
|
+
startSpinner(`${issue.id} \u2014 implementing (native worktree)...`);
|
|
2484
|
+
log(`Implementing with native worktree... (log: ${logFile})`);
|
|
2485
|
+
initLogFile(logFile);
|
|
2486
|
+
const result = await runWithFallback(models, prompt, {
|
|
2487
|
+
logFile,
|
|
2488
|
+
cwd: repoPath,
|
|
2489
|
+
guardrailsDir: repoPath,
|
|
2490
|
+
issueId: issue.id,
|
|
2491
|
+
overseer: config2.overseer,
|
|
2492
|
+
useNativeWorktree: true
|
|
2493
|
+
});
|
|
2494
|
+
stopSpinner();
|
|
2495
|
+
try {
|
|
2496
|
+
appendFileSync6(
|
|
2497
|
+
logFile,
|
|
2498
|
+
`
|
|
2499
|
+
${"=".repeat(80)}
|
|
2500
|
+
Provider used: ${result.providerUsed}
|
|
2501
|
+
Full output:
|
|
2502
|
+
${result.output}
|
|
2503
|
+
`
|
|
2504
|
+
);
|
|
2505
|
+
} catch {
|
|
2506
|
+
}
|
|
2507
|
+
if (repo?.lifecycle) await stopResources();
|
|
2508
|
+
if (!result.success) {
|
|
2509
|
+
error(`Session ${session} failed for ${issue.id}. Check ${logFile}`);
|
|
2510
|
+
cleanupManifest(repoPath);
|
|
2511
|
+
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2512
|
+
}
|
|
2513
|
+
const manifest = readLisaManifest(repoPath);
|
|
2514
|
+
if (!manifest?.branch) {
|
|
2515
|
+
error(`Agent did not produce a valid .lisa-manifest.json for ${issue.id}. Aborting.`);
|
|
2516
|
+
cleanupManifest(repoPath);
|
|
2517
|
+
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2518
|
+
}
|
|
2519
|
+
const effectiveBranch = manifest.branch;
|
|
2520
|
+
ok(`Agent created branch: ${effectiveBranch}`);
|
|
2521
|
+
const worktreePath = await findWorktreeForBranch(repoPath, effectiveBranch);
|
|
2522
|
+
const effectiveCwd = worktreePath ?? repoPath;
|
|
2523
|
+
if (!worktreePath) {
|
|
2524
|
+
warn(`No worktree found for branch ${effectiveBranch} \u2014 using repo root`);
|
|
2525
|
+
}
|
|
2526
|
+
startSpinner(`${issue.id} \u2014 validating tests...`);
|
|
2527
|
+
const testsPassed = await runTestValidation(effectiveCwd);
|
|
2528
|
+
stopSpinner();
|
|
2529
|
+
if (!testsPassed) {
|
|
2530
|
+
error(`Tests failed for ${issue.id}. Blocking PR creation.`);
|
|
2531
|
+
cleanupManifest(repoPath);
|
|
2532
|
+
if (worktreePath) await cleanupWorktree(repoPath, worktreePath);
|
|
2533
|
+
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2534
|
+
}
|
|
2535
|
+
startSpinner(`${issue.id} \u2014 pushing...`);
|
|
2536
|
+
const pushResult = await pushWithRecovery({
|
|
2537
|
+
branch: effectiveBranch,
|
|
2538
|
+
cwd: effectiveCwd,
|
|
2539
|
+
models,
|
|
2540
|
+
logFile,
|
|
2541
|
+
guardrailsDir: repoPath,
|
|
2542
|
+
issueId: issue.id,
|
|
2543
|
+
overseer: config2.overseer
|
|
2544
|
+
});
|
|
2545
|
+
stopSpinner();
|
|
2546
|
+
if (!pushResult.success) {
|
|
2547
|
+
error(`Failed to push branch to remote: ${pushResult.error}`);
|
|
2548
|
+
cleanupManifest(repoPath);
|
|
2549
|
+
if (worktreePath) await cleanupWorktree(repoPath, worktreePath);
|
|
2550
|
+
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2551
|
+
}
|
|
2552
|
+
startSpinner(`${issue.id} \u2014 creating PR...`);
|
|
2553
|
+
const prTitle = manifest.prTitle ?? issue.title;
|
|
2554
|
+
const prBody = manifest.prBody;
|
|
2555
|
+
cleanupManifest(repoPath);
|
|
2556
|
+
const prUrls = [];
|
|
2557
|
+
try {
|
|
2558
|
+
const repoInfo = await getRepoInfo(effectiveCwd);
|
|
2559
|
+
const pr = await createPullRequest(
|
|
2560
|
+
{
|
|
2561
|
+
owner: repoInfo.owner,
|
|
2562
|
+
repo: repoInfo.repo,
|
|
2563
|
+
head: effectiveBranch,
|
|
2564
|
+
base: defaultBranch,
|
|
2565
|
+
title: prTitle,
|
|
2566
|
+
body: buildPrBody(result.providerUsed, prBody)
|
|
2567
|
+
},
|
|
2568
|
+
config2.github
|
|
2569
|
+
);
|
|
2570
|
+
ok(`PR created: ${pr.html_url}`);
|
|
2571
|
+
prUrls.push(pr.html_url);
|
|
2572
|
+
} catch (err) {
|
|
2573
|
+
error(`Failed to create PR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2574
|
+
}
|
|
2575
|
+
stopSpinner();
|
|
2576
|
+
if (worktreePath) await cleanupWorktree(repoPath, worktreePath);
|
|
2577
|
+
ok(`Session ${session} complete for ${issue.id}`);
|
|
2578
|
+
return { success: true, providerUsed: result.providerUsed, prUrls, fallback: result };
|
|
2579
|
+
}
|
|
2580
|
+
async function runManualWorktreeSession(config2, issue, logFile, session, models, repoPath, defaultBranch) {
|
|
2281
2581
|
const branchName = generateBranchName(issue.id, issue.title);
|
|
2282
2582
|
startSpinner(`${issue.id} \u2014 creating worktree...`);
|
|
2283
2583
|
log(`Creating worktree for ${branchName} (base: ${defaultBranch})...`);
|
|
@@ -2431,12 +2731,12 @@ ${result.output}
|
|
|
2431
2731
|
async function runWorktreeMultiRepoSession(config2, issue, logFile, session, models) {
|
|
2432
2732
|
const workspace = resolve5(config2.workspace);
|
|
2433
2733
|
cleanupManifest(workspace);
|
|
2434
|
-
|
|
2435
|
-
startSpinner(`${issue.id} \u2014
|
|
2436
|
-
log(`Multi-repo
|
|
2437
|
-
log(`Implementing (agent selects repo)... (log: ${logFile})`);
|
|
2734
|
+
cleanupPlan(workspace);
|
|
2735
|
+
startSpinner(`${issue.id} \u2014 analyzing issue...`);
|
|
2736
|
+
log(`Multi-repo planning phase for ${issue.id}`);
|
|
2438
2737
|
initLogFile(logFile);
|
|
2439
|
-
const
|
|
2738
|
+
const planPrompt = buildPlanningPrompt(issue, config2);
|
|
2739
|
+
const planResult = await runWithFallback(models, planPrompt, {
|
|
2440
2740
|
logFile,
|
|
2441
2741
|
cwd: workspace,
|
|
2442
2742
|
guardrailsDir: workspace,
|
|
@@ -2449,87 +2749,197 @@ async function runWorktreeMultiRepoSession(config2, issue, logFile, session, mod
|
|
|
2449
2749
|
logFile,
|
|
2450
2750
|
`
|
|
2451
2751
|
${"=".repeat(80)}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
${result.output}
|
|
2752
|
+
Planning phase \u2014 provider: ${planResult.providerUsed}
|
|
2753
|
+
${planResult.output}
|
|
2455
2754
|
`
|
|
2456
2755
|
);
|
|
2457
2756
|
} catch {
|
|
2458
2757
|
}
|
|
2459
|
-
if (!
|
|
2460
|
-
error(`
|
|
2461
|
-
|
|
2462
|
-
return {
|
|
2758
|
+
if (!planResult.success) {
|
|
2759
|
+
error(`Planning phase failed for ${issue.id}. Check ${logFile}`);
|
|
2760
|
+
cleanupPlan(workspace);
|
|
2761
|
+
return {
|
|
2762
|
+
success: false,
|
|
2763
|
+
providerUsed: planResult.providerUsed,
|
|
2764
|
+
prUrls: [],
|
|
2765
|
+
fallback: planResult
|
|
2766
|
+
};
|
|
2463
2767
|
}
|
|
2464
|
-
const
|
|
2465
|
-
if (!
|
|
2466
|
-
error(
|
|
2467
|
-
|
|
2768
|
+
const plan = readLisaPlan(workspace);
|
|
2769
|
+
if (!plan?.steps || plan.steps.length === 0) {
|
|
2770
|
+
error(`Agent did not produce a valid .lisa-plan.json for ${issue.id}. Aborting.`);
|
|
2771
|
+
cleanupPlan(workspace);
|
|
2772
|
+
return {
|
|
2773
|
+
success: false,
|
|
2774
|
+
providerUsed: planResult.providerUsed,
|
|
2775
|
+
prUrls: [],
|
|
2776
|
+
fallback: planResult
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
const sortedSteps = [...plan.steps].sort((a, b) => a.order - b.order);
|
|
2780
|
+
ok(
|
|
2781
|
+
`Plan produced ${sortedSteps.length} step(s): ${sortedSteps.map((s) => s.repoPath).join(" \u2192 ")}`
|
|
2782
|
+
);
|
|
2783
|
+
cleanupPlan(workspace);
|
|
2784
|
+
const prUrls = [];
|
|
2785
|
+
const previousResults = [];
|
|
2786
|
+
let lastFallback = planResult;
|
|
2787
|
+
let lastProvider = planResult.providerUsed;
|
|
2788
|
+
for (const [i, step] of sortedSteps.entries()) {
|
|
2789
|
+
const stepNum = i + 1;
|
|
2790
|
+
divider(stepNum);
|
|
2791
|
+
log(`Step ${stepNum}/${sortedSteps.length}: ${step.repoPath} \u2014 ${step.scope}`);
|
|
2792
|
+
const stepResult = await runMultiRepoStep(
|
|
2793
|
+
config2,
|
|
2794
|
+
issue,
|
|
2795
|
+
step,
|
|
2796
|
+
previousResults,
|
|
2797
|
+
logFile,
|
|
2798
|
+
models,
|
|
2799
|
+
stepNum
|
|
2468
2800
|
);
|
|
2469
|
-
|
|
2470
|
-
|
|
2801
|
+
lastFallback = stepResult.fallback;
|
|
2802
|
+
lastProvider = stepResult.providerUsed;
|
|
2803
|
+
if (!stepResult.success) {
|
|
2804
|
+
error(`Step ${stepNum} failed for ${step.repoPath}. Aborting remaining steps.`);
|
|
2805
|
+
return {
|
|
2806
|
+
success: false,
|
|
2807
|
+
providerUsed: lastProvider,
|
|
2808
|
+
prUrls,
|
|
2809
|
+
fallback: lastFallback
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
if (stepResult.prUrl) {
|
|
2813
|
+
prUrls.push(stepResult.prUrl);
|
|
2814
|
+
}
|
|
2815
|
+
previousResults.push({
|
|
2816
|
+
repoPath: step.repoPath,
|
|
2817
|
+
branch: stepResult.branch,
|
|
2818
|
+
prUrl: stepResult.prUrl
|
|
2819
|
+
});
|
|
2471
2820
|
}
|
|
2472
|
-
ok(`
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
const
|
|
2477
|
-
|
|
2478
|
-
|
|
2821
|
+
ok(`Session ${session} complete for ${issue.id} \u2014 ${prUrls.length} PR(s) created`);
|
|
2822
|
+
return { success: true, providerUsed: lastProvider, prUrls, fallback: lastFallback };
|
|
2823
|
+
}
|
|
2824
|
+
async function runMultiRepoStep(config2, issue, step, previousResults, logFile, models, stepNum) {
|
|
2825
|
+
const repoPath = step.repoPath;
|
|
2826
|
+
const defaultBranch = resolveBaseBranch(config2, repoPath);
|
|
2827
|
+
const branchName = generateBranchName(issue.id, issue.title);
|
|
2828
|
+
const failResult = (providerUsed, fallback) => ({
|
|
2829
|
+
success: false,
|
|
2830
|
+
providerUsed,
|
|
2831
|
+
branch: branchName,
|
|
2832
|
+
fallback: fallback ?? { success: false, output: "", duration: 0, providerUsed, attempts: [] }
|
|
2833
|
+
});
|
|
2834
|
+
startSpinner(`${issue.id} step ${stepNum} \u2014 creating worktree...`);
|
|
2835
|
+
let worktreePath;
|
|
2836
|
+
try {
|
|
2837
|
+
worktreePath = await createWorktree(repoPath, branchName, defaultBranch);
|
|
2838
|
+
} catch (err) {
|
|
2839
|
+
stopSpinner();
|
|
2840
|
+
error(`Failed to create worktree: ${err instanceof Error ? err.message : String(err)}`);
|
|
2841
|
+
return failResult(models[0] ?? "claude");
|
|
2479
2842
|
}
|
|
2480
|
-
|
|
2481
|
-
|
|
2843
|
+
stopSpinner();
|
|
2844
|
+
ok(`Worktree created at ${worktreePath}`);
|
|
2845
|
+
const testRunner = detectTestRunner(worktreePath);
|
|
2846
|
+
if (testRunner) log(`Detected test runner: ${testRunner}`);
|
|
2847
|
+
const prompt = buildScopedImplementPrompt(issue, step, previousResults, testRunner);
|
|
2848
|
+
startSpinner(`${issue.id} step ${stepNum} \u2014 implementing...`);
|
|
2849
|
+
const result = await runWithFallback(models, prompt, {
|
|
2850
|
+
logFile,
|
|
2851
|
+
cwd: worktreePath,
|
|
2852
|
+
guardrailsDir: repoPath,
|
|
2853
|
+
issueId: issue.id,
|
|
2854
|
+
overseer: config2.overseer
|
|
2855
|
+
});
|
|
2856
|
+
stopSpinner();
|
|
2857
|
+
try {
|
|
2858
|
+
appendFileSync6(
|
|
2859
|
+
logFile,
|
|
2860
|
+
`
|
|
2861
|
+
${"=".repeat(80)}
|
|
2862
|
+
Step ${stepNum} \u2014 provider: ${result.providerUsed}
|
|
2863
|
+
${result.output}
|
|
2864
|
+
`
|
|
2865
|
+
);
|
|
2866
|
+
} catch {
|
|
2867
|
+
}
|
|
2868
|
+
if (!result.success) {
|
|
2869
|
+
error(`Step ${stepNum} implementation failed. Check ${logFile}`);
|
|
2870
|
+
await cleanupWorktree(repoPath, worktreePath);
|
|
2871
|
+
return { ...failResult(result.providerUsed, result), branch: branchName };
|
|
2872
|
+
}
|
|
2873
|
+
const manifest = readLisaManifest(worktreePath);
|
|
2874
|
+
let effectiveBranch = branchName;
|
|
2875
|
+
if (manifest?.branch && manifest.branch !== branchName) {
|
|
2876
|
+
log(`Renaming branch to: ${manifest.branch}`);
|
|
2877
|
+
try {
|
|
2878
|
+
await execa3("git", ["branch", "-m", branchName, manifest.branch], { cwd: worktreePath });
|
|
2879
|
+
effectiveBranch = manifest.branch;
|
|
2880
|
+
} catch (err) {
|
|
2881
|
+
warn(`Branch rename failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
startSpinner(`${issue.id} step ${stepNum} \u2014 validating tests...`);
|
|
2885
|
+
const testsPassed = await runTestValidation(worktreePath);
|
|
2482
2886
|
stopSpinner();
|
|
2483
2887
|
if (!testsPassed) {
|
|
2484
|
-
error(`Tests failed for ${
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
return {
|
|
2888
|
+
error(`Tests failed for step ${stepNum}. Blocking PR creation.`);
|
|
2889
|
+
cleanupManifest(worktreePath);
|
|
2890
|
+
await cleanupWorktree(repoPath, worktreePath);
|
|
2891
|
+
return { ...failResult(result.providerUsed, result), branch: effectiveBranch };
|
|
2488
2892
|
}
|
|
2489
|
-
startSpinner(`${issue.id} \u2014 pushing...`);
|
|
2893
|
+
startSpinner(`${issue.id} step ${stepNum} \u2014 pushing...`);
|
|
2490
2894
|
const pushResult = await pushWithRecovery({
|
|
2491
|
-
branch:
|
|
2492
|
-
cwd:
|
|
2895
|
+
branch: effectiveBranch,
|
|
2896
|
+
cwd: worktreePath,
|
|
2493
2897
|
models,
|
|
2494
2898
|
logFile,
|
|
2495
|
-
guardrailsDir:
|
|
2899
|
+
guardrailsDir: repoPath,
|
|
2496
2900
|
issueId: issue.id,
|
|
2497
2901
|
overseer: config2.overseer
|
|
2498
2902
|
});
|
|
2499
2903
|
stopSpinner();
|
|
2500
2904
|
if (!pushResult.success) {
|
|
2501
|
-
error(`Failed to push
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
return {
|
|
2905
|
+
error(`Failed to push step ${stepNum}: ${pushResult.error}`);
|
|
2906
|
+
cleanupManifest(worktreePath);
|
|
2907
|
+
await cleanupWorktree(repoPath, worktreePath);
|
|
2908
|
+
return { ...failResult(result.providerUsed, result), branch: effectiveBranch };
|
|
2505
2909
|
}
|
|
2506
|
-
startSpinner(`${issue.id} \u2014 creating PR...`);
|
|
2507
|
-
const prTitle = manifest
|
|
2508
|
-
const prBody = manifest
|
|
2509
|
-
|
|
2910
|
+
startSpinner(`${issue.id} step ${stepNum} \u2014 creating PR...`);
|
|
2911
|
+
const prTitle = manifest?.prTitle ?? issue.title;
|
|
2912
|
+
const prBody = manifest?.prBody;
|
|
2913
|
+
cleanupManifest(worktreePath);
|
|
2914
|
+
let prUrl;
|
|
2510
2915
|
try {
|
|
2511
|
-
const repoInfo = await getRepoInfo(
|
|
2916
|
+
const repoInfo = await getRepoInfo(worktreePath);
|
|
2512
2917
|
const pr = await createPullRequest(
|
|
2513
2918
|
{
|
|
2514
2919
|
owner: repoInfo.owner,
|
|
2515
2920
|
repo: repoInfo.repo,
|
|
2516
|
-
head:
|
|
2517
|
-
base:
|
|
2921
|
+
head: effectiveBranch,
|
|
2922
|
+
base: defaultBranch,
|
|
2518
2923
|
title: prTitle,
|
|
2519
2924
|
body: buildPrBody(result.providerUsed, prBody)
|
|
2520
2925
|
},
|
|
2521
2926
|
config2.github
|
|
2522
2927
|
);
|
|
2523
2928
|
ok(`PR created: ${pr.html_url}`);
|
|
2524
|
-
|
|
2929
|
+
prUrl = pr.html_url;
|
|
2525
2930
|
} catch (err) {
|
|
2526
2931
|
error(`Failed to create PR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2527
2932
|
}
|
|
2528
2933
|
stopSpinner();
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2934
|
+
await cleanupWorktree(repoPath, worktreePath);
|
|
2935
|
+
ok(`Step ${stepNum} complete: ${repoPath}`);
|
|
2936
|
+
return {
|
|
2937
|
+
success: true,
|
|
2938
|
+
providerUsed: result.providerUsed,
|
|
2939
|
+
branch: effectiveBranch,
|
|
2940
|
+
prUrl,
|
|
2941
|
+
fallback: result
|
|
2942
|
+
};
|
|
2533
2943
|
}
|
|
2534
2944
|
async function runBranchSession(config2, issue, logFile, session, models) {
|
|
2535
2945
|
const workspace = resolve5(config2.workspace);
|