gsd-pi 2.31.2-dev.c8d7e03 → 2.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/extensions/gsd/auto-start.ts +4 -2
- package/dist/resources/extensions/gsd/commands.ts +19 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +28 -0
- package/dist/resources/extensions/gsd/doctor-environment.ts +497 -0
- package/dist/resources/extensions/gsd/doctor-providers.ts +343 -0
- package/dist/resources/extensions/gsd/doctor-types.ts +14 -1
- package/dist/resources/extensions/gsd/doctor.ts +6 -0
- package/dist/resources/extensions/gsd/health-widget.ts +167 -0
- package/dist/resources/extensions/gsd/index.ts +6 -0
- package/dist/resources/extensions/gsd/progress-score.ts +273 -0
- package/dist/resources/extensions/gsd/tests/doctor-environment.test.ts +314 -0
- package/dist/resources/extensions/gsd/tests/doctor-providers.test.ts +298 -0
- package/dist/resources/extensions/gsd/tests/export-html-enhancements.test.ts +3 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +7 -3
- package/dist/resources/extensions/gsd/tests/progress-score.test.ts +206 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +12 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +60 -2
- package/dist/resources/extensions/gsd/visualizer-views.ts +54 -0
- package/package.json +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-start.ts +4 -2
- package/src/resources/extensions/gsd/commands.ts +19 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +28 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +497 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +343 -0
- package/src/resources/extensions/gsd/doctor-types.ts +14 -1
- package/src/resources/extensions/gsd/doctor.ts +6 -0
- package/src/resources/extensions/gsd/health-widget.ts +167 -0
- package/src/resources/extensions/gsd/index.ts +6 -0
- package/src/resources/extensions/gsd/progress-score.ts +273 -0
- package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +314 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +298 -0
- package/src/resources/extensions/gsd/tests/export-html-enhancements.test.ts +3 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +7 -3
- package/src/resources/extensions/gsd/tests/progress-score.test.ts +206 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +12 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +60 -2
- package/src/resources/extensions/gsd/visualizer-views.ts +54 -0
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
} from './metrics.js';
|
|
19
19
|
import { loadAllCaptures, countPendingCaptures } from './captures.js';
|
|
20
20
|
import { loadEffectiveGSDPreferences } from './preferences.js';
|
|
21
|
+
import { runProviderChecks, type ProviderCheckResult } from './doctor-providers.js';
|
|
22
|
+
import { generateSkillHealthReport } from './skill-health.js';
|
|
23
|
+
import { runEnvironmentChecks, type EnvironmentCheckResult } from './doctor-environment.js';
|
|
21
24
|
|
|
22
25
|
import type { Phase } from './types.js';
|
|
23
26
|
import type { CaptureEntry } from './captures.js';
|
|
@@ -142,6 +145,22 @@ export interface CapturesInfo {
|
|
|
142
145
|
totalCount: number;
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
export interface ProviderStatusSummary {
|
|
149
|
+
name: string;
|
|
150
|
+
label: string;
|
|
151
|
+
category: string;
|
|
152
|
+
ok: boolean;
|
|
153
|
+
required: boolean;
|
|
154
|
+
message: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface SkillSummaryInfo {
|
|
158
|
+
total: number;
|
|
159
|
+
warningCount: number;
|
|
160
|
+
criticalCount: number;
|
|
161
|
+
topIssue: string | null;
|
|
162
|
+
}
|
|
163
|
+
|
|
145
164
|
export interface HealthInfo {
|
|
146
165
|
budgetCeiling: number | undefined;
|
|
147
166
|
tokenProfile: string;
|
|
@@ -152,6 +171,9 @@ export interface HealthInfo {
|
|
|
152
171
|
toolCalls: number;
|
|
153
172
|
assistantMessages: number;
|
|
154
173
|
userMessages: number;
|
|
174
|
+
providers: ProviderStatusSummary[];
|
|
175
|
+
skillSummary: SkillSummaryInfo;
|
|
176
|
+
environmentIssues: import("./doctor-environment.js").EnvironmentCheckResult[];
|
|
155
177
|
}
|
|
156
178
|
|
|
157
179
|
export interface VisualizerData {
|
|
@@ -538,7 +560,7 @@ function loadKnowledge(basePath: string): KnowledgeInfo {
|
|
|
538
560
|
|
|
539
561
|
// ─── Health Loader ────────────────────────────────────────────────────────────
|
|
540
562
|
|
|
541
|
-
function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null): HealthInfo {
|
|
563
|
+
function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null, basePath: string): HealthInfo {
|
|
542
564
|
const prefs = loadEffectiveGSDPreferences();
|
|
543
565
|
const budgetCeiling = prefs?.preferences?.budget_ceiling;
|
|
544
566
|
const tokenProfile = prefs?.preferences?.token_profile ?? 'standard';
|
|
@@ -553,6 +575,39 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null): HealthI
|
|
|
553
575
|
const tierBreakdown = aggregateByTier(units);
|
|
554
576
|
const tierSavingsLine = formatTierSavings(units);
|
|
555
577
|
|
|
578
|
+
// Provider checks — fast (auth.json + env vars only, no network)
|
|
579
|
+
let providers: ProviderStatusSummary[] = [];
|
|
580
|
+
try {
|
|
581
|
+
providers = runProviderChecks().map((r: ProviderCheckResult) => ({
|
|
582
|
+
name: r.name,
|
|
583
|
+
label: r.label,
|
|
584
|
+
category: r.category,
|
|
585
|
+
ok: r.status === "ok" || r.status === "unconfigured",
|
|
586
|
+
required: r.required,
|
|
587
|
+
message: r.message,
|
|
588
|
+
}));
|
|
589
|
+
} catch { /* non-fatal */ }
|
|
590
|
+
|
|
591
|
+
// Skill health summary
|
|
592
|
+
let skillSummary: SkillSummaryInfo = { total: 0, warningCount: 0, criticalCount: 0, topIssue: null };
|
|
593
|
+
try {
|
|
594
|
+
const report = generateSkillHealthReport(basePath);
|
|
595
|
+
const warnings = report.suggestions.filter(s => s.severity === "warning");
|
|
596
|
+
const criticals = report.suggestions.filter(s => s.severity === "critical");
|
|
597
|
+
skillSummary = {
|
|
598
|
+
total: report.skills.length,
|
|
599
|
+
warningCount: warnings.length,
|
|
600
|
+
criticalCount: criticals.length,
|
|
601
|
+
topIssue: report.suggestions[0]?.message ?? null,
|
|
602
|
+
};
|
|
603
|
+
} catch { /* non-fatal */ }
|
|
604
|
+
|
|
605
|
+
// Environment issues (from doctor-environment.ts, #1221)
|
|
606
|
+
let environmentIssues: EnvironmentCheckResult[] = [];
|
|
607
|
+
try {
|
|
608
|
+
environmentIssues = runEnvironmentChecks(basePath).filter(r => r.status !== "ok");
|
|
609
|
+
} catch { /* non-fatal */ }
|
|
610
|
+
|
|
556
611
|
return {
|
|
557
612
|
budgetCeiling,
|
|
558
613
|
tokenProfile,
|
|
@@ -563,6 +618,9 @@ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null): HealthI
|
|
|
563
618
|
toolCalls: totals?.toolCalls ?? 0,
|
|
564
619
|
assistantMessages: totals?.assistantMessages ?? 0,
|
|
565
620
|
userMessages: totals?.userMessages ?? 0,
|
|
621
|
+
providers,
|
|
622
|
+
skillSummary,
|
|
623
|
+
environmentIssues,
|
|
566
624
|
};
|
|
567
625
|
}
|
|
568
626
|
|
|
@@ -780,7 +838,7 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
|
|
|
780
838
|
totalCount: allCaptures.length,
|
|
781
839
|
};
|
|
782
840
|
|
|
783
|
-
const health = loadHealth(units, totals);
|
|
841
|
+
const health = loadHealth(units, totals, basePath);
|
|
784
842
|
const stats = buildVisualizerStats(milestones, changelog.entries);
|
|
785
843
|
const discussion = loadDiscussionState(basePath, milestones);
|
|
786
844
|
|
|
@@ -1113,5 +1113,59 @@ export function renderHealthView(
|
|
|
1113
1113
|
lines.push(` Tool calls: ${th.fg("text", String(health.toolCalls))}`);
|
|
1114
1114
|
lines.push(` Messages: ${th.fg("text", String(health.assistantMessages))} sent / ${th.fg("text", String(health.userMessages))} received`);
|
|
1115
1115
|
|
|
1116
|
+
// Environment section — issues only (from doctor-environment.ts, #1221)
|
|
1117
|
+
if (health.environmentIssues?.length > 0) {
|
|
1118
|
+
lines.push("");
|
|
1119
|
+
lines.push(th.fg("accent", th.bold("Environment")));
|
|
1120
|
+
lines.push("");
|
|
1121
|
+
for (const r of health.environmentIssues) {
|
|
1122
|
+
const icon = r.status === "error" ? th.fg("error", "✗") : th.fg("warning", "⚠");
|
|
1123
|
+
lines.push(` ${icon} ${th.fg("text", r.message)}`);
|
|
1124
|
+
if (r.detail) lines.push(` ${th.fg("dim", r.detail)}`);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Providers section
|
|
1129
|
+
if (health.providers?.length > 0) {
|
|
1130
|
+
lines.push("");
|
|
1131
|
+
lines.push(th.fg("accent", th.bold("Providers")));
|
|
1132
|
+
lines.push("");
|
|
1133
|
+
const categoryOrder = ["llm", "remote", "search", "tool"];
|
|
1134
|
+
const categoryLabels: Record<string, string> = { llm: "LLM", remote: "Notifications", search: "Search", tool: "Tools" };
|
|
1135
|
+
const grouped = new Map<string, typeof health.providers>();
|
|
1136
|
+
for (const p of health.providers) {
|
|
1137
|
+
const cat = p.category;
|
|
1138
|
+
if (!grouped.has(cat)) grouped.set(cat, []);
|
|
1139
|
+
grouped.get(cat)!.push(p);
|
|
1140
|
+
}
|
|
1141
|
+
for (const cat of categoryOrder) {
|
|
1142
|
+
const items = grouped.get(cat);
|
|
1143
|
+
if (!items || items.length === 0) continue;
|
|
1144
|
+
lines.push(` ${th.fg("dim", categoryLabels[cat] ?? cat)}`);
|
|
1145
|
+
for (const p of items) {
|
|
1146
|
+
const icon = p.ok ? th.fg("success", "✓") : th.fg("error", "✗");
|
|
1147
|
+
const msg = p.ok ? th.fg("dim", p.message) : th.fg("text", p.message);
|
|
1148
|
+
lines.push(` ${icon} ${msg}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Skills section
|
|
1154
|
+
if (health.skillSummary?.total > 0) {
|
|
1155
|
+
lines.push("");
|
|
1156
|
+
lines.push(th.fg("accent", th.bold("Skills")));
|
|
1157
|
+
lines.push("");
|
|
1158
|
+
const { total, warningCount, criticalCount, topIssue } = health.skillSummary;
|
|
1159
|
+
const issueColor = criticalCount > 0 ? "error" : warningCount > 0 ? "warning" : "success";
|
|
1160
|
+
const issueTag = criticalCount > 0
|
|
1161
|
+
? `${criticalCount} critical`
|
|
1162
|
+
: warningCount > 0
|
|
1163
|
+
? `${warningCount} warning${warningCount > 1 ? "s" : ""}`
|
|
1164
|
+
: "all healthy";
|
|
1165
|
+
lines.push(` ${th.fg("text", String(total))} skills tracked · ${th.fg(issueColor, issueTag)}`);
|
|
1166
|
+
if (topIssue) lines.push(` ${th.fg("warning", "⚠")} ${th.fg("dim", topIssue)}`);
|
|
1167
|
+
lines.push(` ${th.fg("dim", "→ /gsd skill-health for full report")}`);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1116
1170
|
return lines;
|
|
1117
1171
|
}
|
package/package.json
CHANGED
package/pkg/package.json
CHANGED
|
@@ -131,9 +131,11 @@ export async function bootstrapAutoSession(
|
|
|
131
131
|
// Initialize GitServiceImpl
|
|
132
132
|
s.gitService = createGitService(s.basePath);
|
|
133
133
|
|
|
134
|
-
// Check for crash from previous session (use both old and new lock data)
|
|
134
|
+
// Check for crash from previous session (use both old and new lock data).
|
|
135
|
+
// Skip if the lock PID matches this process — acquireSessionLock() writes
|
|
136
|
+
// to the same auto.lock file before this check, so we'd always false-positive.
|
|
135
137
|
const crashLock = readCrashLock(base);
|
|
136
|
-
if (crashLock) {
|
|
138
|
+
if (crashLock && crashLock.pid !== process.pid) {
|
|
137
139
|
// We already hold the session lock, so no concurrent session is running.
|
|
138
140
|
// The crash lock is from a dead process — recover context from it.
|
|
139
141
|
const recoveredMid = crashLock.unitId.split("/")[0];
|
|
@@ -44,6 +44,8 @@ import { handleConfig } from "./commands-config.js";
|
|
|
44
44
|
import { handleInspect } from "./commands-inspect.js";
|
|
45
45
|
import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleDryRun } from "./commands-maintenance.js";
|
|
46
46
|
import { handleDoctor, handleSteer, handleCapture, handleTriage, handleKnowledge, handleRunHook, handleUpdate, handleSkillHealth } from "./commands-handlers.js";
|
|
47
|
+
import { computeProgressScore, formatProgressLine } from "./progress-score.js";
|
|
48
|
+
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
47
49
|
import { handleLogs } from "./commands-logs.js";
|
|
48
50
|
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
49
51
|
|
|
@@ -1068,6 +1070,11 @@ async function handleSetup(args: string, ctx: ExtensionCommandContext): Promise<
|
|
|
1068
1070
|
function formatTextStatus(state: GSDState): string {
|
|
1069
1071
|
const lines: string[] = ["GSD Status\n"];
|
|
1070
1072
|
|
|
1073
|
+
// Progress score — traffic light (#1221)
|
|
1074
|
+
const progressScore = computeProgressScore();
|
|
1075
|
+
lines.push(formatProgressLine(progressScore));
|
|
1076
|
+
lines.push("");
|
|
1077
|
+
|
|
1071
1078
|
// Phase
|
|
1072
1079
|
lines.push(`Phase: ${state.phase}`);
|
|
1073
1080
|
|
|
@@ -1114,5 +1121,17 @@ function formatTextStatus(state: GSDState): string {
|
|
|
1114
1121
|
}
|
|
1115
1122
|
}
|
|
1116
1123
|
|
|
1124
|
+
// Environment health (#1221)
|
|
1125
|
+
const envResults = runEnvironmentChecks(projectRoot());
|
|
1126
|
+
const envIssues = envResults.filter(r => r.status !== "ok");
|
|
1127
|
+
if (envIssues.length > 0) {
|
|
1128
|
+
lines.push("");
|
|
1129
|
+
lines.push("Environment:");
|
|
1130
|
+
for (const r of envIssues) {
|
|
1131
|
+
const icon = r.status === "error" ? "✗" : "⚠";
|
|
1132
|
+
lines.push(` ${icon} ${r.message}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1117
1136
|
return lines.join("\n");
|
|
1118
1137
|
}
|
|
@@ -23,6 +23,8 @@ import { getActiveWorktreeName } from "./worktree-command.js";
|
|
|
23
23
|
import { getWorkerBatches, hasActiveWorkers, type WorkerEntry } from "../subagent/worker-registry.js";
|
|
24
24
|
import { formatDuration, padRight, joinColumns, centerLine, fitColumns, STATUS_GLYPH, STATUS_COLOR } from "../shared/mod.js";
|
|
25
25
|
import { estimateTimeRemaining } from "./auto-dashboard.js";
|
|
26
|
+
import { computeProgressScore, formatProgressLine } from "./progress-score.js";
|
|
27
|
+
import { runEnvironmentChecks, type EnvironmentCheckResult } from "./doctor-environment.js";
|
|
26
28
|
|
|
27
29
|
function unitLabel(type: string): string {
|
|
28
30
|
switch (type) {
|
|
@@ -310,6 +312,15 @@ export class GSDDashboardOverlay {
|
|
|
310
312
|
elapsedParts = th.fg("dim", `since ${this.dashData.remoteSession!.startedAt.replace("T", " ").slice(0, 19)}`);
|
|
311
313
|
}
|
|
312
314
|
lines.push(row(joinColumns(`${title} ${status}${worktreeTag}`, elapsedParts, contentWidth)));
|
|
315
|
+
|
|
316
|
+
// Progress score — traffic light indicator (#1221)
|
|
317
|
+
if (this.dashData.active || this.dashData.paused) {
|
|
318
|
+
const progressScore = computeProgressScore();
|
|
319
|
+
const progressIcon = progressScore.level === "green" ? th.fg("success", "●")
|
|
320
|
+
: progressScore.level === "yellow" ? th.fg("warning", "●")
|
|
321
|
+
: th.fg("error", "●");
|
|
322
|
+
lines.push(row(`${progressIcon} ${th.fg("text", progressScore.summary)}`));
|
|
323
|
+
}
|
|
313
324
|
lines.push(blank());
|
|
314
325
|
|
|
315
326
|
if (this.dashData.currentUnit) {
|
|
@@ -579,6 +590,23 @@ export class GSDDashboardOverlay {
|
|
|
579
590
|
}
|
|
580
591
|
}
|
|
581
592
|
|
|
593
|
+
// Environment health section (#1221) — only show issues
|
|
594
|
+
const envResults = runEnvironmentChecks(this.dashData.basePath || process.cwd());
|
|
595
|
+
const envIssues = envResults.filter(r => r.status !== "ok");
|
|
596
|
+
if (envIssues.length > 0) {
|
|
597
|
+
lines.push(blank());
|
|
598
|
+
lines.push(hr());
|
|
599
|
+
lines.push(row(th.fg("text", th.bold("Environment"))));
|
|
600
|
+
lines.push(blank());
|
|
601
|
+
for (const r of envIssues) {
|
|
602
|
+
const icon = r.status === "error" ? th.fg("error", "✗") : th.fg("warning", "⚠");
|
|
603
|
+
lines.push(row(` ${icon} ${th.fg("text", r.message)}`));
|
|
604
|
+
if (r.detail) {
|
|
605
|
+
lines.push(row(th.fg("dim", ` ${r.detail}`)));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
582
610
|
lines.push(blank());
|
|
583
611
|
lines.push(hr());
|
|
584
612
|
lines.push(centered(th.fg("dim", "↑↓ scroll · g/G top/end · esc close")));
|