@tarcisiopgs/lisa 0.9.4 → 0.9.6
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 +126 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -531,6 +531,16 @@ This project uses **${testRunner}** as its test runner.
|
|
|
531
531
|
- Do NOT skip writing tests \u2014 the PR will be blocked if tests are missing or failing.
|
|
532
532
|
`;
|
|
533
533
|
}
|
|
534
|
+
function buildPreCommitHookInstructions() {
|
|
535
|
+
return `
|
|
536
|
+
**Pre-commit hooks:**
|
|
537
|
+
If \`git commit\` fails due to a pre-commit hook (e.g. husky), read the error output carefully and fix the underlying issue:
|
|
538
|
+
- Linter/formatter failures \u2192 run the project's lint/format commands, then re-stage and retry the commit.
|
|
539
|
+
- Code generation errors (e.g. stale Prisma client) \u2192 run the required generation command (e.g. \`npx prisma generate\`), then re-stage and retry.
|
|
540
|
+
- Type errors \u2192 fix the type issues in the source files, then re-stage and retry.
|
|
541
|
+
Do NOT skip or bypass hooks (no \`--no-verify\`). Fix the root cause and retry.
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
534
544
|
function buildReadmeInstructions() {
|
|
535
545
|
return `
|
|
536
546
|
**README.md Evaluation:**
|
|
@@ -565,6 +575,7 @@ function buildWorktreeMultiRepoPrompt(issue, config2) {
|
|
|
565
575
|
].join("\n");
|
|
566
576
|
}).join("\n\n");
|
|
567
577
|
const readmeBlock = buildReadmeInstructions();
|
|
578
|
+
const hookBlock = buildPreCommitHookInstructions();
|
|
568
579
|
const manifestPath = join(workspace, ".lisa-manifest.json");
|
|
569
580
|
return `You are an autonomous implementation agent working in a multi-repository workspace.
|
|
570
581
|
Your job is to determine the correct repository, create an English-named branch, implement the issue, commit, and write a manifest file.
|
|
@@ -610,7 +621,7 @@ ${repoBlock}
|
|
|
610
621
|
- Follow the implementation instructions exactly
|
|
611
622
|
- Verify each acceptance criteria (if present)
|
|
612
623
|
- Respect any stack or technical constraints (if present)
|
|
613
|
-
${readmeBlock}
|
|
624
|
+
${readmeBlock}${hookBlock}
|
|
614
625
|
5. **Validate**: Run the project's linter/typecheck/tests if available:
|
|
615
626
|
- Check \`package.json\` for lint, typecheck, check, or test scripts.
|
|
616
627
|
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`, \`npm run test\`).
|
|
@@ -647,6 +658,7 @@ ${readmeBlock}
|
|
|
647
658
|
function buildWorktreePrompt(issue, testRunner) {
|
|
648
659
|
const testBlock = buildTestInstructions(testRunner ?? null);
|
|
649
660
|
const readmeBlock = buildReadmeInstructions();
|
|
661
|
+
const hookBlock = buildPreCommitHookInstructions();
|
|
650
662
|
return `You are an autonomous implementation agent. Your job is to implement a single
|
|
651
663
|
issue, validate it, commit, and push the branch.
|
|
652
664
|
|
|
@@ -670,7 +682,7 @@ ${issue.description}
|
|
|
670
682
|
- Follow the implementation instructions exactly
|
|
671
683
|
- Verify each acceptance criteria (if present)
|
|
672
684
|
- Respect any stack or technical constraints (if present)
|
|
673
|
-
${testBlock}${readmeBlock}
|
|
685
|
+
${testBlock}${readmeBlock}${hookBlock}
|
|
674
686
|
2. **Validate**: Run the project's linter/typecheck/tests if available:
|
|
675
687
|
- Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
|
|
676
688
|
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
@@ -711,6 +723,7 @@ function buildBranchPrompt(issue, config2, testRunner) {
|
|
|
711
723
|
const baseBranchInstruction = config2.repos.length > 0 ? "From the repo's base branch (listed above)" : `From \`${config2.base_branch}\``;
|
|
712
724
|
const testBlock = buildTestInstructions(testRunner ?? null);
|
|
713
725
|
const readmeBlock = buildReadmeInstructions();
|
|
726
|
+
const hookBlock = buildPreCommitHookInstructions();
|
|
714
727
|
const manifestPath = join(workspace, ".lisa-manifest.json");
|
|
715
728
|
return `You are an autonomous implementation agent. Your job is to implement a single
|
|
716
729
|
issue, validate it, commit, and push the branch.
|
|
@@ -741,7 +754,7 @@ ${repoEntries}
|
|
|
741
754
|
- Follow the implementation instructions exactly
|
|
742
755
|
- Verify each acceptance criteria (if present)
|
|
743
756
|
- Respect any stack or technical constraints (if present)
|
|
744
|
-
${testBlock}${readmeBlock}
|
|
757
|
+
${testBlock}${readmeBlock}${hookBlock}
|
|
745
758
|
4. **Validate**: Run the project's linter/typecheck/tests if available:
|
|
746
759
|
- Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
|
|
747
760
|
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
@@ -771,6 +784,32 @@ ${testBlock}${readmeBlock}
|
|
|
771
784
|
- Do NOT create pull requests \u2014 the caller handles that.
|
|
772
785
|
- Do NOT update the issue tracker \u2014 the caller handles that.`;
|
|
773
786
|
}
|
|
787
|
+
function buildPushRecoveryPrompt(hookErrors) {
|
|
788
|
+
return `The previous \`git push\` failed because a pre-push hook rejected the push.
|
|
789
|
+
Here is the full error output:
|
|
790
|
+
|
|
791
|
+
\`\`\`
|
|
792
|
+
${hookErrors}
|
|
793
|
+
\`\`\`
|
|
794
|
+
|
|
795
|
+
## Instructions
|
|
796
|
+
|
|
797
|
+
1. **Read the errors** above carefully and identify the root cause.
|
|
798
|
+
2. **Fix the issue** \u2014 common fixes include:
|
|
799
|
+
- Run linters/formatters (e.g. \`npm run lint -- --fix\`, \`npm run format\`)
|
|
800
|
+
- Run code generation (e.g. \`npx prisma generate\`, \`npm run codegen\`)
|
|
801
|
+
- Fix type errors in the source files
|
|
802
|
+
- Fix failing tests
|
|
803
|
+
3. **Amend the commit** so the fix is included:
|
|
804
|
+
\`\`\`
|
|
805
|
+
git add -A && git commit --amend --no-edit
|
|
806
|
+
\`\`\`
|
|
807
|
+
4. **Do NOT push** \u2014 the caller handles pushing after you finish.
|
|
808
|
+
5. **Do NOT create pull requests** \u2014 the caller handles that.
|
|
809
|
+
6. **Do NOT update the issue tracker** \u2014 the caller handles that.
|
|
810
|
+
|
|
811
|
+
Focus only on fixing the hook errors. Do not make unrelated changes.`;
|
|
812
|
+
}
|
|
774
813
|
|
|
775
814
|
// src/guardrails.ts
|
|
776
815
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
@@ -1792,6 +1831,58 @@ function cleanupManifest(dir) {
|
|
|
1792
1831
|
} catch {
|
|
1793
1832
|
}
|
|
1794
1833
|
}
|
|
1834
|
+
var MAX_PUSH_RETRIES = 2;
|
|
1835
|
+
var HOOK_ERROR_PATTERNS = [
|
|
1836
|
+
/husky - pre-push/i,
|
|
1837
|
+
/husky - pre-commit/i,
|
|
1838
|
+
/pre-push hook/i,
|
|
1839
|
+
/pre-commit hook/i,
|
|
1840
|
+
/hook declined/i,
|
|
1841
|
+
/hook.*failed/i,
|
|
1842
|
+
/hook.*exited with/i,
|
|
1843
|
+
/hook.*returned.*exit code/i
|
|
1844
|
+
];
|
|
1845
|
+
function isHookError(errorMessage) {
|
|
1846
|
+
return HOOK_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage));
|
|
1847
|
+
}
|
|
1848
|
+
async function pushWithRecovery(opts) {
|
|
1849
|
+
for (let attempt = 0; attempt <= MAX_PUSH_RETRIES; attempt++) {
|
|
1850
|
+
try {
|
|
1851
|
+
await execa3("git", ["push", "-u", "origin", opts.branch], { cwd: opts.cwd });
|
|
1852
|
+
return { success: true };
|
|
1853
|
+
} catch (err) {
|
|
1854
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1855
|
+
if (!isHookError(errorMessage)) {
|
|
1856
|
+
return { success: false, error: errorMessage };
|
|
1857
|
+
}
|
|
1858
|
+
if (attempt >= MAX_PUSH_RETRIES) {
|
|
1859
|
+
return {
|
|
1860
|
+
success: false,
|
|
1861
|
+
error: `Push hook failed after ${MAX_PUSH_RETRIES} recovery attempts: ${errorMessage}`
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
warn(
|
|
1865
|
+
`Push hook failed (attempt ${attempt + 1}/${MAX_PUSH_RETRIES}). Re-invoking provider to fix...`
|
|
1866
|
+
);
|
|
1867
|
+
const recoveryPrompt = buildPushRecoveryPrompt(errorMessage);
|
|
1868
|
+
const result = await runWithFallback(opts.models, recoveryPrompt, {
|
|
1869
|
+
logFile: opts.logFile,
|
|
1870
|
+
cwd: opts.cwd,
|
|
1871
|
+
guardrailsDir: opts.guardrailsDir,
|
|
1872
|
+
issueId: opts.issueId,
|
|
1873
|
+
overseer: opts.overseer
|
|
1874
|
+
});
|
|
1875
|
+
if (!result.success) {
|
|
1876
|
+
return {
|
|
1877
|
+
success: false,
|
|
1878
|
+
error: `Provider failed to fix push hook errors: ${result.output}`
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
ok("Provider finished recovery. Retrying push...");
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
return { success: false, error: "Push recovery exhausted retries" };
|
|
1885
|
+
}
|
|
1795
1886
|
function installSignalHandlers() {
|
|
1796
1887
|
const cleanup = async (signal) => {
|
|
1797
1888
|
if (shuttingDown) {
|
|
@@ -2158,12 +2249,17 @@ ${result.output}
|
|
|
2158
2249
|
);
|
|
2159
2250
|
}
|
|
2160
2251
|
}
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2252
|
+
const pushResult = await pushWithRecovery({
|
|
2253
|
+
branch: effectiveBranch,
|
|
2254
|
+
cwd: worktreePath,
|
|
2255
|
+
models,
|
|
2256
|
+
logFile,
|
|
2257
|
+
guardrailsDir: repoPath,
|
|
2258
|
+
issueId: issue.id,
|
|
2259
|
+
overseer: config2.overseer
|
|
2260
|
+
});
|
|
2261
|
+
if (!pushResult.success) {
|
|
2262
|
+
error(`Failed to push branch to remote: ${pushResult.error}`);
|
|
2167
2263
|
cleanupManifest(worktreePath);
|
|
2168
2264
|
await cleanupWorktree(repoPath, worktreePath);
|
|
2169
2265
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
@@ -2236,27 +2332,37 @@ ${result.output}
|
|
|
2236
2332
|
ok(`Provider chose repo: ${manifest.repoPath}, branch: ${manifest.branch}`);
|
|
2237
2333
|
const worktreePath = join7(manifest.repoPath, ".worktrees", manifest.branch);
|
|
2238
2334
|
const baseBranch = resolveBaseBranch(config2, manifest.repoPath);
|
|
2239
|
-
const
|
|
2335
|
+
const hasWorktree = existsSync6(worktreePath);
|
|
2336
|
+
const effectiveCwd = hasWorktree ? worktreePath : manifest.repoPath;
|
|
2337
|
+
if (!hasWorktree) {
|
|
2338
|
+
warn(`Worktree not found at ${worktreePath} \u2014 using repo root for git operations`);
|
|
2339
|
+
}
|
|
2340
|
+
const testsPassed = await runTestValidation(effectiveCwd);
|
|
2240
2341
|
if (!testsPassed) {
|
|
2241
2342
|
error(`Tests failed for ${issue.id}. Blocking PR creation.`);
|
|
2242
|
-
await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2343
|
+
if (hasWorktree) await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2243
2344
|
cleanupManifest(workspace);
|
|
2244
2345
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2245
2346
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2347
|
+
const pushResult = await pushWithRecovery({
|
|
2348
|
+
branch: manifest.branch,
|
|
2349
|
+
cwd: effectiveCwd,
|
|
2350
|
+
models,
|
|
2351
|
+
logFile,
|
|
2352
|
+
guardrailsDir: manifest.repoPath,
|
|
2353
|
+
issueId: issue.id,
|
|
2354
|
+
overseer: config2.overseer
|
|
2355
|
+
});
|
|
2356
|
+
if (!pushResult.success) {
|
|
2357
|
+
error(`Failed to push branch to remote: ${pushResult.error}`);
|
|
2358
|
+
if (hasWorktree) await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2253
2359
|
cleanupManifest(workspace);
|
|
2254
2360
|
return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
|
|
2255
2361
|
}
|
|
2256
2362
|
const prTitle = manifest.prTitle ?? issue.title;
|
|
2257
2363
|
const prUrls = [];
|
|
2258
2364
|
try {
|
|
2259
|
-
const repoInfo = await getRepoInfo(
|
|
2365
|
+
const repoInfo = await getRepoInfo(effectiveCwd);
|
|
2260
2366
|
const pr = await createPullRequest(
|
|
2261
2367
|
{
|
|
2262
2368
|
owner: repoInfo.owner,
|
|
@@ -2274,7 +2380,7 @@ ${result.output}
|
|
|
2274
2380
|
error(`Failed to create PR: ${err instanceof Error ? err.message : String(err)}`);
|
|
2275
2381
|
}
|
|
2276
2382
|
cleanupManifest(workspace);
|
|
2277
|
-
await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2383
|
+
if (hasWorktree) await cleanupWorktree(manifest.repoPath, worktreePath);
|
|
2278
2384
|
ok(`Session ${session} complete for ${issue.id}`);
|
|
2279
2385
|
return { success: true, providerUsed: result.providerUsed, prUrls, fallback: result };
|
|
2280
2386
|
}
|