gsd-pi 2.37.1-dev.d3ace49 → 2.38.0-dev.96dc7fb
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 +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto-loop.js +7 -1
- package/dist/resources/extensions/gsd/auto-start.js +6 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +11 -4
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands.js +20 -1
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- 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 +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/gsd/auto-loop.ts +13 -1
- package/src/resources/extensions/gsd/auto-start.ts +7 -1
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +12 -3
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands.ts +21 -1
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/subagent/index.ts +12 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } 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, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
|
|
4
|
+
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } 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 } from "./doctor-format.js";
|
|
12
|
+
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } 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,10 +324,68 @@ 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
|
+
}
|
|
327
384
|
export async function runGSDDoctor(basePath, options) {
|
|
328
385
|
const issues = [];
|
|
329
386
|
const fixesApplied = [];
|
|
330
387
|
const fix = options?.fix === true;
|
|
388
|
+
const dryRun = options?.dryRun === true;
|
|
331
389
|
const fixLevel = options?.fixLevel ?? "all";
|
|
332
390
|
// Issue codes that represent completion state transitions — creating summary
|
|
333
391
|
// stubs, marking slices/milestones done in the roadmap. These belong to the
|
|
@@ -336,12 +394,18 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
336
394
|
// detected and reported but never auto-fixed.
|
|
337
395
|
/** Whether a given issue code should be auto-fixed at the current fixLevel. */
|
|
338
396
|
const shouldFix = (code) => {
|
|
339
|
-
if (!fix)
|
|
397
|
+
if (!fix || dryRun)
|
|
340
398
|
return false;
|
|
341
399
|
if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))
|
|
342
400
|
return false;
|
|
343
401
|
return true;
|
|
344
402
|
};
|
|
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
|
+
};
|
|
345
409
|
const prefs = loadEffectiveGSDPreferences();
|
|
346
410
|
if (prefs) {
|
|
347
411
|
const prefIssues = validatePreferenceShape(prefs.preferences);
|
|
@@ -357,18 +421,30 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
357
421
|
});
|
|
358
422
|
}
|
|
359
423
|
}
|
|
360
|
-
// Git health checks
|
|
424
|
+
// Git health checks — timed
|
|
425
|
+
const t0git = Date.now();
|
|
361
426
|
const isolationMode = options?.isolationMode ??
|
|
362
427
|
(prefs?.preferences?.git?.isolation === "none" ? "none" :
|
|
363
428
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
|
|
364
429
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
365
|
-
|
|
430
|
+
const gitMs = Date.now() - t0git;
|
|
431
|
+
// Runtime health checks — timed
|
|
432
|
+
const t0runtime = Date.now();
|
|
366
433
|
await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
|
|
367
|
-
|
|
368
|
-
|
|
434
|
+
const runtimeMs = Date.now() - t0runtime;
|
|
435
|
+
// Environment health checks — timed
|
|
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;
|
|
369
443
|
const milestonesPath = milestonesDir(basePath);
|
|
370
444
|
if (!existsSync(milestonesPath)) {
|
|
371
|
-
|
|
445
|
+
const report = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
|
|
446
|
+
await appendDoctorHistory(basePath, report);
|
|
447
|
+
return report;
|
|
372
448
|
}
|
|
373
449
|
const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
|
|
374
450
|
const requirementsContent = await loadFile(requirementsPath);
|
|
@@ -432,6 +508,46 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
432
508
|
if (!roadmapContent)
|
|
433
509
|
continue;
|
|
434
510
|
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 */ }
|
|
435
551
|
for (const slice of roadmap.slices) {
|
|
436
552
|
const unitId = `${milestoneId}/${slice.id}`;
|
|
437
553
|
if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId)
|
|
@@ -502,6 +618,34 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
502
618
|
}
|
|
503
619
|
continue;
|
|
504
620
|
}
|
|
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 */ }
|
|
505
649
|
let allTasksDone = plan.tasks.length > 0;
|
|
506
650
|
for (const task of plan.tasks) {
|
|
507
651
|
const taskUnitId = `${unitId}/${task.id}`;
|
|
@@ -517,6 +661,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
517
661
|
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
|
|
518
662
|
fixable: true,
|
|
519
663
|
});
|
|
664
|
+
dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
|
|
520
665
|
if (shouldFix("task_done_missing_summary")) {
|
|
521
666
|
const stubPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks", `${task.id}-SUMMARY.md`);
|
|
522
667
|
const stubContent = [
|
|
@@ -575,6 +720,22 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
575
720
|
}
|
|
576
721
|
}
|
|
577
722
|
}
|
|
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
|
+
}
|
|
578
739
|
allTasksDone = allTasksDone && task.done;
|
|
579
740
|
}
|
|
580
741
|
// Blocker-without-replan detection
|
|
@@ -604,6 +765,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
604
765
|
}
|
|
605
766
|
}
|
|
606
767
|
}
|
|
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
|
+
}
|
|
607
774
|
const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
|
|
608
775
|
const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
|
|
609
776
|
const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
|
|
@@ -618,6 +785,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
618
785
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
619
786
|
fixable: true,
|
|
620
787
|
});
|
|
788
|
+
dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
|
|
621
789
|
if (shouldFix("all_tasks_done_missing_slice_summary"))
|
|
622
790
|
await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
623
791
|
}
|
|
@@ -631,6 +799,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
631
799
|
file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
|
|
632
800
|
fixable: true,
|
|
633
801
|
});
|
|
802
|
+
dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
|
|
634
803
|
if (shouldFix("all_tasks_done_missing_slice_uat"))
|
|
635
804
|
await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
636
805
|
}
|
|
@@ -644,6 +813,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
644
813
|
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
645
814
|
fixable: true,
|
|
646
815
|
});
|
|
816
|
+
dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
|
|
647
817
|
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
|
|
648
818
|
await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
649
819
|
}
|
|
@@ -696,13 +866,16 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
696
866
|
});
|
|
697
867
|
}
|
|
698
868
|
}
|
|
699
|
-
if (fix && fixesApplied.length > 0) {
|
|
869
|
+
if (fix && !dryRun && fixesApplied.length > 0) {
|
|
700
870
|
await updateStateFile(basePath, fixesApplied);
|
|
701
871
|
}
|
|
702
|
-
|
|
872
|
+
const report = {
|
|
703
873
|
ok: issues.every(issue => issue.severity !== "error"),
|
|
704
874
|
basePath,
|
|
705
875
|
issues,
|
|
706
876
|
fixesApplied,
|
|
877
|
+
timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
|
|
707
878
|
};
|
|
879
|
+
await appendDoctorHistory(basePath, report);
|
|
880
|
+
return report;
|
|
708
881
|
}
|
|
@@ -57,42 +57,61 @@ 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
|
+
}
|
|
60
82
|
/**
|
|
61
83
|
* Detect the active worktree name from the current working directory.
|
|
62
84
|
* Returns null if not inside a GSD worktree (.gsd/worktrees/<name>/).
|
|
63
85
|
*/
|
|
64
86
|
export function detectWorktreeName(basePath) {
|
|
65
87
|
const normalizedPath = basePath.replaceAll("\\", "/");
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
if (idx === -1)
|
|
88
|
+
const seg = findWorktreeSegment(normalizedPath);
|
|
89
|
+
if (!seg)
|
|
69
90
|
return null;
|
|
70
|
-
const afterMarker = normalizedPath.slice(
|
|
91
|
+
const afterMarker = normalizedPath.slice(seg.afterWorktrees);
|
|
71
92
|
const name = afterMarker.split("/")[0];
|
|
72
93
|
return name || null;
|
|
73
94
|
}
|
|
74
95
|
/**
|
|
75
96
|
* Resolve the project root from a path that may be inside a worktree.
|
|
76
|
-
* If the path contains
|
|
77
|
-
*
|
|
97
|
+
* If the path contains a worktrees segment, returns the portion before
|
|
98
|
+
* `/.gsd/`. Otherwise returns the input unchanged.
|
|
78
99
|
*
|
|
79
100
|
* Use this in commands that call `process.cwd()` to ensure they always
|
|
80
101
|
* operate against the real project root, not a worktree subdirectory.
|
|
81
102
|
*/
|
|
82
103
|
export function resolveProjectRoot(basePath) {
|
|
83
104
|
const normalizedPath = basePath.replaceAll("\\", "/");
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
if (idx === -1)
|
|
105
|
+
const seg = findWorktreeSegment(normalizedPath);
|
|
106
|
+
if (!seg)
|
|
87
107
|
return basePath;
|
|
88
|
-
// Return the original path up to the
|
|
89
|
-
// Account for potential OS-specific separators
|
|
108
|
+
// Return the original path up to the /.gsd/ boundary
|
|
90
109
|
const sep = basePath.includes("\\") ? "\\" : "/";
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
return basePath.slice(0,
|
|
95
|
-
return basePath.slice(0,
|
|
110
|
+
const gsdMarker = `${sep}.gsd${sep}`;
|
|
111
|
+
const gsdIdx = basePath.indexOf(gsdMarker);
|
|
112
|
+
if (gsdIdx !== -1)
|
|
113
|
+
return basePath.slice(0, gsdIdx);
|
|
114
|
+
return basePath.slice(0, seg.gsdIdx);
|
|
96
115
|
}
|
|
97
116
|
/**
|
|
98
117
|
* 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, directionOrSurfaceId, 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,7 +397,12 @@ async function runSingleAgentInCmuxSplit(cmuxClient, direction, defaultCwd, agen
|
|
|
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
|
-
|
|
400
|
+
// Accept either a pre-created surface ID or a direction to create a new split
|
|
401
|
+
const isDirection = directionOrSurfaceId === "right" || directionOrSurfaceId === "down"
|
|
402
|
+
|| directionOrSurfaceId === "left" || directionOrSurfaceId === "up";
|
|
403
|
+
const cmuxSurfaceId = isDirection
|
|
404
|
+
? await cmuxClient.createSplit(directionOrSurfaceId)
|
|
405
|
+
: directionOrSurfaceId;
|
|
401
406
|
if (!cmuxSurfaceId) {
|
|
402
407
|
return runSingleAgent(defaultCwd, agents, agentName, task, cwd, step, signal, onUpdate, makeDetails);
|
|
403
408
|
}
|
|
@@ -656,10 +661,14 @@ export default function (pi) {
|
|
|
656
661
|
const MAX_RETRIES = 1; // Retry failed tasks once
|
|
657
662
|
const batchId = crypto.randomUUID();
|
|
658
663
|
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
|
+
: [];
|
|
659
668
|
const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
|
|
660
669
|
const workerId = registerWorker(t.agent, t.task, index, batchSize, batchId);
|
|
661
670
|
const runTask = () => cmuxSplitsEnabled
|
|
662
|
-
? runSingleAgentInCmuxSplit(cmuxClient, index % 2 === 0 ? "right" : "down", ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
671
|
+
? runSingleAgentInCmuxSplit(cmuxClient, gridSurfaces[index] ?? (index % 2 === 0 ? "right" : "down"), ctx.cwd, agents, t.agent, t.task, t.cwd, undefined, signal, (partial) => {
|
|
663
672
|
if (partial.details?.results[0]) {
|
|
664
673
|
allResults[index] = partial.details.results[0];
|
|
665
674
|
emitParallelUpdate();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Welcome Screen
|
|
3
|
+
*
|
|
4
|
+
* Rendered to stderr before the TUI takes over.
|
|
5
|
+
* No box, no panels — logo with metadata alongside, dim hint below.
|
|
6
|
+
*/
|
|
7
|
+
export interface WelcomeScreenOptions {
|
|
8
|
+
version: string;
|
|
9
|
+
modelName?: string;
|
|
10
|
+
provider?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function printWelcomeScreen(opts: WelcomeScreenOptions): void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Welcome Screen
|
|
3
|
+
*
|
|
4
|
+
* Rendered to stderr before the TUI takes over.
|
|
5
|
+
* No box, no panels — logo with metadata alongside, dim hint below.
|
|
6
|
+
*/
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { GSD_LOGO } from './logo.js';
|
|
10
|
+
function getShortCwd() {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
return cwd.startsWith(home) ? '~' + cwd.slice(home.length) : cwd;
|
|
14
|
+
}
|
|
15
|
+
export function printWelcomeScreen(opts) {
|
|
16
|
+
if (!process.stderr.isTTY)
|
|
17
|
+
return;
|
|
18
|
+
const { version, modelName, provider } = opts;
|
|
19
|
+
const shortCwd = getShortCwd();
|
|
20
|
+
// Info lines to sit alongside the logo (one per logo row)
|
|
21
|
+
const modelLine = [modelName, provider].filter(Boolean).join(' · ');
|
|
22
|
+
const INFO = [
|
|
23
|
+
` ${chalk.bold('Get Shit Done')} ${chalk.dim('v' + version)}`,
|
|
24
|
+
undefined,
|
|
25
|
+
modelLine ? ` ${chalk.dim(modelLine)}` : undefined,
|
|
26
|
+
` ${chalk.dim(shortCwd)}`,
|
|
27
|
+
undefined,
|
|
28
|
+
undefined,
|
|
29
|
+
];
|
|
30
|
+
const lines = [''];
|
|
31
|
+
for (let i = 0; i < GSD_LOGO.length; i++) {
|
|
32
|
+
lines.push(chalk.cyan(GSD_LOGO[i]) + (INFO[i] ?? ''));
|
|
33
|
+
}
|
|
34
|
+
// Tool status + hint — dim, aligned under the info text
|
|
35
|
+
const pad = ' '.repeat(28) + ' '; // aligns with the info text column
|
|
36
|
+
const toolParts = [];
|
|
37
|
+
if (process.env.BRAVE_API_KEY)
|
|
38
|
+
toolParts.push('Brave ✓');
|
|
39
|
+
if (process.env.BRAVE_ANSWERS_KEY)
|
|
40
|
+
toolParts.push('Answers ✓');
|
|
41
|
+
if (process.env.JINA_API_KEY)
|
|
42
|
+
toolParts.push('Jina ✓');
|
|
43
|
+
if (process.env.TAVILY_API_KEY)
|
|
44
|
+
toolParts.push('Tavily ✓');
|
|
45
|
+
if (process.env.CONTEXT7_API_KEY)
|
|
46
|
+
toolParts.push('Context7 ✓');
|
|
47
|
+
if (toolParts.length > 0) {
|
|
48
|
+
lines.push(chalk.dim(pad + ['Web search loaded', ...toolParts].join(' · ')));
|
|
49
|
+
}
|
|
50
|
+
lines.push(chalk.dim(pad + '/gsd to begin · /gsd help for all commands'));
|
|
51
|
+
lines.push('');
|
|
52
|
+
process.stderr.write(lines.join('\n') + '\n');
|
|
53
|
+
}
|
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,7 +292,13 @@ 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
|
|
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
|
+
}
|
|
296
302
|
const entries = [];
|
|
297
303
|
for (const extPath of manifest.extensions) {
|
|
298
304
|
const resolvedExtPath = resolve(dir, extPath);
|
|
@@ -300,9 +306,7 @@ function resolveExtensionEntries(dir) {
|
|
|
300
306
|
entries.push(resolvedExtPath);
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
|
-
|
|
304
|
-
return entries;
|
|
305
|
-
}
|
|
309
|
+
return entries.length > 0 ? entries : null;
|
|
306
310
|
}
|
|
307
311
|
}
|
|
308
312
|
const indexTs = join(dir, "index.ts");
|