gsd-pi 2.38.0-dev.96dc7fb → 2.38.0-dev.eeb3520
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/cli.js +0 -9
- package/dist/extension-discovery.d.ts +3 -5
- package/dist/extension-discovery.js +9 -14
- package/dist/resources/extensions/browser-tools/package.json +1 -3
- package/dist/resources/extensions/cmux/index.js +1 -55
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/google-search/package.json +1 -3
- package/dist/resources/extensions/gsd/auto-loop.js +1 -7
- package/dist/resources/extensions/gsd/auto-start.js +1 -6
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -11
- package/dist/resources/extensions/gsd/captures.js +1 -9
- package/dist/resources/extensions/gsd/commands-handlers.js +3 -16
- package/dist/resources/extensions/gsd/commands.js +1 -20
- package/dist/resources/extensions/gsd/doctor-checks.js +0 -82
- package/dist/resources/extensions/gsd/doctor-environment.js +0 -78
- package/dist/resources/extensions/gsd/doctor-format.js +0 -15
- package/dist/resources/extensions/gsd/doctor.js +11 -184
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/worktree.js +16 -35
- package/dist/resources/extensions/subagent/index.js +3 -12
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +4 -8
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/src/core/package-manager.ts +4 -8
- package/src/resources/extensions/cmux/index.ts +1 -57
- package/src/resources/extensions/gsd/auto-loop.ts +1 -13
- package/src/resources/extensions/gsd/auto-start.ts +1 -7
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -12
- package/src/resources/extensions/gsd/captures.ts +1 -10
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -17
- package/src/resources/extensions/gsd/commands.ts +1 -21
- package/src/resources/extensions/gsd/doctor-checks.ts +0 -75
- package/src/resources/extensions/gsd/doctor-environment.ts +1 -82
- package/src/resources/extensions/gsd/doctor-format.ts +0 -20
- package/src/resources/extensions/gsd/doctor-types.ts +1 -16
- package/src/resources/extensions/gsd/doctor.ts +13 -177
- package/src/resources/extensions/gsd/tests/cmux.test.ts +0 -93
- package/src/resources/extensions/gsd/tests/worktree.test.ts +0 -47
- package/src/resources/extensions/gsd/worktree.ts +15 -35
- package/src/resources/extensions/subagent/index.ts +3 -12
- package/dist/welcome-screen.d.ts +0 -12
- package/dist/welcome-screen.js +0 -53
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +0 -266
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
4
|
-
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir,
|
|
4
|
+
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
|
|
5
5
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
6
6
|
import { invalidateAllCaches } from "./cache.js";
|
|
7
7
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
@@ -9,7 +9,7 @@ import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
|
9
9
|
import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
|
|
10
10
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
11
11
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
12
|
-
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt
|
|
12
|
+
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
|
|
13
13
|
export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport } from "./doctor-environment.js";
|
|
14
14
|
export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport } from "./progress-score.js";
|
|
15
15
|
/**
|
|
@@ -324,68 +324,10 @@ export async function selectDoctorScope(basePath, requestedScope) {
|
|
|
324
324
|
}
|
|
325
325
|
return state.registry[0]?.id;
|
|
326
326
|
}
|
|
327
|
-
// ── Helper: circular dependency detection ──────────────────────────────────
|
|
328
|
-
function detectCircularDependencies(slices) {
|
|
329
|
-
const known = new Set(slices.map(s => s.id));
|
|
330
|
-
const adj = new Map();
|
|
331
|
-
for (const s of slices)
|
|
332
|
-
adj.set(s.id, s.depends.filter(d => known.has(d)));
|
|
333
|
-
const state = new Map();
|
|
334
|
-
for (const s of slices)
|
|
335
|
-
state.set(s.id, "unvisited");
|
|
336
|
-
const cycles = [];
|
|
337
|
-
function dfs(id, path) {
|
|
338
|
-
const st = state.get(id);
|
|
339
|
-
if (st === "done")
|
|
340
|
-
return;
|
|
341
|
-
if (st === "visiting") {
|
|
342
|
-
cycles.push([...path.slice(path.indexOf(id)), id]);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
state.set(id, "visiting");
|
|
346
|
-
for (const dep of adj.get(id) ?? [])
|
|
347
|
-
dfs(dep, [...path, id]);
|
|
348
|
-
state.set(id, "done");
|
|
349
|
-
}
|
|
350
|
-
for (const s of slices)
|
|
351
|
-
if (state.get(s.id) === "unvisited")
|
|
352
|
-
dfs(s.id, []);
|
|
353
|
-
return cycles;
|
|
354
|
-
}
|
|
355
|
-
async function appendDoctorHistory(basePath, report) {
|
|
356
|
-
try {
|
|
357
|
-
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
358
|
-
const entry = JSON.stringify({
|
|
359
|
-
ts: new Date().toISOString(),
|
|
360
|
-
ok: report.ok,
|
|
361
|
-
errors: report.issues.filter(i => i.severity === "error").length,
|
|
362
|
-
warnings: report.issues.filter(i => i.severity === "warning").length,
|
|
363
|
-
fixes: report.fixesApplied.length,
|
|
364
|
-
codes: [...new Set(report.issues.map(i => i.code))],
|
|
365
|
-
});
|
|
366
|
-
const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
|
|
367
|
-
await saveFile(historyPath, existing + entry + "\n");
|
|
368
|
-
}
|
|
369
|
-
catch { /* non-fatal */ }
|
|
370
|
-
}
|
|
371
|
-
/** Read the last N doctor history entries. Returns most-recent-first. */
|
|
372
|
-
export async function readDoctorHistory(basePath, lastN = 50) {
|
|
373
|
-
try {
|
|
374
|
-
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
375
|
-
if (!existsSync(historyPath))
|
|
376
|
-
return [];
|
|
377
|
-
const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
|
|
378
|
-
return lines.slice(-lastN).reverse().map(l => JSON.parse(l));
|
|
379
|
-
}
|
|
380
|
-
catch {
|
|
381
|
-
return [];
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
327
|
export async function runGSDDoctor(basePath, options) {
|
|
385
328
|
const issues = [];
|
|
386
329
|
const fixesApplied = [];
|
|
387
330
|
const fix = options?.fix === true;
|
|
388
|
-
const dryRun = options?.dryRun === true;
|
|
389
331
|
const fixLevel = options?.fixLevel ?? "all";
|
|
390
332
|
// Issue codes that represent completion state transitions — creating summary
|
|
391
333
|
// stubs, marking slices/milestones done in the roadmap. These belong to the
|
|
@@ -394,18 +336,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
394
336
|
// detected and reported but never auto-fixed.
|
|
395
337
|
/** Whether a given issue code should be auto-fixed at the current fixLevel. */
|
|
396
338
|
const shouldFix = (code) => {
|
|
397
|
-
if (!fix
|
|
339
|
+
if (!fix)
|
|
398
340
|
return false;
|
|
399
341
|
if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))
|
|
400
342
|
return false;
|
|
401
343
|
return true;
|
|
402
344
|
};
|
|
403
|
-
/** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
|
|
404
|
-
const dryRunCanFix = (code, message) => {
|
|
405
|
-
if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
|
|
406
|
-
fixesApplied.push(`[dry-run] would fix: ${message}`);
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
345
|
const prefs = loadEffectiveGSDPreferences();
|
|
410
346
|
if (prefs) {
|
|
411
347
|
const prefIssues = validatePreferenceShape(prefs.preferences);
|
|
@@ -421,30 +357,18 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
421
357
|
});
|
|
422
358
|
}
|
|
423
359
|
}
|
|
424
|
-
// Git health checks
|
|
425
|
-
const t0git = Date.now();
|
|
360
|
+
// Git health checks (orphaned worktrees, stale branches, corrupt merge state, tracked runtime files)
|
|
426
361
|
const isolationMode = options?.isolationMode ??
|
|
427
362
|
(prefs?.preferences?.git?.isolation === "none" ? "none" :
|
|
428
363
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
|
|
429
364
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
430
|
-
|
|
431
|
-
// Runtime health checks — timed
|
|
432
|
-
const t0runtime = Date.now();
|
|
365
|
+
// Runtime health checks (crash locks, completed-units, hook state, activity logs, STATE.md, gitignore)
|
|
433
366
|
await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const t0env = Date.now();
|
|
437
|
-
await checkEnvironmentHealth(basePath, issues, {
|
|
438
|
-
includeRemote: !options?.scope,
|
|
439
|
-
includeBuild: options?.includeBuild,
|
|
440
|
-
includeTests: options?.includeTests,
|
|
441
|
-
});
|
|
442
|
-
const envMs = Date.now() - t0env;
|
|
367
|
+
// Environment health checks (#1221: missing tools, port conflicts, stale deps, disk space)
|
|
368
|
+
await checkEnvironmentHealth(basePath, issues, { includeRemote: !options?.scope });
|
|
443
369
|
const milestonesPath = milestonesDir(basePath);
|
|
444
370
|
if (!existsSync(milestonesPath)) {
|
|
445
|
-
|
|
446
|
-
await appendDoctorHistory(basePath, report);
|
|
447
|
-
return report;
|
|
371
|
+
return { ok: issues.every(issue => issue.severity !== "error"), basePath, issues, fixesApplied };
|
|
448
372
|
}
|
|
449
373
|
const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
|
|
450
374
|
const requirementsContent = await loadFile(requirementsPath);
|
|
@@ -508,46 +432,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
508
432
|
if (!roadmapContent)
|
|
509
433
|
continue;
|
|
510
434
|
const roadmap = parseRoadmap(roadmapContent);
|
|
511
|
-
// ── Circular dependency detection ──────────────────────────────────────
|
|
512
|
-
for (const cycle of detectCircularDependencies(roadmap.slices)) {
|
|
513
|
-
issues.push({
|
|
514
|
-
severity: "error",
|
|
515
|
-
code: "circular_slice_dependency",
|
|
516
|
-
scope: "milestone",
|
|
517
|
-
unitId: milestoneId,
|
|
518
|
-
message: `Circular dependency detected: ${cycle.join(" → ")}`,
|
|
519
|
-
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
520
|
-
fixable: false,
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
// ── Orphaned slice directories ─────────────────────────────────────────
|
|
524
|
-
try {
|
|
525
|
-
const slicesDir = join(milestonePath, "slices");
|
|
526
|
-
if (existsSync(slicesDir)) {
|
|
527
|
-
const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
|
|
528
|
-
for (const entry of readdirSync(slicesDir)) {
|
|
529
|
-
try {
|
|
530
|
-
if (!lstatSync(join(slicesDir, entry)).isDirectory())
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
catch {
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
if (!knownSliceIds.has(entry)) {
|
|
537
|
-
issues.push({
|
|
538
|
-
severity: "warning",
|
|
539
|
-
code: "orphaned_slice_directory",
|
|
540
|
-
scope: "milestone",
|
|
541
|
-
unitId: milestoneId,
|
|
542
|
-
message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
|
|
543
|
-
file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
|
|
544
|
-
fixable: false,
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
catch { /* non-fatal */ }
|
|
551
435
|
for (const slice of roadmap.slices) {
|
|
552
436
|
const unitId = `${milestoneId}/${slice.id}`;
|
|
553
437
|
if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId)
|
|
@@ -618,34 +502,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
618
502
|
}
|
|
619
503
|
continue;
|
|
620
504
|
}
|
|
621
|
-
// ── Duplicate task IDs ───────────────────────────────────────────────
|
|
622
|
-
const taskIdCounts = new Map();
|
|
623
|
-
for (const task of plan.tasks)
|
|
624
|
-
taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
|
|
625
|
-
for (const [taskId, count] of taskIdCounts) {
|
|
626
|
-
if (count > 1) {
|
|
627
|
-
issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
|
|
628
|
-
message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
|
|
629
|
-
file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
// ── Task files on disk not in plan ────────────────────────────────────
|
|
633
|
-
try {
|
|
634
|
-
if (tasksDir) {
|
|
635
|
-
const planTaskIds = new Set(plan.tasks.map(t => t.id));
|
|
636
|
-
for (const f of readdirSync(tasksDir)) {
|
|
637
|
-
if (!f.endsWith("-SUMMARY.md"))
|
|
638
|
-
continue;
|
|
639
|
-
const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
|
|
640
|
-
if (!planTaskIds.has(diskTaskId)) {
|
|
641
|
-
issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
|
|
642
|
-
message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
|
|
643
|
-
file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
catch { /* non-fatal */ }
|
|
649
505
|
let allTasksDone = plan.tasks.length > 0;
|
|
650
506
|
for (const task of plan.tasks) {
|
|
651
507
|
const taskUnitId = `${unitId}/${task.id}`;
|
|
@@ -661,7 +517,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
661
517
|
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
|
|
662
518
|
fixable: true,
|
|
663
519
|
});
|
|
664
|
-
dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
|
|
665
520
|
if (shouldFix("task_done_missing_summary")) {
|
|
666
521
|
const stubPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks", `${task.id}-SUMMARY.md`);
|
|
667
522
|
const stubContent = [
|
|
@@ -720,22 +575,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
720
575
|
}
|
|
721
576
|
}
|
|
722
577
|
}
|
|
723
|
-
// ── Future timestamp check ─────────────────────────────────────
|
|
724
|
-
if (task.done && hasSummary && summaryPath) {
|
|
725
|
-
try {
|
|
726
|
-
const rawSummary = await loadFile(summaryPath);
|
|
727
|
-
const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
|
|
728
|
-
if (m) {
|
|
729
|
-
const ts = new Date(m[1].trim());
|
|
730
|
-
if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
|
|
731
|
-
issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
|
|
732
|
-
message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
|
|
733
|
-
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
catch { /* non-fatal */ }
|
|
738
|
-
}
|
|
739
578
|
allTasksDone = allTasksDone && task.done;
|
|
740
579
|
}
|
|
741
580
|
// Blocker-without-replan detection
|
|
@@ -765,12 +604,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
765
604
|
}
|
|
766
605
|
}
|
|
767
606
|
}
|
|
768
|
-
// ── Stale REPLAN: exists but all tasks done ────────────────────────
|
|
769
|
-
if (replanPath && allTasksDone) {
|
|
770
|
-
issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
|
|
771
|
-
message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
|
|
772
|
-
file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
|
|
773
|
-
}
|
|
774
607
|
const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
|
|
775
608
|
const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
|
|
776
609
|
const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
|
|
@@ -785,7 +618,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
785
618
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
786
619
|
fixable: true,
|
|
787
620
|
});
|
|
788
|
-
dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
|
|
789
621
|
if (shouldFix("all_tasks_done_missing_slice_summary"))
|
|
790
622
|
await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
791
623
|
}
|
|
@@ -799,7 +631,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
799
631
|
file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
|
|
800
632
|
fixable: true,
|
|
801
633
|
});
|
|
802
|
-
dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
|
|
803
634
|
if (shouldFix("all_tasks_done_missing_slice_uat"))
|
|
804
635
|
await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
805
636
|
}
|
|
@@ -813,7 +644,6 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
813
644
|
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
814
645
|
fixable: true,
|
|
815
646
|
});
|
|
816
|
-
dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
|
|
817
647
|
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
|
|
818
648
|
await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
819
649
|
}
|
|
@@ -866,16 +696,13 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
866
696
|
});
|
|
867
697
|
}
|
|
868
698
|
}
|
|
869
|
-
if (fix &&
|
|
699
|
+
if (fix && fixesApplied.length > 0) {
|
|
870
700
|
await updateStateFile(basePath, fixesApplied);
|
|
871
701
|
}
|
|
872
|
-
|
|
702
|
+
return {
|
|
873
703
|
ok: issues.every(issue => issue.severity !== "error"),
|
|
874
704
|
basePath,
|
|
875
705
|
issues,
|
|
876
706
|
fixesApplied,
|
|
877
|
-
timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
|
|
878
707
|
};
|
|
879
|
-
await appendDoctorHistory(basePath, report);
|
|
880
|
-
return report;
|
|
881
708
|
}
|
|
@@ -57,61 +57,42 @@ export function captureIntegrationBranch(basePath, milestoneId, options) {
|
|
|
57
57
|
writeIntegrationBranch(basePath, milestoneId, current, options);
|
|
58
58
|
}
|
|
59
59
|
// ─── Pure Utility Functions (unchanged) ────────────────────────────────────
|
|
60
|
-
/**
|
|
61
|
-
* Find the worktrees segment in a path, supporting both direct
|
|
62
|
-
* (`/.gsd/worktrees/`) and symlink-resolved (`/.gsd/projects/<hash>/worktrees/`)
|
|
63
|
-
* layouts. When `.gsd` is a symlink to `~/.gsd/projects/<hash>`, resolved
|
|
64
|
-
* paths contain the intermediate `projects/<hash>/` segment that the old
|
|
65
|
-
* single-marker check missed.
|
|
66
|
-
*/
|
|
67
|
-
function findWorktreeSegment(normalizedPath) {
|
|
68
|
-
// Direct layout: /.gsd/worktrees/<name>
|
|
69
|
-
const directMarker = "/.gsd/worktrees/";
|
|
70
|
-
const idx = normalizedPath.indexOf(directMarker);
|
|
71
|
-
if (idx !== -1) {
|
|
72
|
-
return { gsdIdx: idx, afterWorktrees: idx + directMarker.length };
|
|
73
|
-
}
|
|
74
|
-
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/<name>
|
|
75
|
-
const symlinkRe = /\/\.gsd\/projects\/[a-f0-9]+\/worktrees\//;
|
|
76
|
-
const match = normalizedPath.match(symlinkRe);
|
|
77
|
-
if (match && match.index !== undefined) {
|
|
78
|
-
return { gsdIdx: match.index, afterWorktrees: match.index + match[0].length };
|
|
79
|
-
}
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
60
|
/**
|
|
83
61
|
* Detect the active worktree name from the current working directory.
|
|
84
62
|
* Returns null if not inside a GSD worktree (.gsd/worktrees/<name>/).
|
|
85
63
|
*/
|
|
86
64
|
export function detectWorktreeName(basePath) {
|
|
87
65
|
const normalizedPath = basePath.replaceAll("\\", "/");
|
|
88
|
-
const
|
|
89
|
-
|
|
66
|
+
const marker = "/.gsd/worktrees/";
|
|
67
|
+
const idx = normalizedPath.indexOf(marker);
|
|
68
|
+
if (idx === -1)
|
|
90
69
|
return null;
|
|
91
|
-
const afterMarker = normalizedPath.slice(
|
|
70
|
+
const afterMarker = normalizedPath.slice(idx + marker.length);
|
|
92
71
|
const name = afterMarker.split("/")[0];
|
|
93
72
|
return name || null;
|
|
94
73
|
}
|
|
95
74
|
/**
|
|
96
75
|
* Resolve the project root from a path that may be inside a worktree.
|
|
97
|
-
* If the path contains
|
|
98
|
-
* `/.gsd/`. Otherwise returns the input unchanged.
|
|
76
|
+
* If the path contains `/.gsd/worktrees/<name>/`, returns the portion
|
|
77
|
+
* before `/.gsd/`. Otherwise returns the input unchanged.
|
|
99
78
|
*
|
|
100
79
|
* Use this in commands that call `process.cwd()` to ensure they always
|
|
101
80
|
* operate against the real project root, not a worktree subdirectory.
|
|
102
81
|
*/
|
|
103
82
|
export function resolveProjectRoot(basePath) {
|
|
104
83
|
const normalizedPath = basePath.replaceAll("\\", "/");
|
|
105
|
-
const
|
|
106
|
-
|
|
84
|
+
const marker = "/.gsd/worktrees/";
|
|
85
|
+
const idx = normalizedPath.indexOf(marker);
|
|
86
|
+
if (idx === -1)
|
|
107
87
|
return basePath;
|
|
108
|
-
// Return the original path up to the
|
|
88
|
+
// Return the original path up to the .gsd/ marker (un-normalized)
|
|
89
|
+
// Account for potential OS-specific separators
|
|
109
90
|
const sep = basePath.includes("\\") ? "\\" : "/";
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
return basePath.slice(0,
|
|
114
|
-
return basePath.slice(0,
|
|
91
|
+
const markerOs = `${sep}.gsd${sep}worktrees${sep}`;
|
|
92
|
+
const idxOs = basePath.indexOf(markerOs);
|
|
93
|
+
if (idxOs !== -1)
|
|
94
|
+
return basePath.slice(0, idxOs);
|
|
95
|
+
return basePath.slice(0, idx);
|
|
115
96
|
}
|
|
116
97
|
/**
|
|
117
98
|
* Get the slice branch name, namespaced by worktree when inside one.
|
|
@@ -360,7 +360,7 @@ async function runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, si
|
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
362
|
}
|
|
363
|
-
async function runSingleAgentInCmuxSplit(cmuxClient,
|
|
363
|
+
async function runSingleAgentInCmuxSplit(cmuxClient, direction, defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails) {
|
|
364
364
|
const agent = agents.find((a) => a.name === agentName);
|
|
365
365
|
if (!agent) {
|
|
366
366
|
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
@@ -397,12 +397,7 @@ async function runSingleAgentInCmuxSplit(cmuxClient, directionOrSurfaceId, defau
|
|
|
397
397
|
const stdoutPath = path.join(tmpOutputDir, "stdout.jsonl");
|
|
398
398
|
const stderrPath = path.join(tmpOutputDir, "stderr.log");
|
|
399
399
|
const exitPath = path.join(tmpOutputDir, "exit.code");
|
|
400
|
-
|
|
401
|
-
const isDirection = directionOrSurfaceId === "right" || directionOrSurfaceId === "down"
|
|
402
|
-
|| directionOrSurfaceId === "left" || directionOrSurfaceId === "up";
|
|
403
|
-
const cmuxSurfaceId = isDirection
|
|
404
|
-
? await cmuxClient.createSplit(directionOrSurfaceId)
|
|
405
|
-
: directionOrSurfaceId;
|
|
400
|
+
const cmuxSurfaceId = await cmuxClient.createSplit(direction);
|
|
406
401
|
if (!cmuxSurfaceId) {
|
|
407
402
|
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
408
403
|
}
|
|
@@ -661,14 +656,10 @@ export default function (pi) {
|
|
|
661
656
|
const MAX_RETRIES = 1; // Retry failed tasks once
|
|
662
657
|
const batchId = crypto.randomUUID();
|
|
663
658
|
const batchSize = params.tasks.length;
|
|
664
|
-
// Pre-create a grid layout for cmux splits so agents get a clean tiled arrangement
|
|
665
|
-
const gridSurfaces = cmuxSplitsEnabled
|
|
666
|
-
? await cmuxClient.createGridLayout(Math.min(batchSize, MAX_CONCURRENCY))
|
|
667
|
-
: [];
|
|
668
659
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
669
660
|
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
670
661
|
const runTask = () => cmuxSplitsEnabled
|
|
671
|
-
? runSingleAgentInCmuxSplit(cmuxClient,
|
|
662
|
+
? runSingleAgentInCmuxSplit(cmuxClient, index % 2 === 0 ? "right" : "down", ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
672
663
|
if (partial.details?.results[0]) {
|
|
673
664
|
allResults[index] = partial.details.results[0];
|
|
674
665
|
emitParallelUpdate();
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../src/core/package-manager.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAU5E,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,CAAC;IAClD,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE9D,MAAM,WAAW,cAAc;IAC9B,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC9F,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,uBAAuB,CACtB,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAChD,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1B,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;IAC5E,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;IACjF,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,SAAS,GAAG,IAAI,CAAC;IAClE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;CAChF;AAED,UAAU,qBAAqB;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,eAAe,CAAC;CACjC;AAED,KAAK,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../src/core/package-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -292,13 +292,7 @@ function resolveExtensionEntries(dir) {
|
|
|
292
292
|
const packageJsonPath = join(dir, "package.json");
|
|
293
293
|
if (existsSync(packageJsonPath)) {
|
|
294
294
|
const manifest = readPiManifestFile(packageJsonPath);
|
|
295
|
-
if (manifest) {
|
|
296
|
-
// When a pi manifest exists, it is authoritative — don't fall through
|
|
297
|
-
// to index.ts/index.js auto-detection. This allows library directories
|
|
298
|
-
// (like cmux) to opt out by declaring "pi": {} with no extensions.
|
|
299
|
-
if (!manifest.extensions?.length) {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
295
|
+
if (manifest?.extensions?.length) {
|
|
302
296
|
const entries = [];
|
|
303
297
|
for (const extPath of manifest.extensions) {
|
|
304
298
|
const resolvedExtPath = resolve(dir, extPath);
|
|
@@ -306,7 +300,9 @@ function resolveExtensionEntries(dir) {
|
|
|
306
300
|
entries.push(resolvedExtPath);
|
|
307
301
|
}
|
|
308
302
|
}
|
|
309
|
-
|
|
303
|
+
if (entries.length > 0) {
|
|
304
|
+
return entries;
|
|
305
|
+
}
|
|
310
306
|
}
|
|
311
307
|
}
|
|
312
308
|
const indexTs = join(dir, "index.ts");
|