@tarcisiopgs/lisa 1.34.0 → 1.36.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/{chunk-2DONWCCQ.js → chunk-6VIN5PMW.js} +33 -19
- package/dist/{chunk-S2PRXET6.js → chunk-AFKXWCAM.js} +141 -23
- package/dist/{chunk-5FODVMWF.js → chunk-PGIXWLQT.js} +6 -1
- package/dist/index.js +278 -16
- package/dist/{loop-D4AKZUXG.js → loop-732CLNLZ.js} +2 -2
- package/dist/{tui-bridge-6C4IRKUQ.js → tui-bridge-TSGCSJV4.js} +2 -2
- package/package.json +1 -1
|
@@ -1195,6 +1195,7 @@ function buildPrompt(opts) {
|
|
|
1195
1195
|
const depBlock = issue.dependency ? buildDependencyContext(issue.dependency) : "";
|
|
1196
1196
|
const specWarningBlock = buildSpecWarningBlock(issue.specWarning);
|
|
1197
1197
|
const contextMdBlock = buildContextMdBlock(repoContextMd ?? null);
|
|
1198
|
+
const dodBlock = buildDefinitionOfDone(issue.description ?? "");
|
|
1198
1199
|
const relevantFilesBlock = relevantFiles ?? "";
|
|
1199
1200
|
const prBase = issue.dependency ? issue.dependency.branch : baseBranch;
|
|
1200
1201
|
const prCreateBlock = buildPrCreateInstruction(platform2, prBase);
|
|
@@ -1267,10 +1268,7 @@ ${repoEntries}
|
|
|
1267
1268
|
- Verify each acceptance criteria (if present)
|
|
1268
1269
|
- Respect any stack or technical constraints (if present)
|
|
1269
1270
|
${testBlock}${hookBlock}
|
|
1270
|
-
4.
|
|
1271
|
-
- Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
|
|
1272
|
-
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
1273
|
-
- Fix any errors before proceeding.
|
|
1271
|
+
4. ${buildValidateStep(testRunner ?? null, pm)}
|
|
1274
1272
|
${readmeBlock}
|
|
1275
1273
|
**CRITICAL \u2014 Do NOT stop here. The following steps (commit, push, PR, manifest) are MANDATORY. Skipping them means the task has FAILED.**
|
|
1276
1274
|
|
|
@@ -1306,10 +1304,7 @@ ${readmeBlock}
|
|
|
1306
1304
|
- Follow the implementation instructions exactly
|
|
1307
1305
|
- Verify each acceptance criteria relevant to your scope
|
|
1308
1306
|
${testBlock}${hookBlock}
|
|
1309
|
-
2.
|
|
1310
|
-
- Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
|
|
1311
|
-
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
1312
|
-
- Fix any errors before proceeding.
|
|
1307
|
+
2. ${buildValidateStep(testRunner ?? null, pm)}
|
|
1313
1308
|
${readmeBlock}
|
|
1314
1309
|
**CRITICAL \u2014 Do NOT stop here. The following steps (commit, push, PR, manifest) are MANDATORY. Skipping them means the task has FAILED.**
|
|
1315
1310
|
|
|
@@ -1342,10 +1337,7 @@ ${trackerStep}
|
|
|
1342
1337
|
- Verify each acceptance criteria (if present)
|
|
1343
1338
|
- Respect any stack or technical constraints (if present)
|
|
1344
1339
|
${testBlock}${hookBlock}
|
|
1345
|
-
2.
|
|
1346
|
-
- Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
|
|
1347
|
-
- Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
1348
|
-
- Fix any errors before proceeding.
|
|
1340
|
+
2. ${buildValidateStep(testRunner ?? null, pm)}
|
|
1349
1341
|
${readmeBlock}
|
|
1350
1342
|
**CRITICAL \u2014 Do NOT stop here. The following steps (commit, push, PR, manifest) are MANDATORY. Skipping them means the task has FAILED.**
|
|
1351
1343
|
|
|
@@ -1405,7 +1397,7 @@ ${lineageSection}
|
|
|
1405
1397
|
### Description
|
|
1406
1398
|
|
|
1407
1399
|
${issue.description}
|
|
1408
|
-
${specWarningBlock}${scopeSection}${GUARDRAILS_PLACEHOLDER}
|
|
1400
|
+
${specWarningBlock}${dodBlock}${scopeSection}${GUARDRAILS_PLACEHOLDER}
|
|
1409
1401
|
${instructions}
|
|
1410
1402
|
|
|
1411
1403
|
${rulesSection}
|
|
@@ -1490,12 +1482,26 @@ function buildTestInstructions(testRunner, pm = "npm") {
|
|
|
1490
1482
|
if (!testRunner) return "";
|
|
1491
1483
|
const testCmd = pm === "bun" ? "bun run test" : `${pm} run test`;
|
|
1492
1484
|
return `
|
|
1493
|
-
**MANDATORY \u2014
|
|
1494
|
-
This project uses **${testRunner}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1485
|
+
**MANDATORY \u2014 Test-Driven Development (TDD):**
|
|
1486
|
+
This project uses **${testRunner}**. Follow the RED \u2192 GREEN \u2192 REFACTOR cycle strictly:
|
|
1487
|
+
1. **RED**: Write the failing tests first \u2014 before writing any implementation code.
|
|
1488
|
+
Run \`${testCmd}\` and confirm the new tests fail. If they pass immediately, the tests are wrong.
|
|
1489
|
+
2. **GREEN**: Write the minimum implementation to make the tests pass.
|
|
1490
|
+
Run \`${testCmd}\` \u2014 all tests must pass before continuing.
|
|
1491
|
+
3. **REFACTOR**: Clean up the code without breaking tests. Run \`${testCmd}\` one final time to confirm.
|
|
1492
|
+
- Cover the main functionality, edge cases, and error scenarios.
|
|
1493
|
+
- Do NOT write implementation before tests. The PR will be blocked if tests are missing or written after the fact.
|
|
1494
|
+
`;
|
|
1495
|
+
}
|
|
1496
|
+
function buildDefinitionOfDone(description) {
|
|
1497
|
+
const criteria = description.split("\n").map((l) => l.trim()).filter((l) => /^- \[ \]/.test(l));
|
|
1498
|
+
if (criteria.length === 0) return "";
|
|
1499
|
+
return `
|
|
1500
|
+
## Definition of Done
|
|
1501
|
+
|
|
1502
|
+
Verify each item before finishing:
|
|
1503
|
+
|
|
1504
|
+
${criteria.join("\n")}
|
|
1499
1505
|
`;
|
|
1500
1506
|
}
|
|
1501
1507
|
function buildSpecWarningBlock(warning) {
|
|
@@ -1531,6 +1537,14 @@ function buildEnvironmentDependencyRule(env) {
|
|
|
1531
1537
|
}
|
|
1532
1538
|
return "";
|
|
1533
1539
|
}
|
|
1540
|
+
function buildValidateStep(testRunner, pm = "npm") {
|
|
1541
|
+
const testCmd = pm === "bun" ? "bun run test" : `${pm} run test`;
|
|
1542
|
+
const testLine = testRunner ? ` - Run \`${testCmd}\` \u2014 ALL tests must pass (final gate after the TDD cycle).
|
|
1543
|
+
` : "";
|
|
1544
|
+
return `**Validate**: Confirm all quality gates before committing:
|
|
1545
|
+
${testLine} - Run lint/typecheck scripts if available (e.g., \`npm run lint\`, \`npm run typecheck\`).
|
|
1546
|
+
- Fix every error. Do NOT commit with failing tests or lint errors.`;
|
|
1547
|
+
}
|
|
1534
1548
|
function buildPreCommitHookInstructions() {
|
|
1535
1549
|
return `
|
|
1536
1550
|
**Pre-commit hooks:**
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
resolveModels,
|
|
6
6
|
runWithFallback,
|
|
7
7
|
saveLineage
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-6VIN5PMW.js";
|
|
9
9
|
import {
|
|
10
10
|
error,
|
|
11
11
|
log,
|
|
@@ -65,7 +65,9 @@ function parsePlanResponse(raw) {
|
|
|
65
65
|
relevantFiles: Array.isArray(issue.relevantFiles) ? issue.relevantFiles.filter((f) => typeof f === "string") : [],
|
|
66
66
|
order: typeof issue.order === "number" ? issue.order : idx + 1,
|
|
67
67
|
dependsOn: Array.isArray(issue.dependsOn) ? issue.dependsOn.filter((d) => typeof d === "number") : [],
|
|
68
|
-
repo: typeof issue.repo === "string" ? issue.repo : void 0
|
|
68
|
+
repo: typeof issue.repo === "string" ? issue.repo : void 0,
|
|
69
|
+
verifyCommand: typeof issue.verifyCommand === "string" ? issue.verifyCommand : void 0,
|
|
70
|
+
doneCriteria: typeof issue.doneCriteria === "string" ? issue.doneCriteria : void 0
|
|
69
71
|
};
|
|
70
72
|
});
|
|
71
73
|
return issues;
|
|
@@ -175,6 +177,20 @@ async function createPlanIssues(source, config, plan, workspace) {
|
|
|
175
177
|
const orderToId = /* @__PURE__ */ new Map();
|
|
176
178
|
for (const issue of sorted) {
|
|
177
179
|
let description = ensureAcceptanceCriteria(issue.description, issue.acceptanceCriteria);
|
|
180
|
+
if (issue.verifyCommand) {
|
|
181
|
+
description += `
|
|
182
|
+
|
|
183
|
+
## Verify Command
|
|
184
|
+
|
|
185
|
+
\`\`\`bash
|
|
186
|
+
${issue.verifyCommand}
|
|
187
|
+
\`\`\`
|
|
188
|
+
`;
|
|
189
|
+
if (issue.doneCriteria) {
|
|
190
|
+
description += `Expected: ${issue.doneCriteria}
|
|
191
|
+
`;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
178
194
|
if (issue.dependsOn.length > 0 && !source.linkDependency) {
|
|
179
195
|
const depRefs = issue.dependsOn.map((depOrder) => {
|
|
180
196
|
const depId = orderToId.get(depOrder);
|
|
@@ -275,6 +291,8 @@ For each issue, provide:
|
|
|
275
291
|
- **relevantFiles**: Array of file paths in the codebase that will be modified or created
|
|
276
292
|
- **order**: Integer (1-based) \u2014 execution order based on dependencies
|
|
277
293
|
- **dependsOn**: Array of order numbers this issue depends on (empty if independent)
|
|
294
|
+
- **verifyCommand**: A shell command that validates the issue is complete (e.g., \`npm test\`, \`npx tsc --noEmit\`, \`curl localhost:3000/health\`)
|
|
295
|
+
- **doneCriteria**: What success looks like when the verify command runs (e.g., "All tests pass", "Returns 200 OK")
|
|
278
296
|
${config.repos.length > 1 ? "- **repo**: Name of the target repository from the list above (required for multi-repo)\n" : ""}
|
|
279
297
|
## Rules
|
|
280
298
|
|
|
@@ -284,13 +302,14 @@ ${config.repos.length > 1 ? "- **repo**: Name of the target repository from the
|
|
|
284
302
|
4. Issues MUST include test expectations in their acceptance criteria
|
|
285
303
|
5. Order issues so dependencies come first (lower order = executes first)
|
|
286
304
|
6. Use clear, specific titles \u2014 not vague ("Improve X" is bad, "Add rate limit middleware to /api/users" is good)
|
|
287
|
-
7.
|
|
305
|
+
7. Each issue SHOULD include a verifyCommand that can programmatically validate completion
|
|
306
|
+
8. Output ONLY valid JSON \u2014 no markdown code fences, no explanation text
|
|
288
307
|
|
|
289
308
|
## Output Format
|
|
290
309
|
|
|
291
310
|
Respond with ONLY this JSON structure (no wrapping, no markdown):
|
|
292
311
|
|
|
293
|
-
{"issues":[{"title":"...","description":"...","acceptanceCriteria":["..."],"relevantFiles":["..."],"order":1,"dependsOn":[]${config.repos.length > 1 ? ',"repo":"..."' : ""}}]}`;
|
|
312
|
+
{"issues":[{"title":"...","description":"...","acceptanceCriteria":["..."],"relevantFiles":["..."],"order":1,"dependsOn":[],"verifyCommand":"...","doneCriteria":"..."${config.repos.length > 1 ? ',"repo":"..."' : ""}}]}`;
|
|
294
313
|
}
|
|
295
314
|
|
|
296
315
|
// src/plan/persistence.ts
|
|
@@ -404,6 +423,40 @@ Please output ONLY valid JSON with the exact structure specified above.`;
|
|
|
404
423
|
);
|
|
405
424
|
}
|
|
406
425
|
|
|
426
|
+
// src/plan/waves.ts
|
|
427
|
+
function buildExecutionWaves(issues) {
|
|
428
|
+
if (issues.length === 0) return [];
|
|
429
|
+
const remaining = /* @__PURE__ */ new Map();
|
|
430
|
+
for (const issue of issues) {
|
|
431
|
+
remaining.set(issue.order, new Set(issue.dependsOn));
|
|
432
|
+
}
|
|
433
|
+
const waves = [];
|
|
434
|
+
while (remaining.size > 0) {
|
|
435
|
+
const wave = [];
|
|
436
|
+
for (const [order, deps] of remaining) {
|
|
437
|
+
if (deps.size === 0) {
|
|
438
|
+
wave.push(order);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (wave.length === 0) {
|
|
442
|
+
waves.push([...remaining.keys()].sort((a, b) => a - b));
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
wave.sort((a, b) => a - b);
|
|
446
|
+
waves.push(wave);
|
|
447
|
+
const assigned = new Set(wave);
|
|
448
|
+
for (const order of wave) {
|
|
449
|
+
remaining.delete(order);
|
|
450
|
+
}
|
|
451
|
+
for (const deps of remaining.values()) {
|
|
452
|
+
for (const a of assigned) {
|
|
453
|
+
deps.delete(a);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return waves;
|
|
458
|
+
}
|
|
459
|
+
|
|
407
460
|
// src/plan/wizard.ts
|
|
408
461
|
async function runPlanWizard(plan, planPath, opts) {
|
|
409
462
|
const workspace = opts.config.workspace;
|
|
@@ -457,17 +510,29 @@ async function runPlanWizard(plan, planPath, opts) {
|
|
|
457
510
|
function displayPlan(plan) {
|
|
458
511
|
clack.log.info(`${pc.bold("Goal:")} ${plan.goal}`);
|
|
459
512
|
clack.log.info("");
|
|
513
|
+
const waves = buildExecutionWaves(plan.issues);
|
|
460
514
|
const sorted = [...plan.issues].sort((a, b) => a.order - b.order);
|
|
461
|
-
for (
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
515
|
+
for (let waveIdx = 0; waveIdx < waves.length; waveIdx++) {
|
|
516
|
+
const wave = waves[waveIdx];
|
|
517
|
+
const label = wave.length > 1 ? "parallel" : "sequential";
|
|
518
|
+
clack.log.info(pc.dim(` Wave ${waveIdx + 1} (${label}):`));
|
|
519
|
+
for (const orderNum of wave) {
|
|
520
|
+
const issue = sorted.find((i) => i.order === orderNum);
|
|
521
|
+
if (!issue) continue;
|
|
522
|
+
const deps = issue.dependsOn.length > 0 ? pc.gray(` \u2192 depends on: ${issue.dependsOn.map((d) => `#${d}`).join(", ")}`) : "";
|
|
523
|
+
const repo = issue.repo ? pc.cyan(` [${issue.repo}]`) : "";
|
|
524
|
+
const files = issue.relevantFiles.length > 0 ? pc.gray(
|
|
525
|
+
`
|
|
466
526
|
Files: ${issue.relevantFiles.slice(0, 3).join(", ")}${issue.relevantFiles.length > 3 ? ` +${issue.relevantFiles.length - 3}` : ""}`
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
527
|
+
) : "";
|
|
528
|
+
const verify = issue.verifyCommand ? pc.gray(`
|
|
529
|
+
Verify: ${issue.verifyCommand}`) : "";
|
|
530
|
+
const criteria = issue.acceptanceCriteria.length > 0 ? pc.gray(`
|
|
531
|
+
Criteria: ${issue.acceptanceCriteria.length} item(s)`) : "";
|
|
532
|
+
clack.log.info(
|
|
533
|
+
` ${pc.yellow(String(issue.order))}. ${pc.bold(issue.title)}${repo}${deps}${files}${criteria}${verify}`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
471
536
|
}
|
|
472
537
|
clack.log.info("");
|
|
473
538
|
}
|
|
@@ -542,15 +607,13 @@ async function reorderIssues(plan, _workspace) {
|
|
|
542
607
|
});
|
|
543
608
|
if (clack.isCancel(answer)) return;
|
|
544
609
|
const newOrder = answer.split(",").map((n) => Number(n.trim()));
|
|
545
|
-
const
|
|
546
|
-
for (const issue of plan.issues) {
|
|
547
|
-
oldOrderMap.set(issue.order, issue);
|
|
548
|
-
}
|
|
610
|
+
const oldToNew = /* @__PURE__ */ new Map();
|
|
549
611
|
for (let i = 0; i < newOrder.length; i++) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
612
|
+
oldToNew.set(newOrder[i], i + 1);
|
|
613
|
+
}
|
|
614
|
+
for (const issue of plan.issues) {
|
|
615
|
+
issue.order = oldToNew.get(issue.order) ?? issue.order;
|
|
616
|
+
issue.dependsOn = issue.dependsOn.map((d) => oldToNew.get(d) ?? d).filter((d) => d !== issue.order);
|
|
554
617
|
}
|
|
555
618
|
clack.log.success("Issues reordered.");
|
|
556
619
|
}
|
|
@@ -582,6 +645,14 @@ function issueToMarkdown(issue) {
|
|
|
582
645
|
`;
|
|
583
646
|
md += `${issue.description}
|
|
584
647
|
`;
|
|
648
|
+
if (issue.dependsOn.length > 0) {
|
|
649
|
+
md += `
|
|
650
|
+
## Depends On
|
|
651
|
+
|
|
652
|
+
`;
|
|
653
|
+
md += `${issue.dependsOn.join(", ")}
|
|
654
|
+
`;
|
|
655
|
+
}
|
|
585
656
|
if (issue.relevantFiles.length > 0) {
|
|
586
657
|
md += `
|
|
587
658
|
## Relevant Files
|
|
@@ -589,6 +660,21 @@ function issueToMarkdown(issue) {
|
|
|
589
660
|
`;
|
|
590
661
|
for (const f of issue.relevantFiles) {
|
|
591
662
|
md += `- ${f}
|
|
663
|
+
`;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (issue.verifyCommand) {
|
|
667
|
+
md += `
|
|
668
|
+
## Verify
|
|
669
|
+
|
|
670
|
+
`;
|
|
671
|
+
md += `\`\`\`bash
|
|
672
|
+
${issue.verifyCommand}
|
|
673
|
+
\`\`\`
|
|
674
|
+
`;
|
|
675
|
+
if (issue.doneCriteria) {
|
|
676
|
+
md += `
|
|
677
|
+
Expected: ${issue.doneCriteria}
|
|
592
678
|
`;
|
|
593
679
|
}
|
|
594
680
|
}
|
|
@@ -599,10 +685,24 @@ function markdownToIssue(content, original) {
|
|
|
599
685
|
const titleLine = lines.find((l) => l.startsWith("# "));
|
|
600
686
|
const title = titleLine ? titleLine.replace(/^#\s+/, "").trim() : original.title;
|
|
601
687
|
const titleIdx = lines.indexOf(titleLine ?? "");
|
|
688
|
+
const depsIdx = lines.findIndex((l) => l.startsWith("## Depends On"));
|
|
602
689
|
const filesIdx = lines.findIndex((l) => l.startsWith("## Relevant Files"));
|
|
603
|
-
const
|
|
690
|
+
const verifyIdx = lines.findIndex((l) => l.startsWith("## Verify"));
|
|
691
|
+
const sectionBoundaries = [depsIdx, filesIdx, verifyIdx].filter((i) => i > 0);
|
|
692
|
+
const descEnd = sectionBoundaries.length > 0 ? Math.min(...sectionBoundaries) : -1;
|
|
693
|
+
const descLines = descEnd > 0 ? lines.slice(titleIdx + 1, descEnd) : lines.slice(titleIdx + 1);
|
|
604
694
|
const description = descLines.join("\n").trim();
|
|
605
695
|
const acceptanceCriteria = descLines.filter((l) => l.trim().startsWith("- [ ]") || l.trim().startsWith("- [x]")).map((l) => l.trim().replace(/^- \[[ x]\]\s*/, ""));
|
|
696
|
+
let dependsOn;
|
|
697
|
+
if (depsIdx > 0) {
|
|
698
|
+
const nextSection = [filesIdx, verifyIdx].filter((i) => i > depsIdx);
|
|
699
|
+
const depsEnd = nextSection.length > 0 ? Math.min(...nextSection) : lines.length;
|
|
700
|
+
const depsContent = lines.slice(depsIdx + 1, depsEnd).join(" ").trim();
|
|
701
|
+
if (depsContent) {
|
|
702
|
+
const parsed = depsContent.split(/[,\s]+/).map((s) => Number.parseInt(s.trim(), 10)).filter((n) => !Number.isNaN(n));
|
|
703
|
+
if (parsed.length > 0) dependsOn = parsed;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
606
706
|
const relevantFiles = [];
|
|
607
707
|
if (filesIdx > 0) {
|
|
608
708
|
for (let i = filesIdx + 1; i < lines.length; i++) {
|
|
@@ -614,11 +714,28 @@ function markdownToIssue(content, original) {
|
|
|
614
714
|
}
|
|
615
715
|
}
|
|
616
716
|
}
|
|
717
|
+
let verifyCommand;
|
|
718
|
+
let doneCriteria;
|
|
719
|
+
if (verifyIdx > 0) {
|
|
720
|
+
const verifyLines = lines.slice(verifyIdx + 1);
|
|
721
|
+
const codeStart = verifyLines.findIndex((l) => l.trim().startsWith("```"));
|
|
722
|
+
const codeEnd = verifyLines.findIndex((l, i) => i > codeStart && l.trim().startsWith("```"));
|
|
723
|
+
if (codeStart >= 0 && codeEnd > codeStart) {
|
|
724
|
+
verifyCommand = verifyLines.slice(codeStart + 1, codeEnd).join("\n").trim();
|
|
725
|
+
}
|
|
726
|
+
const expectedLine = verifyLines.find((l) => l.trim().startsWith("Expected:"));
|
|
727
|
+
if (expectedLine) {
|
|
728
|
+
doneCriteria = expectedLine.trim().replace(/^Expected:\s*/, "");
|
|
729
|
+
}
|
|
730
|
+
}
|
|
617
731
|
return {
|
|
618
732
|
title,
|
|
619
733
|
description: description || original.description,
|
|
620
734
|
acceptanceCriteria: acceptanceCriteria.length > 0 ? acceptanceCriteria : original.acceptanceCriteria,
|
|
621
|
-
relevantFiles: relevantFiles.length > 0 ? relevantFiles : original.relevantFiles
|
|
735
|
+
relevantFiles: relevantFiles.length > 0 ? relevantFiles : original.relevantFiles,
|
|
736
|
+
...dependsOn ? { dependsOn } : {},
|
|
737
|
+
...verifyCommand ? { verifyCommand } : {},
|
|
738
|
+
...doneCriteria ? { doneCriteria } : {}
|
|
622
739
|
};
|
|
623
740
|
}
|
|
624
741
|
|
|
@@ -630,6 +747,7 @@ export {
|
|
|
630
747
|
generatePlan,
|
|
631
748
|
savePlan,
|
|
632
749
|
loadLatestPlan,
|
|
750
|
+
buildExecutionWaves,
|
|
633
751
|
runPlanWizard,
|
|
634
752
|
issueToMarkdown,
|
|
635
753
|
markdownToIssue
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
resolveModels,
|
|
33
33
|
runValidationCommands,
|
|
34
34
|
runWithFallback
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-6VIN5PMW.js";
|
|
36
36
|
import {
|
|
37
37
|
divider,
|
|
38
38
|
error,
|
|
@@ -255,6 +255,7 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
255
255
|
const rawReviewMonitor = parsed.review_monitor;
|
|
256
256
|
const rawReactions = parsed.reactions;
|
|
257
257
|
const rawSpecCompliance = parsed.spec_compliance;
|
|
258
|
+
const rawPlanValidation = parsed.plan_validation;
|
|
258
259
|
const rawProgress = parsed.progress_comments;
|
|
259
260
|
const rawPr = parsed.pr;
|
|
260
261
|
const config = {
|
|
@@ -309,6 +310,10 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
309
310
|
max_retries: rawSpecCompliance.max_retries,
|
|
310
311
|
block_on_failure: rawSpecCompliance.block_on_failure
|
|
311
312
|
} : void 0,
|
|
313
|
+
plan_validation: rawPlanValidation ? {
|
|
314
|
+
enabled: rawPlanValidation.enabled ?? false,
|
|
315
|
+
max_iterations: rawPlanValidation.max_iterations
|
|
316
|
+
} : void 0,
|
|
312
317
|
progress_comments: rawProgress ? { enabled: rawProgress.enabled ?? false } : void 0,
|
|
313
318
|
pr: parsePrConfig(rawPr),
|
|
314
319
|
provider_options: {
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
CliError,
|
|
4
|
+
buildExecutionWaves,
|
|
4
5
|
createPlanIssues,
|
|
5
6
|
generatePlan,
|
|
6
7
|
loadLatestPlan,
|
|
7
8
|
parseStructuredOutput,
|
|
8
9
|
runPlanWizard,
|
|
9
10
|
savePlan
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AFKXWCAM.js";
|
|
11
12
|
import {
|
|
12
13
|
detectDefaultBranch,
|
|
13
14
|
detectGitRepos,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
runLoop,
|
|
38
39
|
saveConfig,
|
|
39
40
|
validateConfig
|
|
40
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-PGIXWLQT.js";
|
|
41
42
|
import {
|
|
42
43
|
buildContextMdBlock,
|
|
43
44
|
createProvider,
|
|
@@ -49,7 +50,7 @@ import {
|
|
|
49
50
|
readContext,
|
|
50
51
|
resolveModels,
|
|
51
52
|
runWithFallback
|
|
52
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-6VIN5PMW.js";
|
|
53
54
|
import {
|
|
54
55
|
banner,
|
|
55
56
|
error,
|
|
@@ -58,7 +59,8 @@ import {
|
|
|
58
59
|
ok,
|
|
59
60
|
setLogLevel,
|
|
60
61
|
setOutputMode,
|
|
61
|
-
updateNotice
|
|
62
|
+
updateNotice,
|
|
63
|
+
warn
|
|
62
64
|
} from "./chunk-V44FTYWZ.js";
|
|
63
65
|
import "./chunk-3EOEDL3T.js";
|
|
64
66
|
import {
|
|
@@ -955,6 +957,14 @@ function runAdvancedChecks(config2) {
|
|
|
955
957
|
category: "advanced"
|
|
956
958
|
});
|
|
957
959
|
}
|
|
960
|
+
if (config2.plan_validation?.enabled) {
|
|
961
|
+
const maxIter = config2.plan_validation.max_iterations ?? 2;
|
|
962
|
+
results.push({
|
|
963
|
+
passed: true,
|
|
964
|
+
label: `Plan validation is enabled (max ${maxIter} iteration${maxIter !== 1 ? "s" : ""})`,
|
|
965
|
+
category: "advanced"
|
|
966
|
+
});
|
|
967
|
+
}
|
|
958
968
|
if (config2.hooks) {
|
|
959
969
|
for (const [name, cmd] of Object.entries(config2.hooks)) {
|
|
960
970
|
if (name === "timeout" || !cmd || typeof cmd !== "string") continue;
|
|
@@ -1293,7 +1303,7 @@ var init = defineCommand5({
|
|
|
1293
1303
|
// src/cli/commands/issue.ts
|
|
1294
1304
|
import { defineCommand as defineCommand6 } from "citty";
|
|
1295
1305
|
function sleep(ms) {
|
|
1296
|
-
return new Promise((
|
|
1306
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
1297
1307
|
}
|
|
1298
1308
|
var issueGet = defineCommand6({
|
|
1299
1309
|
meta: { name: "get", description: "Fetch full issue details as JSON" },
|
|
@@ -1370,7 +1380,7 @@ var issue = defineCommand6({
|
|
|
1370
1380
|
import { defineCommand as defineCommand7 } from "citty";
|
|
1371
1381
|
|
|
1372
1382
|
// src/plan/index.ts
|
|
1373
|
-
import { resolve as
|
|
1383
|
+
import { resolve as resolve5 } from "path";
|
|
1374
1384
|
import * as clack4 from "@clack/prompts";
|
|
1375
1385
|
|
|
1376
1386
|
// src/plan/brainstorm.ts
|
|
@@ -1455,10 +1465,230 @@ ALWAYS respond with a single JSON object (no markdown fences, no extra text):
|
|
|
1455
1465
|
Output ONLY the JSON object.`;
|
|
1456
1466
|
}
|
|
1457
1467
|
|
|
1468
|
+
// src/plan/plan-checker.ts
|
|
1469
|
+
import { mkdtempSync as mkdtempSync2 } from "fs";
|
|
1470
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1471
|
+
import { join as join3, resolve as resolve4 } from "path";
|
|
1472
|
+
function buildPlanValidationPrompt(goal, issues, contextMd) {
|
|
1473
|
+
const contextBlock = buildContextMdBlock(contextMd);
|
|
1474
|
+
const issuesBlock = issues.map((issue2) => {
|
|
1475
|
+
const deps = issue2.dependsOn.length > 0 ? ` (depends on: ${issue2.dependsOn.join(", ")})` : "";
|
|
1476
|
+
const verify = issue2.verifyCommand ? `
|
|
1477
|
+
Verify: ${issue2.verifyCommand}` : "";
|
|
1478
|
+
const done = issue2.doneCriteria ? `
|
|
1479
|
+
Done: ${issue2.doneCriteria}` : "";
|
|
1480
|
+
const files = issue2.relevantFiles.length > 0 ? `
|
|
1481
|
+
Files: ${issue2.relevantFiles.join(", ")}` : "";
|
|
1482
|
+
const criteria = issue2.acceptanceCriteria.length > 0 ? `
|
|
1483
|
+
Criteria: ${issue2.acceptanceCriteria.join("; ")}` : "";
|
|
1484
|
+
return `${issue2.order}. ${issue2.title}${deps}${files}${criteria}${verify}${done}`;
|
|
1485
|
+
}).join("\n\n");
|
|
1486
|
+
return `You are a plan quality validator. Your ONLY task is to evaluate whether an implementation plan is well-structured and complete. Do NOT modify any files or run any commands.
|
|
1487
|
+
|
|
1488
|
+
Always respond in the same language the user wrote their goal in.
|
|
1489
|
+
|
|
1490
|
+
## Goal
|
|
1491
|
+
|
|
1492
|
+
${goal}
|
|
1493
|
+
${contextBlock}
|
|
1494
|
+
## Plan to Validate
|
|
1495
|
+
|
|
1496
|
+
${issuesBlock}
|
|
1497
|
+
|
|
1498
|
+
## Evaluation Dimensions
|
|
1499
|
+
|
|
1500
|
+
Evaluate the plan across these 6 dimensions:
|
|
1501
|
+
|
|
1502
|
+
1. **Requirement Coverage**: Does the plan fully address the stated goal? Are there aspects of the goal that no issue covers?
|
|
1503
|
+
2. **Task Atomicity**: Is each issue small enough to complete in a single AI coding session (under 1 hour)? Are any issues too broad or too granular?
|
|
1504
|
+
3. **Dependency Correctness**: Are dependencies properly ordered? Are there missing dependencies where one issue clearly requires another to be completed first?
|
|
1505
|
+
4. **File Scope**: Is the file scope per task reasonable? Do multiple issues modify the same files (merge conflict risk)?
|
|
1506
|
+
5. **Verification**: Does each issue have testable acceptance criteria or a verify command? Can completion be objectively determined?
|
|
1507
|
+
6. **Gap Detection**: Are there missing implementation steps? Would executing all issues actually achieve the goal?
|
|
1508
|
+
|
|
1509
|
+
## Response Format
|
|
1510
|
+
|
|
1511
|
+
Respond with ONLY a valid JSON object \u2014 no markdown fences, no explanation, no other text:
|
|
1512
|
+
|
|
1513
|
+
{
|
|
1514
|
+
"passed": true,
|
|
1515
|
+
"findings": [
|
|
1516
|
+
{ "dimension": "requirement_coverage", "severity": "low", "description": "Minor: no logging added", "suggestion": "Consider adding a logging issue" }
|
|
1517
|
+
],
|
|
1518
|
+
"refinedPlan": null
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
When "passed" is false, include a "refinedPlan" with the corrected issues:
|
|
1522
|
+
|
|
1523
|
+
{
|
|
1524
|
+
"passed": false,
|
|
1525
|
+
"findings": [
|
|
1526
|
+
{ "dimension": "gap_detection", "severity": "high", "description": "Missing database migration step", "suggestion": "Add an issue for the migration", "issueOrder": 2 }
|
|
1527
|
+
],
|
|
1528
|
+
"refinedPlan": {
|
|
1529
|
+
"issues": [
|
|
1530
|
+
{ "title": "...", "description": "...", "acceptanceCriteria": ["..."], "relevantFiles": ["..."], "order": 1, "dependsOn": [], "verifyCommand": "...", "doneCriteria": "..." }
|
|
1531
|
+
]
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
IMPORTANT:
|
|
1536
|
+
- Set "passed" to false ONLY for high-severity findings that would cause implementation failure.
|
|
1537
|
+
- Medium and low findings are informational \u2014 the plan can still pass.
|
|
1538
|
+
- Do NOT create, edit, or modify any files.
|
|
1539
|
+
- Do NOT run any shell commands.
|
|
1540
|
+
- ONLY output the JSON object above.`;
|
|
1541
|
+
}
|
|
1542
|
+
function parsePlanValidationResponse(output) {
|
|
1543
|
+
const jsonPatterns = [
|
|
1544
|
+
/\{[\s\S]*"findings"[\s\S]*\}/,
|
|
1545
|
+
/```(?:json)?\s*(\{[\s\S]*"findings"[\s\S]*\})\s*```/
|
|
1546
|
+
];
|
|
1547
|
+
for (const pattern of jsonPatterns) {
|
|
1548
|
+
const match = pattern.exec(output);
|
|
1549
|
+
if (match) {
|
|
1550
|
+
const jsonStr = match[1] ?? match[0];
|
|
1551
|
+
try {
|
|
1552
|
+
const parsed = JSON.parse(jsonStr);
|
|
1553
|
+
if (Array.isArray(parsed.findings)) {
|
|
1554
|
+
const hasHighSeverity = parsed.findings.some((f) => f.severity === "high");
|
|
1555
|
+
return {
|
|
1556
|
+
passed: hasHighSeverity ? false : parsed.passed !== false,
|
|
1557
|
+
findings: parsed.findings,
|
|
1558
|
+
refinedIssues: parseRefinedIssues(parsed)
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
} catch {
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
function parseRefinedIssues(parsed) {
|
|
1568
|
+
const refined = parsed.refinedPlan;
|
|
1569
|
+
if (!refined?.issues || !Array.isArray(refined.issues)) return void 0;
|
|
1570
|
+
return refined.issues.filter(
|
|
1571
|
+
(i) => typeof i === "object" && i !== null && typeof i.title === "string"
|
|
1572
|
+
).map((issue2, idx) => ({
|
|
1573
|
+
title: String(issue2.title),
|
|
1574
|
+
description: typeof issue2.description === "string" ? issue2.description : "",
|
|
1575
|
+
acceptanceCriteria: Array.isArray(issue2.acceptanceCriteria) ? issue2.acceptanceCriteria.filter((c) => typeof c === "string") : [],
|
|
1576
|
+
relevantFiles: Array.isArray(issue2.relevantFiles) ? issue2.relevantFiles.filter((f) => typeof f === "string") : [],
|
|
1577
|
+
order: typeof issue2.order === "number" ? issue2.order : idx + 1,
|
|
1578
|
+
dependsOn: Array.isArray(issue2.dependsOn) ? issue2.dependsOn.filter((d) => typeof d === "number") : [],
|
|
1579
|
+
repo: typeof issue2.repo === "string" ? issue2.repo : void 0,
|
|
1580
|
+
verifyCommand: typeof issue2.verifyCommand === "string" ? issue2.verifyCommand : void 0,
|
|
1581
|
+
doneCriteria: typeof issue2.doneCriteria === "string" ? issue2.doneCriteria : void 0
|
|
1582
|
+
}));
|
|
1583
|
+
}
|
|
1584
|
+
async function validateAndRefinePlan(goal, issues, config2) {
|
|
1585
|
+
const maxIterations = config2.plan_validation?.max_iterations ?? 2;
|
|
1586
|
+
const workspace = resolve4(config2.workspace);
|
|
1587
|
+
const contextMd = readContext(workspace);
|
|
1588
|
+
const models = resolveModels(config2);
|
|
1589
|
+
const logDir = mkdtempSync2(join3(tmpdir2(), "lisa-plan-check-"));
|
|
1590
|
+
const logFile = join3(logDir, "plan-check.log");
|
|
1591
|
+
let currentIssues = issues;
|
|
1592
|
+
const allFindings = [];
|
|
1593
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
1594
|
+
const prompt = buildPlanValidationPrompt(goal, currentIssues, contextMd);
|
|
1595
|
+
const result = await runWithFallback(models, prompt, {
|
|
1596
|
+
logFile,
|
|
1597
|
+
cwd: workspace,
|
|
1598
|
+
sessionTimeout: 120
|
|
1599
|
+
});
|
|
1600
|
+
if (!result.success) {
|
|
1601
|
+
warn("Plan validation failed \u2014 skipping quality gate.");
|
|
1602
|
+
break;
|
|
1603
|
+
}
|
|
1604
|
+
const validation = parsePlanValidationResponse(result.output);
|
|
1605
|
+
if (!validation) {
|
|
1606
|
+
warn("Could not parse validation response \u2014 skipping quality gate.");
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
allFindings.push(...validation.findings);
|
|
1610
|
+
if (validation.passed) {
|
|
1611
|
+
ok(
|
|
1612
|
+
`Plan validated (iteration ${iteration + 1}): ${validation.findings.length} finding(s).`
|
|
1613
|
+
);
|
|
1614
|
+
break;
|
|
1615
|
+
}
|
|
1616
|
+
if (validation.refinedIssues && validation.refinedIssues.length > 0) {
|
|
1617
|
+
log(
|
|
1618
|
+
`Plan refined (iteration ${iteration + 1}/${maxIterations}): ${validation.findings.filter((f) => f.severity === "high").length} high-severity finding(s).`
|
|
1619
|
+
);
|
|
1620
|
+
currentIssues = validation.refinedIssues;
|
|
1621
|
+
} else {
|
|
1622
|
+
warn("Validation failed but no refined plan provided \u2014 using current plan.");
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return { issues: currentIssues, findings: allFindings };
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// src/plan/validate.ts
|
|
1630
|
+
function detectDependencyCycles(issues) {
|
|
1631
|
+
const issueByOrder = /* @__PURE__ */ new Map();
|
|
1632
|
+
for (const issue2 of issues) {
|
|
1633
|
+
issueByOrder.set(issue2.order, issue2);
|
|
1634
|
+
}
|
|
1635
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
1636
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
1637
|
+
for (const issue2 of issues) {
|
|
1638
|
+
adjacency.set(issue2.order, []);
|
|
1639
|
+
inDegree.set(issue2.order, 0);
|
|
1640
|
+
}
|
|
1641
|
+
for (const issue2 of issues) {
|
|
1642
|
+
for (const dep of issue2.dependsOn) {
|
|
1643
|
+
if (!adjacency.has(dep)) continue;
|
|
1644
|
+
adjacency.get(dep).push(issue2.order);
|
|
1645
|
+
inDegree.set(issue2.order, (inDegree.get(issue2.order) ?? 0) + 1);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
const queue = [];
|
|
1649
|
+
for (const [order, degree] of inDegree) {
|
|
1650
|
+
if (degree === 0) queue.push(order);
|
|
1651
|
+
}
|
|
1652
|
+
const sorted = [];
|
|
1653
|
+
while (queue.length > 0) {
|
|
1654
|
+
const node = queue.shift();
|
|
1655
|
+
sorted.push(node);
|
|
1656
|
+
for (const neighbor of adjacency.get(node) ?? []) {
|
|
1657
|
+
const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
|
|
1658
|
+
inDegree.set(neighbor, newDegree);
|
|
1659
|
+
if (newDegree === 0) queue.push(neighbor);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (sorted.length === issues.length) return null;
|
|
1663
|
+
const sortedSet = new Set(sorted);
|
|
1664
|
+
const cycleNodes = issues.filter((i) => !sortedSet.has(i.order)).map((i) => `#${i.order} "${i.title}"`);
|
|
1665
|
+
return [`Circular dependency involving: ${cycleNodes.join(" -> ")}`];
|
|
1666
|
+
}
|
|
1667
|
+
function detectFileOverlaps(issues) {
|
|
1668
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
1669
|
+
for (const issue2 of issues) {
|
|
1670
|
+
for (const file of issue2.relevantFiles) {
|
|
1671
|
+
const existing = fileMap.get(file);
|
|
1672
|
+
if (existing) {
|
|
1673
|
+
existing.push(issue2.order);
|
|
1674
|
+
} else {
|
|
1675
|
+
fileMap.set(file, [issue2.order]);
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
const overlaps = [];
|
|
1680
|
+
for (const [file, issueOrders] of fileMap) {
|
|
1681
|
+
if (issueOrders.length >= 2) {
|
|
1682
|
+
overlaps.push({ file, issues: issueOrders });
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
return overlaps;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1458
1688
|
// src/plan/index.ts
|
|
1459
1689
|
async function runPlan(opts) {
|
|
1460
1690
|
const { config: config2 } = opts;
|
|
1461
|
-
const workspace =
|
|
1691
|
+
const workspace = resolve5(config2.workspace);
|
|
1462
1692
|
if (opts.continueLatest) {
|
|
1463
1693
|
const latest = loadLatestPlan(workspace);
|
|
1464
1694
|
if (!latest)
|
|
@@ -1498,11 +1728,41 @@ async function runPlan(opts) {
|
|
|
1498
1728
|
}
|
|
1499
1729
|
}
|
|
1500
1730
|
}
|
|
1501
|
-
|
|
1731
|
+
let validatedIssues = await generatePlan(refinedGoal, config2, { parentDescription });
|
|
1732
|
+
const cycles = detectDependencyCycles(validatedIssues);
|
|
1733
|
+
if (cycles) {
|
|
1734
|
+
warn(`Dependency cycles detected: ${cycles.join(", ")}`);
|
|
1735
|
+
validatedIssues = await generatePlan(refinedGoal, config2, {
|
|
1736
|
+
parentDescription,
|
|
1737
|
+
feedback: `Fix dependency cycles: ${cycles.join(", ")}. Ensure no circular dependencies.`
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
const overlaps = detectFileOverlaps(validatedIssues);
|
|
1741
|
+
if (overlaps.length > 0) {
|
|
1742
|
+
for (const o of overlaps) {
|
|
1743
|
+
clack4.log.warning(
|
|
1744
|
+
`File ${o.file} touched by issues ${o.issues.join(", ")} \u2014 merge conflict risk`
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (config2.plan_validation?.enabled && !opts.jsonOutput) {
|
|
1749
|
+
log("Validating plan quality...");
|
|
1750
|
+
const { issues: validated, findings } = await validateAndRefinePlan(
|
|
1751
|
+
refinedGoal,
|
|
1752
|
+
validatedIssues,
|
|
1753
|
+
config2
|
|
1754
|
+
);
|
|
1755
|
+
validatedIssues = validated;
|
|
1756
|
+
for (const f of findings) {
|
|
1757
|
+
if (f.severity === "high") {
|
|
1758
|
+
clack4.log.warning(`[${f.dimension}] ${f.description}`);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1502
1762
|
const plan2 = {
|
|
1503
1763
|
goal: refinedGoal,
|
|
1504
1764
|
sourceIssueId: opts.issueId,
|
|
1505
|
-
issues,
|
|
1765
|
+
issues: validatedIssues,
|
|
1506
1766
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1507
1767
|
status: "draft",
|
|
1508
1768
|
brainstormHistory
|
|
@@ -1533,7 +1793,7 @@ async function reviewAndCreate(plan2, planPath, opts) {
|
|
|
1533
1793
|
});
|
|
1534
1794
|
if (clack4.isCancel(confirm3) || !confirm3) {
|
|
1535
1795
|
plan2.status = "draft";
|
|
1536
|
-
savePlan(
|
|
1796
|
+
savePlan(resolve5(config2.workspace), plan2);
|
|
1537
1797
|
log("Plan saved. Resume with: lisa plan --continue");
|
|
1538
1798
|
return;
|
|
1539
1799
|
}
|
|
@@ -1542,7 +1802,7 @@ async function reviewAndCreate(plan2, planPath, opts) {
|
|
|
1542
1802
|
const createdIds = await createPlanIssues(source, config2.source_config, plan2, config2.workspace);
|
|
1543
1803
|
plan2.status = "created";
|
|
1544
1804
|
plan2.createdIssueIds = createdIds;
|
|
1545
|
-
savePlan(
|
|
1805
|
+
savePlan(resolve5(config2.workspace), plan2);
|
|
1546
1806
|
ok(`${createdIds.length} issue${createdIds.length !== 1 ? "s" : ""} created.`);
|
|
1547
1807
|
for (let i = 0; i < createdIds.length; i++) {
|
|
1548
1808
|
log(` ${plan2.issues[i].order}. ${createdIds[i]}: ${plan2.issues[i].title}`);
|
|
@@ -1555,13 +1815,15 @@ async function reviewAndCreate(plan2, planPath, opts) {
|
|
|
1555
1815
|
log("Run `lisa run` when ready.");
|
|
1556
1816
|
return;
|
|
1557
1817
|
}
|
|
1558
|
-
const { runLoop: runLoop2 } = await import("./loop-
|
|
1818
|
+
const { runLoop: runLoop2 } = await import("./loop-732CLNLZ.js");
|
|
1819
|
+
const waves = buildExecutionWaves(plan2.issues);
|
|
1820
|
+
const maxWaveSize = Math.max(...waves.map((w) => w.length));
|
|
1559
1821
|
await runLoop2(config2, {
|
|
1560
1822
|
once: false,
|
|
1561
1823
|
watch: false,
|
|
1562
1824
|
limit: createdIds.length,
|
|
1563
1825
|
dryRun: false,
|
|
1564
|
-
concurrency: 1
|
|
1826
|
+
concurrency: maxWaveSize > 1 ? maxWaveSize : 1
|
|
1565
1827
|
});
|
|
1566
1828
|
}
|
|
1567
1829
|
|
|
@@ -1628,7 +1890,7 @@ var plan = defineCommand7({
|
|
|
1628
1890
|
});
|
|
1629
1891
|
|
|
1630
1892
|
// src/cli/commands/run.ts
|
|
1631
|
-
import { resolve as
|
|
1893
|
+
import { resolve as resolve6 } from "path";
|
|
1632
1894
|
import { defineCommand as defineCommand8 } from "citty";
|
|
1633
1895
|
import pc6 from "picocolors";
|
|
1634
1896
|
|
|
@@ -2018,12 +2280,12 @@ Add them to your ${shell} and run: source ${shell}`));
|
|
|
2018
2280
|
let onBeforeExit;
|
|
2019
2281
|
let persistedCards;
|
|
2020
2282
|
if (isTTY) {
|
|
2021
|
-
const workspace =
|
|
2283
|
+
const workspace = resolve6(merged.workspace);
|
|
2022
2284
|
const persistence = createKanbanPersistence(workspace);
|
|
2023
2285
|
const initialCards = persistence.load();
|
|
2024
2286
|
persistedCards = initialCards;
|
|
2025
2287
|
persistence.start();
|
|
2026
|
-
const { registerPlanBridge } = await import("./tui-bridge-
|
|
2288
|
+
const { registerPlanBridge } = await import("./tui-bridge-TSGCSJV4.js");
|
|
2027
2289
|
const cleanupPlan = registerPlanBridge(merged);
|
|
2028
2290
|
onBeforeExit = () => {
|
|
2029
2291
|
persistence.stop();
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
checkoutBaseBranches,
|
|
4
4
|
runDemoLoop,
|
|
5
5
|
runLoop
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-PGIXWLQT.js";
|
|
7
7
|
import {
|
|
8
8
|
WATCH_POLL_INTERVAL_MS
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-6VIN5PMW.js";
|
|
10
10
|
import "./chunk-V44FTYWZ.js";
|
|
11
11
|
import "./chunk-3EOEDL3T.js";
|
|
12
12
|
import "./chunk-7OCDGYDM.js";
|
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
markdownToIssue,
|
|
7
7
|
parseStructuredOutput,
|
|
8
8
|
savePlan
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-AFKXWCAM.js";
|
|
10
10
|
import {
|
|
11
11
|
createSource,
|
|
12
12
|
resolveModels,
|
|
13
13
|
runWithFallback
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-6VIN5PMW.js";
|
|
15
15
|
import {
|
|
16
16
|
error,
|
|
17
17
|
kanbanEmitter,
|