gsd-pi 2.30.0-dev.92a3417 → 2.30.0-dev.ab42fba
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 +51 -0
- package/dist/help-text.js +35 -0
- package/dist/resources/extensions/aws-auth/index.ts +144 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +65 -186
- package/dist/resources/extensions/gsd/auto-prompts.ts +2 -10
- package/dist/resources/extensions/gsd/auto-start.ts +3 -10
- package/dist/resources/extensions/gsd/auto-worktree.ts +12 -8
- package/dist/resources/extensions/gsd/auto.ts +2 -2
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +2 -12
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -4
- package/dist/resources/extensions/gsd/git-service.ts +4 -22
- package/dist/resources/extensions/gsd/gitignore.ts +6 -7
- package/dist/resources/extensions/gsd/guided-flow-queue.ts +3 -7
- package/dist/resources/extensions/gsd/guided-flow.ts +8 -11
- package/dist/resources/extensions/gsd/index.ts +13 -0
- package/dist/resources/extensions/gsd/init-wizard.ts +2 -30
- package/dist/resources/extensions/gsd/preferences-types.ts +0 -2
- package/dist/resources/extensions/gsd/preferences-validation.ts +1 -2
- package/dist/resources/extensions/gsd/roadmap-slices.ts +22 -7
- package/dist/resources/extensions/gsd/session-lock.ts +53 -4
- package/dist/resources/extensions/gsd/templates/preferences.md +0 -1
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +14 -42
- package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +3 -9
- package/dist/resources/extensions/gsd/tests/preferences.test.ts +1 -9
- package/dist/resources/extensions/gsd/tests/worktree.test.ts +1 -4
- package/dist/resources/extensions/gsd/worktree.ts +2 -2
- package/dist/worktree-cli.d.ts +34 -0
- package/dist/worktree-cli.js +294 -0
- package/dist/worktree-name-gen.d.ts +7 -0
- package/dist/worktree-name-gen.js +44 -0
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +14 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +4 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/runner.js +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +14 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +5 -0
- package/packages/pi-coding-agent/src/core/extensions/runner.ts +1 -0
- package/packages/pi-coding-agent/src/core/extensions/types.ts +8 -0
- package/src/resources/extensions/aws-auth/index.ts +144 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +65 -186
- package/src/resources/extensions/gsd/auto-prompts.ts +2 -10
- package/src/resources/extensions/gsd/auto-start.ts +3 -10
- package/src/resources/extensions/gsd/auto-worktree.ts +12 -8
- package/src/resources/extensions/gsd/auto.ts +2 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -12
- package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -4
- package/src/resources/extensions/gsd/git-service.ts +4 -22
- package/src/resources/extensions/gsd/gitignore.ts +6 -7
- package/src/resources/extensions/gsd/guided-flow-queue.ts +3 -7
- package/src/resources/extensions/gsd/guided-flow.ts +8 -11
- package/src/resources/extensions/gsd/index.ts +13 -0
- package/src/resources/extensions/gsd/init-wizard.ts +2 -30
- package/src/resources/extensions/gsd/preferences-types.ts +0 -2
- package/src/resources/extensions/gsd/preferences-validation.ts +1 -2
- package/src/resources/extensions/gsd/roadmap-slices.ts +22 -7
- package/src/resources/extensions/gsd/session-lock.ts +53 -4
- package/src/resources/extensions/gsd/templates/preferences.md +0 -1
- package/src/resources/extensions/gsd/tests/git-service.test.ts +14 -42
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +3 -9
- package/src/resources/extensions/gsd/tests/preferences.test.ts +1 -9
- package/src/resources/extensions/gsd/tests/worktree.test.ts +1 -4
- package/src/resources/extensions/gsd/worktree.ts +2 -2
|
@@ -205,13 +205,6 @@ export function estimateTimeRemaining(): string | null {
|
|
|
205
205
|
|
|
206
206
|
// ─── Slice Progress Cache ─────────────────────────────────────────────────────
|
|
207
207
|
|
|
208
|
-
/** Cached task detail for the widget task checklist */
|
|
209
|
-
interface CachedTaskDetail {
|
|
210
|
-
id: string;
|
|
211
|
-
title: string;
|
|
212
|
-
done: boolean;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
208
|
/** Cached slice progress for the widget — avoid async in render */
|
|
216
209
|
let cachedSliceProgress: {
|
|
217
210
|
done: number;
|
|
@@ -219,8 +212,6 @@ let cachedSliceProgress: {
|
|
|
219
212
|
milestoneId: string;
|
|
220
213
|
/** Real task progress for the active slice, if its plan file exists */
|
|
221
214
|
activeSliceTasks: { done: number; total: number } | null;
|
|
222
|
-
/** Full task list for the active slice checklist */
|
|
223
|
-
taskDetails: CachedTaskDetail[] | null;
|
|
224
215
|
} | null = null;
|
|
225
216
|
|
|
226
217
|
export function updateSliceProgressCache(base: string, mid: string, activeSid?: string): void {
|
|
@@ -231,7 +222,6 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
231
222
|
const roadmap = parseRoadmap(content);
|
|
232
223
|
|
|
233
224
|
let activeSliceTasks: { done: number; total: number } | null = null;
|
|
234
|
-
let taskDetails: CachedTaskDetail[] | null = null;
|
|
235
225
|
if (activeSid) {
|
|
236
226
|
try {
|
|
237
227
|
const planFile = resolveSliceFile(base, mid, activeSid, "PLAN");
|
|
@@ -242,7 +232,6 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
242
232
|
done: plan.tasks.filter(t => t.done).length,
|
|
243
233
|
total: plan.tasks.length,
|
|
244
234
|
};
|
|
245
|
-
taskDetails = plan.tasks.map(t => ({ id: t.id, title: t.title, done: t.done }));
|
|
246
235
|
}
|
|
247
236
|
} catch {
|
|
248
237
|
// Non-fatal — just omit task count
|
|
@@ -254,19 +243,13 @@ export function updateSliceProgressCache(base: string, mid: string, activeSid?:
|
|
|
254
243
|
total: roadmap.slices.length,
|
|
255
244
|
milestoneId: mid,
|
|
256
245
|
activeSliceTasks,
|
|
257
|
-
taskDetails,
|
|
258
246
|
};
|
|
259
247
|
} catch {
|
|
260
248
|
// Non-fatal — widget just won't show progress bar
|
|
261
249
|
}
|
|
262
250
|
}
|
|
263
251
|
|
|
264
|
-
export function getRoadmapSlicesSync(): {
|
|
265
|
-
done: number;
|
|
266
|
-
total: number;
|
|
267
|
-
activeSliceTasks: { done: number; total: number } | null;
|
|
268
|
-
taskDetails: CachedTaskDetail[] | null;
|
|
269
|
-
} | null {
|
|
252
|
+
export function getRoadmapSlicesSync(): { done: number; total: number; activeSliceTasks: { done: number; total: number } | null } | null {
|
|
270
253
|
return cachedSliceProgress;
|
|
271
254
|
}
|
|
272
255
|
|
|
@@ -367,84 +350,87 @@ export function updateProgressWidget(
|
|
|
367
350
|
const lines: string[] = [];
|
|
368
351
|
const pad = INDENT.base;
|
|
369
352
|
|
|
370
|
-
// ── Top bar
|
|
353
|
+
// ── Line 1: Top bar ───────────────────────────────────────────────
|
|
371
354
|
lines.push(...ui.bar());
|
|
372
355
|
|
|
373
|
-
// ── Header: GSD AUTO ... elapsed ────────────────────────────────
|
|
374
356
|
const dot = pulseBright
|
|
375
357
|
? theme.fg("accent", GLYPH.statusActive)
|
|
376
358
|
: theme.fg("dim", GLYPH.statusPending);
|
|
377
359
|
const elapsed = formatAutoElapsed(accessors.getAutoStartTime());
|
|
378
360
|
const modeTag = accessors.isStepMode() ? "NEXT" : "AUTO";
|
|
379
|
-
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))}
|
|
361
|
+
const headerLeft = `${pad}${dot} ${theme.fg("accent", theme.bold("GSD"))} ${theme.fg("success", modeTag)}`;
|
|
380
362
|
const headerRight = elapsed ? theme.fg("dim", elapsed) : "";
|
|
381
363
|
lines.push(rightAlign(headerLeft, headerRight, width));
|
|
382
364
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (mid)
|
|
365
|
+
lines.push("");
|
|
366
|
+
|
|
367
|
+
if (mid) {
|
|
368
|
+
lines.push(truncateToWidth(`${pad}${theme.fg("dim", mid.title)}`, width));
|
|
369
|
+
}
|
|
370
|
+
|
|
386
371
|
if (slice && unitType !== "research-milestone" && unitType !== "plan-milestone") {
|
|
387
|
-
|
|
372
|
+
lines.push(truncateToWidth(
|
|
373
|
+
`${pad}${theme.fg("text", theme.bold(`${slice.id}: ${slice.title}`))}`,
|
|
374
|
+
width,
|
|
375
|
+
));
|
|
388
376
|
}
|
|
377
|
+
|
|
378
|
+
lines.push("");
|
|
379
|
+
|
|
389
380
|
const isHook = unitType.startsWith("hook/");
|
|
390
381
|
const target = isHook
|
|
391
382
|
? (unitId.split("/").pop() ?? unitId)
|
|
392
383
|
: (task ? `${task.id}: ${task.title}` : unitId);
|
|
393
|
-
|
|
394
|
-
|
|
384
|
+
const actionLeft = `${pad}${theme.fg("accent", "▸")} ${theme.fg("accent", verb)} ${theme.fg("text", target)}`;
|
|
395
385
|
const tierTag = tierBadge ? theme.fg("dim", `[${tierBadge}] `) : "";
|
|
396
386
|
const phaseBadge = `${tierTag}${theme.fg("dim", phaseLabel)}`;
|
|
397
|
-
|
|
398
|
-
lines.push(
|
|
399
|
-
|
|
400
|
-
// ── Two-column body ─────────────────────────────────────────────
|
|
401
|
-
// Left: progress, ETA, next, stats (fixed) | Right: task checklist (fixed, adjacent)
|
|
402
|
-
// Both columns sit left-to-center; empty space is on the right.
|
|
403
|
-
const divider = theme.fg("dim", "│");
|
|
404
|
-
const minTwoColWidth = 100;
|
|
405
|
-
const rightColFixed = 44;
|
|
406
|
-
const colGap = 5; // breathing room between columns
|
|
407
|
-
// Left column takes remaining space — no truncation on wide terminals
|
|
408
|
-
const useTwoCol = width >= minTwoColWidth;
|
|
409
|
-
const rightColWidth = useTwoCol ? rightColFixed : 0;
|
|
410
|
-
const leftColWidth = useTwoCol ? width - rightColWidth - colGap : width;
|
|
411
|
-
|
|
412
|
-
const roadmapSlices = mid ? getRoadmapSlicesSync() : null;
|
|
413
|
-
|
|
414
|
-
// Build left column: progress bar, ETA, next step, token stats
|
|
415
|
-
const leftLines: string[] = [];
|
|
416
|
-
|
|
417
|
-
if (roadmapSlices) {
|
|
418
|
-
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
419
|
-
const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
|
|
420
|
-
const pct = total > 0 ? done / total : 0;
|
|
421
|
-
const filled = Math.round(pct * barWidth);
|
|
422
|
-
const bar = theme.fg("success", "█".repeat(filled))
|
|
423
|
-
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
424
|
-
|
|
425
|
-
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
426
|
-
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
427
|
-
const taskNum = isHook
|
|
428
|
-
? Math.max(activeSliceTasks.done, 1)
|
|
429
|
-
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
430
|
-
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
431
|
-
}
|
|
432
|
-
leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
|
|
387
|
+
lines.push(rightAlign(actionLeft, phaseBadge, width));
|
|
388
|
+
lines.push("");
|
|
433
389
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
390
|
+
if (mid) {
|
|
391
|
+
const roadmapSlices = getRoadmapSlicesSync();
|
|
392
|
+
if (roadmapSlices) {
|
|
393
|
+
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
394
|
+
const barWidth = Math.max(8, Math.min(24, Math.floor(width * 0.3)));
|
|
395
|
+
const pct = total > 0 ? done / total : 0;
|
|
396
|
+
const filled = Math.round(pct * barWidth);
|
|
397
|
+
const bar = theme.fg("success", "█".repeat(filled))
|
|
398
|
+
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
399
|
+
|
|
400
|
+
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
401
|
+
|
|
402
|
+
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
403
|
+
// For hooks, show the trigger task number (done), not the next task (done + 1)
|
|
404
|
+
const taskNum = isHook
|
|
405
|
+
? Math.max(activeSliceTasks.done, 1)
|
|
406
|
+
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
407
|
+
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// ETA estimate
|
|
411
|
+
const eta = estimateTimeRemaining();
|
|
412
|
+
if (eta) {
|
|
413
|
+
meta += theme.fg("dim", ` · ${eta}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
lines.push(truncateToWidth(`${pad}${bar} ${meta}`, width));
|
|
437
417
|
}
|
|
438
418
|
}
|
|
439
419
|
|
|
420
|
+
lines.push("");
|
|
421
|
+
|
|
440
422
|
if (next) {
|
|
441
|
-
|
|
423
|
+
lines.push(truncateToWidth(
|
|
442
424
|
`${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
|
|
443
|
-
|
|
425
|
+
width,
|
|
444
426
|
));
|
|
445
427
|
}
|
|
446
428
|
|
|
447
|
-
//
|
|
429
|
+
// ── Footer info (pwd, tokens, cost, context, model) ──────────────
|
|
430
|
+
lines.push("");
|
|
431
|
+
lines.push(truncateToWidth(theme.fg("dim", `${pad}${widgetPwd}`), width, theme.fg("dim", "…")));
|
|
432
|
+
|
|
433
|
+
// Token stats from current unit session + cumulative cost from metrics
|
|
448
434
|
{
|
|
449
435
|
const cmdCtx = accessors.getCmdCtx();
|
|
450
436
|
let totalInput = 0, totalOutput = 0;
|
|
@@ -479,6 +465,7 @@ export function updateProgressWidget(
|
|
|
479
465
|
if (totalOutput) sp.push(`↓${formatWidgetTokens(totalOutput)}`);
|
|
480
466
|
if (totalCacheRead) sp.push(`R${formatWidgetTokens(totalCacheRead)}`);
|
|
481
467
|
if (totalCacheWrite) sp.push(`W${formatWidgetTokens(totalCacheWrite)}`);
|
|
468
|
+
// Cache hit rate for current unit
|
|
482
469
|
if (totalCacheRead + totalInput > 0) {
|
|
483
470
|
const hitRate = Math.round((totalCacheRead / (totalCacheRead + totalInput)) * 100);
|
|
484
471
|
sp.push(`\u26A1${hitRate}%`);
|
|
@@ -497,134 +484,33 @@ export function updateProgressWidget(
|
|
|
497
484
|
sp.push(cxDisplay);
|
|
498
485
|
}
|
|
499
486
|
|
|
500
|
-
const
|
|
487
|
+
const sLeft = sp.map(p => p.includes("\x1b[") ? p : theme.fg("dim", p))
|
|
501
488
|
.join(theme.fg("dim", " "));
|
|
502
|
-
leftLines.push(truncateToWidth(`${pad}${tokenLine}`, leftColWidth));
|
|
503
489
|
|
|
504
490
|
const modelId = cmdCtx?.model?.id ?? "";
|
|
505
491
|
const modelProvider = cmdCtx?.model?.provider ?? "";
|
|
492
|
+
const modelPhase = phaseLabel ? theme.fg("dim", `[${phaseLabel}] `) : "";
|
|
506
493
|
const modelDisplay = modelProvider && modelId
|
|
507
494
|
? `${modelProvider}/${modelId}`
|
|
508
495
|
: modelId;
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
496
|
+
const sRight = modelDisplay
|
|
497
|
+
? `${modelPhase}${theme.fg("dim", modelDisplay)}`
|
|
498
|
+
: "";
|
|
499
|
+
lines.push(rightAlign(`${pad}${sLeft}`, sRight, width));
|
|
512
500
|
|
|
513
|
-
// Dynamic routing savings
|
|
501
|
+
// Dynamic routing savings summary
|
|
514
502
|
if (mLedger && mLedger.units.some(u => u.tier)) {
|
|
515
503
|
const savings = formatTierSavings(mLedger.units);
|
|
516
504
|
if (savings) {
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Build right column: task checklist (pegged to right edge)
|
|
523
|
-
const rightLines: string[] = [];
|
|
524
|
-
const taskDetails = roadmapSlices?.taskDetails ?? null;
|
|
525
|
-
const maxVisibleTasks = 8;
|
|
526
|
-
const rpad = " ";
|
|
527
|
-
|
|
528
|
-
if (useTwoCol) {
|
|
529
|
-
if (taskDetails && taskDetails.length > 0) {
|
|
530
|
-
const visibleTasks = taskDetails.slice(0, maxVisibleTasks);
|
|
531
|
-
for (const t of visibleTasks) {
|
|
532
|
-
const isCurrent = task && t.id === task.id;
|
|
533
|
-
const glyph = t.done
|
|
534
|
-
? theme.fg("success", GLYPH.statusDone)
|
|
535
|
-
: isCurrent
|
|
536
|
-
? theme.fg("accent", "▸")
|
|
537
|
-
: theme.fg("dim", " ");
|
|
538
|
-
const label = isCurrent
|
|
539
|
-
? theme.fg("text", `${t.id}: ${t.title}`)
|
|
540
|
-
: t.done
|
|
541
|
-
? theme.fg("dim", `${t.id}: ${t.title}`)
|
|
542
|
-
: theme.fg("text", `${t.id}: ${t.title}`);
|
|
543
|
-
rightLines.push(truncateToWidth(`${rpad}${glyph} ${label}`, rightColWidth));
|
|
544
|
-
}
|
|
545
|
-
if (taskDetails.length > maxVisibleTasks) {
|
|
546
|
-
rightLines.push(truncateToWidth(
|
|
547
|
-
`${rpad}${theme.fg("dim", ` …+${taskDetails.length - maxVisibleTasks} more`)}`,
|
|
548
|
-
rightColWidth,
|
|
549
|
-
));
|
|
550
|
-
}
|
|
551
|
-
} else if (roadmapSlices?.activeSliceTasks) {
|
|
552
|
-
const { done: tDone, total: tTotal } = roadmapSlices.activeSliceTasks;
|
|
553
|
-
rightLines.push(`${rpad}${theme.fg("dim", `${tDone}/${tTotal} tasks`)}`);
|
|
554
|
-
}
|
|
555
|
-
} else {
|
|
556
|
-
// Narrow single-column: task list goes into left column
|
|
557
|
-
if (taskDetails && taskDetails.length > 0) {
|
|
558
|
-
for (const t of taskDetails.slice(0, maxVisibleTasks)) {
|
|
559
|
-
const isCurrent = task && t.id === task.id;
|
|
560
|
-
const glyph = t.done
|
|
561
|
-
? theme.fg("success", GLYPH.statusDone)
|
|
562
|
-
: isCurrent
|
|
563
|
-
? theme.fg("accent", "▸")
|
|
564
|
-
: theme.fg("dim", " ");
|
|
565
|
-
const label = isCurrent
|
|
566
|
-
? theme.fg("text", `${t.id}: ${t.title}`)
|
|
567
|
-
: t.done
|
|
568
|
-
? theme.fg("dim", `${t.id}: ${t.title}`)
|
|
569
|
-
: theme.fg("text", `${t.id}: ${t.title}`);
|
|
570
|
-
leftLines.push(truncateToWidth(`${pad}${glyph} ${label}`, leftColWidth));
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
// Add progress bar inline
|
|
574
|
-
if (roadmapSlices) {
|
|
575
|
-
const { done, total, activeSliceTasks } = roadmapSlices;
|
|
576
|
-
const barWidth = Math.max(6, Math.min(18, Math.floor(leftColWidth * 0.4)));
|
|
577
|
-
const pct = total > 0 ? done / total : 0;
|
|
578
|
-
const filled = Math.round(pct * barWidth);
|
|
579
|
-
const bar = theme.fg("success", "█".repeat(filled))
|
|
580
|
-
+ theme.fg("dim", "░".repeat(barWidth - filled));
|
|
581
|
-
let meta = theme.fg("dim", `${done}/${total} slices`);
|
|
582
|
-
if (activeSliceTasks && activeSliceTasks.total > 0) {
|
|
583
|
-
const taskNum = isHook
|
|
584
|
-
? Math.max(activeSliceTasks.done, 1)
|
|
585
|
-
: Math.min(activeSliceTasks.done + 1, activeSliceTasks.total);
|
|
586
|
-
meta += theme.fg("dim", ` · task ${taskNum}/${activeSliceTasks.total}`);
|
|
505
|
+
lines.push(truncateToWidth(theme.fg("dim", `${pad}${savings}`), width));
|
|
587
506
|
}
|
|
588
|
-
const eta = estimateTimeRemaining();
|
|
589
|
-
if (eta) meta += theme.fg("dim", ` · ${eta}`);
|
|
590
|
-
leftLines.push(truncateToWidth(`${pad}${bar} ${meta}`, leftColWidth));
|
|
591
|
-
}
|
|
592
|
-
if (next) {
|
|
593
|
-
leftLines.push(truncateToWidth(
|
|
594
|
-
`${pad}${theme.fg("dim", "→")} ${theme.fg("dim", `then ${next}`)}`,
|
|
595
|
-
leftColWidth,
|
|
596
|
-
));
|
|
597
507
|
}
|
|
598
508
|
}
|
|
599
509
|
|
|
600
|
-
// Compose columns
|
|
601
|
-
if (useTwoCol) {
|
|
602
|
-
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
603
|
-
if (maxRows > 0) {
|
|
604
|
-
lines.push(""); // spacer before columns
|
|
605
|
-
for (let i = 0; i < maxRows; i++) {
|
|
606
|
-
const left = padToWidth(leftLines[i] ?? "", leftColWidth);
|
|
607
|
-
const gap = " ".repeat(colGap - 2); // colGap minus divider and its trailing space
|
|
608
|
-
const right = rightLines[i] ?? "";
|
|
609
|
-
lines.push(truncateToWidth(`${left}${gap}${divider} ${right}`, width));
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
} else {
|
|
613
|
-
// Narrow single-column: just stack
|
|
614
|
-
if (leftLines.length > 0) {
|
|
615
|
-
lines.push("");
|
|
616
|
-
for (const l of leftLines) lines.push(l);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// ── Footer: pwd + hints ─────────────────────────────────────────
|
|
621
|
-
lines.push("");
|
|
622
510
|
const hintParts: string[] = [];
|
|
623
511
|
hintParts.push("esc pause");
|
|
624
512
|
hintParts.push(process.platform === "darwin" ? "⌃⌥G dashboard" : "Ctrl+Alt+G dashboard");
|
|
625
|
-
|
|
626
|
-
const pwdStr = theme.fg("dim", widgetPwd);
|
|
627
|
-
lines.push(rightAlign(`${pad}${pwdStr}`, hintStr, width));
|
|
513
|
+
lines.push(...ui.hints(hintParts));
|
|
628
514
|
|
|
629
515
|
lines.push(...ui.bar());
|
|
630
516
|
|
|
@@ -742,10 +628,3 @@ function rightAlign(left: string, right: string, width: number): string {
|
|
|
742
628
|
const gap = Math.max(1, width - leftVis - rightVis);
|
|
743
629
|
return truncateToWidth(left + " ".repeat(gap) + right, width);
|
|
744
630
|
}
|
|
745
|
-
|
|
746
|
-
/** Pad a string with trailing spaces to fill exactly `colWidth` (ANSI-aware). */
|
|
747
|
-
function padToWidth(s: string, colWidth: number): string {
|
|
748
|
-
const vis = visibleWidth(s);
|
|
749
|
-
if (vis >= colWidth) return truncateToWidth(s, colWidth);
|
|
750
|
-
return s + " ".repeat(colWidth - vis);
|
|
751
|
-
}
|
|
@@ -732,11 +732,7 @@ export async function buildPlanSlicePrompt(
|
|
|
732
732
|
const executorContextConstraints = formatExecutorConstraints();
|
|
733
733
|
|
|
734
734
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
735
|
-
const
|
|
736
|
-
const commitDocsEnabled = prefs?.preferences?.git?.commit_docs !== false;
|
|
737
|
-
const commitInstruction = commitDocsEnabled
|
|
738
|
-
? `Commit the plan files only: \`git add ${relSlicePath(base, mid, sid)}/ .gsd/DECISIONS.md .gitignore && git commit -m "docs(${sid}): add slice plan"\`. Do not stage .gsd/STATE.md or other runtime files — the system manages those.`
|
|
739
|
-
: "Do not commit — planning docs are not tracked in git for this project.";
|
|
735
|
+
const commitInstruction = "Do not commit planning artifacts — .gsd/ is managed externally.";
|
|
740
736
|
return loadPrompt("plan-slice", {
|
|
741
737
|
workingDirectory: base,
|
|
742
738
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
|
|
@@ -1194,11 +1190,7 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1194
1190
|
// Non-fatal — captures module may not be available
|
|
1195
1191
|
}
|
|
1196
1192
|
|
|
1197
|
-
const
|
|
1198
|
-
const reassessCommitDocsEnabled = reassessPrefs?.preferences?.git?.commit_docs !== false;
|
|
1199
|
-
const reassessCommitInstruction = reassessCommitDocsEnabled
|
|
1200
|
-
? `Commit: \`docs(${mid}): reassess roadmap after ${completedSliceId}\`. Stage only the .gsd/milestones/ files you changed — do not stage .gsd/STATE.md or other runtime files.`
|
|
1201
|
-
: "Do not commit — planning docs are not tracked in git for this project.";
|
|
1193
|
+
const reassessCommitInstruction = "Do not commit planning artifacts — .gsd/ is managed externally.";
|
|
1202
1194
|
|
|
1203
1195
|
return loadPrompt("reassess-roadmap", {
|
|
1204
1196
|
workingDirectory: base,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
} from "./session-lock.js";
|
|
38
38
|
import { selfHealRuntimeRecords } from "./auto-recovery.js";
|
|
39
39
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
40
|
-
import { nativeIsRepo, nativeInit
|
|
40
|
+
import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
|
|
41
41
|
import { GitServiceImpl } from "./git-service.js";
|
|
42
42
|
import {
|
|
43
43
|
captureIntegrationBranch,
|
|
@@ -109,9 +109,8 @@ export async function bootstrapAutoSession(
|
|
|
109
109
|
|
|
110
110
|
// Ensure .gitignore has baseline patterns
|
|
111
111
|
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
112
|
-
const commitDocs = gitPrefs?.commit_docs;
|
|
113
112
|
const manageGitignore = gitPrefs?.manage_gitignore;
|
|
114
|
-
ensureGitignore(base, {
|
|
113
|
+
ensureGitignore(base, { manageGitignore });
|
|
115
114
|
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
116
115
|
|
|
117
116
|
// Migrate legacy in-project .gsd/ to external state directory
|
|
@@ -127,12 +126,6 @@ export async function bootstrapAutoSession(
|
|
|
127
126
|
const gsdDir = gsdRoot(base);
|
|
128
127
|
if (!existsSync(gsdDir)) {
|
|
129
128
|
mkdirSync(join(gsdDir, "milestones"), { recursive: true });
|
|
130
|
-
if (commitDocs !== false) {
|
|
131
|
-
try {
|
|
132
|
-
nativeAddAll(base);
|
|
133
|
-
nativeCommit(base, "chore: init gsd");
|
|
134
|
-
} catch { /* nothing to commit */ }
|
|
135
|
-
}
|
|
136
129
|
}
|
|
137
130
|
|
|
138
131
|
// Initialize GitServiceImpl
|
|
@@ -323,7 +316,7 @@ export async function bootstrapAutoSession(
|
|
|
323
316
|
// Capture integration branch
|
|
324
317
|
if (s.currentMilestoneId) {
|
|
325
318
|
if (getIsolationMode() !== "none") {
|
|
326
|
-
captureIntegrationBranch(base, s.currentMilestoneId
|
|
319
|
+
captureIntegrationBranch(base, s.currentMilestoneId);
|
|
327
320
|
}
|
|
328
321
|
setActiveMilestoneId(base, s.currentMilestoneId);
|
|
329
322
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* manages create, enter, detect, and teardown for auto-mode worktrees.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { existsSync, readFileSync, realpathSync, unlinkSync, statSync } from "node:fs";
|
|
9
|
+
import { existsSync, readFileSync, realpathSync, unlinkSync, statSync, rmSync } from "node:fs";
|
|
10
10
|
import { isAbsolute, join, sep } from "node:path";
|
|
11
11
|
import { GSDError, GSD_IO_ERROR, GSD_GIT_ERROR } from "./errors.js";
|
|
12
12
|
import { execSync, execFileSync } from "node:child_process";
|
|
@@ -370,14 +370,18 @@ export function mergeMilestoneToMain(
|
|
|
370
370
|
// squash merge can fail with "Your local changes would be overwritten" (#1127).
|
|
371
371
|
autoCommitDirtyState(originalBasePath_);
|
|
372
372
|
|
|
373
|
-
// 3b. Remove untracked .gsd/ files that syncStateToProjectRoot copied.
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
373
|
+
// 3b. Remove untracked .gsd/ runtime files that syncStateToProjectRoot copied.
|
|
374
|
+
// Only clean specific runtime files — NEVER touch milestones/, decisions, or
|
|
375
|
+
// other planning artifacts that represent user work (#1250).
|
|
376
|
+
const runtimeFilesToClean = ["STATE.md", "completed-units.json", "auto.lock", "gsd.db"];
|
|
377
|
+
for (const f of runtimeFilesToClean) {
|
|
378
|
+
const p = join(originalBasePath_, ".gsd", f);
|
|
379
|
+
try { if (existsSync(p)) unlinkSync(p); } catch { /* non-fatal */ }
|
|
380
|
+
}
|
|
378
381
|
try {
|
|
379
|
-
|
|
380
|
-
|
|
382
|
+
const runtimeDir = join(originalBasePath_, ".gsd", "runtime");
|
|
383
|
+
if (existsSync(runtimeDir)) rmSync(runtimeDir, { recursive: true, force: true });
|
|
384
|
+
} catch { /* non-fatal */ }
|
|
381
385
|
|
|
382
386
|
// 4. Resolve integration branch — prefer milestone metadata, fall back to preferences / "main"
|
|
383
387
|
const prefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
|
|
@@ -1132,7 +1132,7 @@ async function dispatchNextUnit(
|
|
|
1132
1132
|
midTitle = state.activeMilestone?.title;
|
|
1133
1133
|
|
|
1134
1134
|
if (mid) {
|
|
1135
|
-
captureIntegrationBranch(s.basePath, mid
|
|
1135
|
+
captureIntegrationBranch(s.basePath, mid);
|
|
1136
1136
|
try {
|
|
1137
1137
|
const wtPath = createAutoWorktree(s.basePath, mid);
|
|
1138
1138
|
s.basePath = wtPath;
|
|
@@ -1147,7 +1147,7 @@ async function dispatchNextUnit(
|
|
|
1147
1147
|
}
|
|
1148
1148
|
} else {
|
|
1149
1149
|
if (getIsolationMode() !== "none") {
|
|
1150
|
-
captureIntegrationBranch(s.originalBasePath || s.basePath, mid
|
|
1150
|
+
captureIntegrationBranch(s.originalBasePath || s.basePath, mid);
|
|
1151
1151
|
}
|
|
1152
1152
|
}
|
|
1153
1153
|
|
|
@@ -469,16 +469,6 @@ async function configureGit(ctx: ExtensionCommandContext, prefs: Record<string,
|
|
|
469
469
|
git.isolation = isolationChoice;
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
-
// commit_docs
|
|
473
|
-
const currentCommitDocs = git.commit_docs;
|
|
474
|
-
const commitDocsChoice = await ctx.ui.select(
|
|
475
|
-
`Track .gsd/ planning docs in git${currentCommitDocs !== undefined ? ` (current: ${currentCommitDocs})` : ""}:`,
|
|
476
|
-
["true", "false", "(keep current)"],
|
|
477
|
-
);
|
|
478
|
-
if (commitDocsChoice && commitDocsChoice !== "(keep current)") {
|
|
479
|
-
git.commit_docs = commitDocsChoice === "true";
|
|
480
|
-
}
|
|
481
|
-
|
|
482
472
|
if (Object.keys(git).length > 0) {
|
|
483
473
|
prefs.git = git;
|
|
484
474
|
}
|
|
@@ -598,13 +588,13 @@ export async function configureMode(ctx: ExtensionCommandContext, prefs: Record<
|
|
|
598
588
|
if (modeStr.startsWith("solo")) {
|
|
599
589
|
prefs.mode = "solo";
|
|
600
590
|
ctx.ui.notify(
|
|
601
|
-
"Mode: solo — defaults: auto_push=true, push_branches=false, pre_merge_check=false, merge_strategy=squash, isolation=worktree,
|
|
591
|
+
"Mode: solo — defaults: auto_push=true, push_branches=false, pre_merge_check=false, merge_strategy=squash, isolation=worktree, unique_milestone_ids=false",
|
|
602
592
|
"info",
|
|
603
593
|
);
|
|
604
594
|
} else if (modeStr.startsWith("team")) {
|
|
605
595
|
prefs.mode = "team";
|
|
606
596
|
ctx.ui.notify(
|
|
607
|
-
"Mode: team — defaults: auto_push=false, push_branches=true, pre_merge_check=true, merge_strategy=squash, isolation=worktree,
|
|
597
|
+
"Mode: team — defaults: auto_push=false, push_branches=true, pre_merge_check=true, merge_strategy=squash, isolation=worktree, unique_milestone_ids=true",
|
|
608
598
|
"info",
|
|
609
599
|
);
|
|
610
600
|
} else {
|
|
@@ -82,7 +82,6 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
82
82
|
| `git.pre_merge_check` | `false` | `true` |
|
|
83
83
|
| `git.merge_strategy` | `"squash"` | `"squash"` |
|
|
84
84
|
| `git.isolation` | `"worktree"` | `"worktree"` |
|
|
85
|
-
| `git.commit_docs` | `true` | `true` |
|
|
86
85
|
| `unique_milestone_ids` | `false` | `true` |
|
|
87
86
|
|
|
88
87
|
Quick setup: `/gsd mode` (global) or `/gsd mode project` (project-level).
|
|
@@ -127,7 +126,6 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
127
126
|
- `main_branch`: string — the primary branch name for new git repos (e.g., `"main"`, `"master"`, `"trunk"`). Also used by `getMainBranch()` as the preferred branch when auto-detection is ambiguous. Default: `"main"`.
|
|
128
127
|
- `merge_strategy`: `"squash"` or `"merge"` — controls how worktree branches are merged back. `"squash"` combines all commits into one; `"merge"` preserves individual commits. Default: `"squash"`.
|
|
129
128
|
- `isolation`: `"worktree"`, `"branch"`, or `"none"` — controls auto-mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root but creates a milestone branch (useful for submodule-heavy repos); `"none"` works directly on the current branch with no worktree or milestone branch (ideal for step-mode with hot reloads). Default: `"worktree"`.
|
|
130
|
-
- `commit_docs`: boolean — when `false`, prevents GSD from committing `.gsd/` planning artifacts to git. The `.gsd/` folder is added to `.gitignore` and kept local-only. Useful for teams where only some members use GSD, or when company policy requires a clean repository. Default: `true`.
|
|
131
129
|
- `manage_gitignore`: boolean — when `false`, GSD will not touch `.gitignore` at all. Useful when your project has a strictly managed `.gitignore` and you don't want GSD adding entries. Default: `true`.
|
|
132
130
|
- `worktree_post_create`: string — script to run after a worktree is created (both auto-mode and manual `/worktree`). Receives `SOURCE_DIR` and `WORKTREE_DIR` as environment variables. Can be absolute or relative to project root. Runs with 30-second timeout. Failure is non-fatal (logged as warning). Default: none.
|
|
133
131
|
|
|
@@ -244,7 +242,7 @@ mode: solo
|
|
|
244
242
|
---
|
|
245
243
|
```
|
|
246
244
|
|
|
247
|
-
Equivalent to setting `git.auto_push: true`, `git.push_branches: false`, `git.pre_merge_check: false`, `git.merge_strategy: squash`, `git.isolation: worktree`, `
|
|
245
|
+
Equivalent to setting `git.auto_push: true`, `git.push_branches: false`, `git.pre_merge_check: false`, `git.merge_strategy: squash`, `git.isolation: worktree`, `unique_milestone_ids: false`.
|
|
248
246
|
|
|
249
247
|
**Team — unique IDs, push branches, pre-merge checks:**
|
|
250
248
|
|
|
@@ -255,7 +253,7 @@ mode: team
|
|
|
255
253
|
---
|
|
256
254
|
```
|
|
257
255
|
|
|
258
|
-
Equivalent to setting `git.auto_push: false`, `git.push_branches: true`, `git.pre_merge_check: true`, `git.merge_strategy: squash`, `git.isolation: worktree`, `
|
|
256
|
+
Equivalent to setting `git.auto_push: false`, `git.push_branches: true`, `git.pre_merge_check: true`, `git.merge_strategy: squash`, `git.isolation: worktree`, `unique_milestone_ids: true`.
|
|
259
257
|
|
|
260
258
|
**Mode with overrides — team mode but with auto-push:**
|
|
261
259
|
|
|
@@ -50,11 +50,6 @@ export interface GitPreferences {
|
|
|
50
50
|
* - "none": no git isolation — commits land on the user's current branch directly
|
|
51
51
|
*/
|
|
52
52
|
isolation?: "worktree" | "branch" | "none";
|
|
53
|
-
/** When false, prevents GSD from committing .gsd/ planning artifacts to git.
|
|
54
|
-
* The .gsd/ folder is added to .gitignore and kept local-only.
|
|
55
|
-
* Default: true (planning docs are tracked in git).
|
|
56
|
-
*/
|
|
57
|
-
commit_docs?: boolean;
|
|
58
53
|
/** When false, GSD will not modify .gitignore at all — no baseline patterns
|
|
59
54
|
* are added and no self-healing occurs. Use this if you manage your own
|
|
60
55
|
* .gitignore and don't want GSD touching it.
|
|
@@ -226,7 +221,7 @@ export function readIntegrationBranch(basePath: string, milestoneId: string): st
|
|
|
226
221
|
*
|
|
227
222
|
* The file is committed immediately so the metadata is persisted in git.
|
|
228
223
|
*/
|
|
229
|
-
export function writeIntegrationBranch(basePath: string, milestoneId: string, branch: string
|
|
224
|
+
export function writeIntegrationBranch(basePath: string, milestoneId: string, branch: string): void {
|
|
230
225
|
// Don't record slice branches as the integration target
|
|
231
226
|
if (SLICE_BRANCH_RE.test(branch)) return;
|
|
232
227
|
// Validate
|
|
@@ -250,18 +245,7 @@ export function writeIntegrationBranch(basePath: string, milestoneId: string, br
|
|
|
250
245
|
|
|
251
246
|
existing.integrationBranch = branch;
|
|
252
247
|
writeFileSync(metaFile, JSON.stringify(existing, null, 2) + "\n", "utf-8");
|
|
253
|
-
|
|
254
|
-
// Commit immediately so the metadata is persisted in git.
|
|
255
|
-
// Skip when commit_docs is explicitly false — .gsd/ is local-only.
|
|
256
|
-
if (options?.commitDocs !== false) {
|
|
257
|
-
try {
|
|
258
|
-
nativeAddPaths(basePath, [metaFile]);
|
|
259
|
-
nativeCommit(basePath, `chore(${milestoneId}): record integration branch`, { allowEmpty: false });
|
|
260
|
-
} catch {
|
|
261
|
-
// Non-fatal — file is on disk even if commit fails (e.g. nothing to commit
|
|
262
|
-
// because the file was already tracked with identical content)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
248
|
+
// .gsd/ is managed externally (symlinked) — metadata is not committed to git.
|
|
265
249
|
}
|
|
266
250
|
|
|
267
251
|
// ─── Git Helper ────────────────────────────────────────────────────────────
|
|
@@ -350,10 +334,8 @@ export class GitServiceImpl {
|
|
|
350
334
|
* @param extraExclusions Additional pathspec exclusions beyond RUNTIME_EXCLUSION_PATHS.
|
|
351
335
|
*/
|
|
352
336
|
private smartStage(extraExclusions: readonly string[] = []): void {
|
|
353
|
-
//
|
|
354
|
-
const
|
|
355
|
-
const gsdExclusion = commitDocsDisabled ? [".gsd/"] : [];
|
|
356
|
-
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...gsdExclusion, ...extraExclusions];
|
|
337
|
+
// Always exclude .gsd/ — state is managed externally (symlinked to ~/.gsd/projects/<hash>/)
|
|
338
|
+
const allExclusions = [".gsd/", ...extraExclusions];
|
|
357
339
|
|
|
358
340
|
// One-time cleanup: if runtime files are already tracked in the index
|
|
359
341
|
// (from older versions where the fallback bug staged them), untrack them
|
|
@@ -79,15 +79,14 @@ const BASELINE_PATTERNS = [
|
|
|
79
79
|
];
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
|
-
* Ensure basePath/.gitignore contains
|
|
83
|
-
* Creates the file if missing; appends
|
|
82
|
+
* Ensure basePath/.gitignore contains a blanket `.gsd/` ignore.
|
|
83
|
+
* Creates the file if missing; appends `.gsd/` if not present.
|
|
84
84
|
* Returns true if the file was created or modified, false if already complete.
|
|
85
85
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* artifacts local-only.
|
|
86
|
+
* `.gsd/` state is managed externally (symlinked to `~/.gsd/projects/<hash>/`),
|
|
87
|
+
* so the entire directory is always gitignored.
|
|
89
88
|
*/
|
|
90
|
-
export function ensureGitignore(basePath: string, options?: {
|
|
89
|
+
export function ensureGitignore(basePath: string, options?: { manageGitignore?: boolean }): boolean {
|
|
91
90
|
// If manage_gitignore is explicitly false, do not touch .gitignore at all
|
|
92
91
|
if (options?.manageGitignore === false) return false;
|
|
93
92
|
|
|
@@ -192,7 +191,7 @@ See \`~/.gsd/agent/extensions/gsd/docs/preferences-reference.md\` for full field
|
|
|
192
191
|
- \`models\`: Model preferences for specific task types
|
|
193
192
|
- \`skill_discovery\`: Automatic skill detection preferences
|
|
194
193
|
- \`auto_supervisor\`: Supervision and gating rules for autonomous modes
|
|
195
|
-
- \`git\`: Git preferences — \`main_branch\` (default branch name for new repos, e.g., "main", "master", "trunk"), \`auto_push\`, \`snapshots\`,
|
|
194
|
+
- \`git\`: Git preferences — \`main_branch\` (default branch name for new repos, e.g., "main", "master", "trunk"), \`auto_push\`, \`snapshots\`, etc.
|
|
196
195
|
|
|
197
196
|
## Examples
|
|
198
197
|
|