gsd-pi 2.38.0-dev.96dc7fb → 2.38.0-dev.98b44dc
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/README.md +15 -11
- package/dist/app-paths.js +1 -1
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +636 -594
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
- package/dist/resources/extensions/gsd/auto-start.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +2 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +4 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
- package/dist/resources/extensions/gsd/doctor.js +20 -1
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +48 -9
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/gitignore.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +149 -38
- package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
- package/dist/resources/extensions/gsd/health-widget.js +3 -86
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/migrate-external.js +18 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +22 -11
- package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +42 -23
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -1
- package/dist/resources/extensions/remote-questions/store.js +4 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/shared/frontmatter.js +1 -1
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +6 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/skills.ts +9 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +526 -545
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
- package/src/resources/extensions/gsd/auto-start.ts +11 -1
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +5 -3
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
- package/src/resources/extensions/gsd/doctor.ts +22 -1
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +51 -11
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/gitignore.ts +17 -3
- package/src/resources/extensions/gsd/guided-flow.ts +177 -44
- package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
- package/src/resources/extensions/gsd/health-widget.ts +3 -89
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/migrate-external.ts +18 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +25 -11
- package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +39 -21
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
- package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/types.ts +18 -1
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +5 -1
- package/src/resources/extensions/remote-questions/store.ts +5 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/shared/frontmatter.ts +1 -1
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
* runtime integrations so the regressions can be tested directly.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { detectProjectState } from "./detection.js";
|
|
9
10
|
import { gsdRoot } from "./paths.js";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import type { GSDState, Phase } from "./types.js";
|
|
12
11
|
|
|
13
12
|
export type HealthWidgetProjectState = "none" | "initialized" | "active";
|
|
14
13
|
|
|
@@ -20,75 +19,19 @@ export interface HealthWidgetData {
|
|
|
20
19
|
environmentErrorCount: number;
|
|
21
20
|
environmentWarningCount: number;
|
|
22
21
|
lastRefreshed: number;
|
|
23
|
-
executionPhase?: Phase;
|
|
24
|
-
executionStatus?: string;
|
|
25
|
-
executionTarget?: string;
|
|
26
|
-
nextAction?: string;
|
|
27
|
-
blocker?: string | null;
|
|
28
|
-
activeMilestoneId?: string;
|
|
29
|
-
activeSliceId?: string;
|
|
30
|
-
activeTaskId?: string;
|
|
31
|
-
progress?: GSDState["progress"];
|
|
32
|
-
eta?: string | null;
|
|
33
22
|
}
|
|
34
23
|
|
|
35
24
|
export function detectHealthWidgetProjectState(basePath: string): HealthWidgetProjectState {
|
|
36
|
-
|
|
37
|
-
if (!existsSync(root)) return "none";
|
|
25
|
+
if (!existsSync(gsdRoot(basePath))) return "none";
|
|
38
26
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const milestonesDir = join(root, "milestones");
|
|
43
|
-
if (existsSync(milestonesDir)) {
|
|
44
|
-
const entries = readdirSync(milestonesDir, { withFileTypes: true });
|
|
45
|
-
if (entries.some(e => e.isDirectory())) return "active";
|
|
46
|
-
}
|
|
47
|
-
} catch { /* non-fatal */ }
|
|
48
|
-
|
|
49
|
-
return "initialized";
|
|
27
|
+
const { state } = detectProjectState(basePath);
|
|
28
|
+
return state === "v2-gsd" ? "active" : "initialized";
|
|
50
29
|
}
|
|
51
30
|
|
|
52
31
|
function formatCost(n: number): string {
|
|
53
32
|
return n >= 1 ? `$${n.toFixed(2)}` : `${(n * 100).toFixed(1)}¢`;
|
|
54
33
|
}
|
|
55
34
|
|
|
56
|
-
function formatProgress(progress?: GSDState["progress"]): string | null {
|
|
57
|
-
if (!progress) return null;
|
|
58
|
-
|
|
59
|
-
const parts: string[] = [];
|
|
60
|
-
parts.push(`M ${progress.milestones.done}/${progress.milestones.total}`);
|
|
61
|
-
if (progress.slices) parts.push(`S ${progress.slices.done}/${progress.slices.total}`);
|
|
62
|
-
if (progress.tasks) parts.push(`T ${progress.tasks.done}/${progress.tasks.total}`);
|
|
63
|
-
return parts.length > 0 ? `Progress: ${parts.join(" · ")}` : null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function formatEnvironmentSummary(errorCount: number, warningCount: number): string | null {
|
|
67
|
-
if (errorCount <= 0 && warningCount <= 0) return null;
|
|
68
|
-
|
|
69
|
-
const parts: string[] = [];
|
|
70
|
-
if (errorCount > 0) parts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
|
|
71
|
-
if (warningCount > 0) parts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
|
|
72
|
-
return `Env: ${parts.join(", ")}`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function formatBudgetSummary(data: HealthWidgetData): string | null {
|
|
76
|
-
if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
|
|
77
|
-
const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
|
|
78
|
-
return `Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`;
|
|
79
|
-
}
|
|
80
|
-
if (data.budgetSpent > 0) {
|
|
81
|
-
return `Spent: ${formatCost(data.budgetSpent)}`;
|
|
82
|
-
}
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function buildExecutionHeadline(data: HealthWidgetData): string {
|
|
87
|
-
const status = data.executionStatus ?? "Active project";
|
|
88
|
-
const target = data.executionTarget ?? data.blocker ?? "loading status…";
|
|
89
|
-
return ` GSD ${status}${target ? ` - ${target}` : ""}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
35
|
/**
|
|
93
36
|
* Build compact health lines for the widget.
|
|
94
37
|
* Returns a string array suitable for setWidget().
|
|
@@ -102,28 +45,33 @@ export function buildHealthLines(data: HealthWidgetData): string[] {
|
|
|
102
45
|
return [" GSD Project initialized — run /gsd to continue setup"];
|
|
103
46
|
}
|
|
104
47
|
|
|
105
|
-
const
|
|
106
|
-
const details: string[] = [];
|
|
107
|
-
|
|
108
|
-
const progress = formatProgress(data.progress);
|
|
109
|
-
if (progress) details.push(progress);
|
|
110
|
-
|
|
111
|
-
if (data.providerIssue) details.push(data.providerIssue);
|
|
48
|
+
const parts: string[] = [];
|
|
112
49
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
117
|
-
|
|
50
|
+
const totalIssues = data.environmentErrorCount + data.environmentWarningCount + (data.providerIssue ? 1 : 0);
|
|
51
|
+
if (totalIssues === 0) {
|
|
52
|
+
parts.push("● System OK");
|
|
53
|
+
} else if (data.environmentErrorCount > 0 || data.providerIssue?.includes("✗")) {
|
|
54
|
+
parts.push(`✗ ${totalIssues} issue${totalIssues > 1 ? "s" : ""}`);
|
|
55
|
+
} else {
|
|
56
|
+
parts.push(`⚠ ${totalIssues} warning${totalIssues > 1 ? "s" : ""}`);
|
|
57
|
+
}
|
|
118
58
|
|
|
119
|
-
|
|
120
|
-
|
|
59
|
+
if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
|
|
60
|
+
const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
|
|
61
|
+
parts.push(`Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`);
|
|
62
|
+
} else if (data.budgetSpent > 0) {
|
|
63
|
+
parts.push(`Spent: ${formatCost(data.budgetSpent)}`);
|
|
64
|
+
}
|
|
121
65
|
|
|
122
|
-
if (data.
|
|
66
|
+
if (data.providerIssue) {
|
|
67
|
+
parts.push(data.providerIssue);
|
|
68
|
+
}
|
|
123
69
|
|
|
124
|
-
if (
|
|
125
|
-
|
|
70
|
+
if (data.environmentErrorCount > 0) {
|
|
71
|
+
parts.push(`Env: ${data.environmentErrorCount} error${data.environmentErrorCount > 1 ? "s" : ""}`);
|
|
72
|
+
} else if (data.environmentWarningCount > 0) {
|
|
73
|
+
parts.push(`Env: ${data.environmentWarningCount} warning${data.environmentWarningCount > 1 ? "s" : ""}`);
|
|
126
74
|
}
|
|
127
75
|
|
|
128
|
-
return
|
|
76
|
+
return [` ${parts.join(" │ ")}`];
|
|
129
77
|
}
|
|
@@ -16,7 +16,6 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
|
16
16
|
import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
|
|
17
17
|
import { describeNextUnit, estimateTimeRemaining, updateSliceProgressCache } from "./auto-dashboard.js";
|
|
18
18
|
import { projectRoot } from "./commands.js";
|
|
19
|
-
import { deriveState, invalidateStateCache } from "./state.js";
|
|
20
19
|
import {
|
|
21
20
|
buildHealthLines,
|
|
22
21
|
detectHealthWidgetProjectState,
|
|
@@ -25,7 +24,7 @@ import {
|
|
|
25
24
|
|
|
26
25
|
// ── Data loader ────────────────────────────────────────────────────────────────
|
|
27
26
|
|
|
28
|
-
function
|
|
27
|
+
function loadHealthWidgetData(basePath: string): HealthWidgetData {
|
|
29
28
|
let budgetCeiling: number | undefined;
|
|
30
29
|
let budgetSpent = 0;
|
|
31
30
|
let providerIssue: string | null = null;
|
|
@@ -69,90 +68,6 @@ function loadBaseHealthWidgetData(basePath: string): HealthWidgetData {
|
|
|
69
68
|
};
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
function compactText(text: string, max = 64): string {
|
|
73
|
-
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
74
|
-
if (trimmed.length <= max) return trimmed;
|
|
75
|
-
return `${trimmed.slice(0, max - 1).trimEnd()}…`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function summarizeExecutionStatus(state: GSDState): string {
|
|
79
|
-
switch (state.phase) {
|
|
80
|
-
case "blocked": return "Blocked";
|
|
81
|
-
case "paused": return "Paused";
|
|
82
|
-
case "complete": return "Complete";
|
|
83
|
-
case "executing": return "Executing";
|
|
84
|
-
case "planning": return "Planning";
|
|
85
|
-
case "pre-planning": return "Pre-planning";
|
|
86
|
-
case "summarizing": return "Summarizing";
|
|
87
|
-
case "validating-milestone": return "Validating";
|
|
88
|
-
case "completing-milestone": return "Completing";
|
|
89
|
-
case "needs-discussion": return "Needs discussion";
|
|
90
|
-
case "replanning-slice": return "Replanning";
|
|
91
|
-
default: return "Active";
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function summarizeExecutionTarget(state: GSDState): string {
|
|
96
|
-
switch (state.phase) {
|
|
97
|
-
case "needs-discussion":
|
|
98
|
-
return state.activeMilestone ? `Discuss ${state.activeMilestone.id}` : "Discuss milestone draft";
|
|
99
|
-
case "pre-planning":
|
|
100
|
-
return state.activeMilestone ? `Plan ${state.activeMilestone.id}` : "Research & plan milestone";
|
|
101
|
-
case "planning":
|
|
102
|
-
return state.activeSlice ? `Plan ${state.activeSlice.id}` : "Plan next slice";
|
|
103
|
-
case "executing":
|
|
104
|
-
return state.activeTask ? `Execute ${state.activeTask.id}` : "Execute next task";
|
|
105
|
-
case "summarizing":
|
|
106
|
-
return state.activeSlice ? `Complete ${state.activeSlice.id}` : "Complete current slice";
|
|
107
|
-
case "validating-milestone":
|
|
108
|
-
return state.activeMilestone ? `Validate ${state.activeMilestone.id}` : "Validate milestone";
|
|
109
|
-
case "completing-milestone":
|
|
110
|
-
return state.activeMilestone ? `Complete ${state.activeMilestone.id}` : "Complete milestone";
|
|
111
|
-
case "replanning-slice":
|
|
112
|
-
return state.activeSlice ? `Replan ${state.activeSlice.id}` : "Replan current slice";
|
|
113
|
-
case "blocked":
|
|
114
|
-
return `waiting on ${compactText(state.blockers[0] ?? state.nextAction, 56)}`;
|
|
115
|
-
case "paused":
|
|
116
|
-
return compactText(state.nextAction || "waiting to resume", 56);
|
|
117
|
-
case "complete":
|
|
118
|
-
return "All milestones complete";
|
|
119
|
-
default:
|
|
120
|
-
return compactText(describeNextUnit(state).label, 56);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function enrichHealthWidgetData(basePath: string, baseData: HealthWidgetData): Promise<HealthWidgetData> {
|
|
125
|
-
if (baseData.projectState !== "active") return baseData;
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
invalidateStateCache();
|
|
129
|
-
const state = await deriveState(basePath);
|
|
130
|
-
|
|
131
|
-
if (state.activeMilestone) {
|
|
132
|
-
// Warm the slice-progress cache so estimateTimeRemaining() has data
|
|
133
|
-
updateSliceProgressCache(basePath, state.activeMilestone.id, state.activeSlice?.id);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
...baseData,
|
|
138
|
-
executionPhase: state.phase,
|
|
139
|
-
executionStatus: summarizeExecutionStatus(state),
|
|
140
|
-
executionTarget: summarizeExecutionTarget(state),
|
|
141
|
-
nextAction: state.nextAction,
|
|
142
|
-
blocker: state.blockers[0] ?? null,
|
|
143
|
-
activeMilestoneId: state.activeMilestone?.id,
|
|
144
|
-
activeSliceId: state.activeSlice?.id,
|
|
145
|
-
activeTaskId: state.activeTask?.id,
|
|
146
|
-
progress: state.progress,
|
|
147
|
-
eta: state.phase === "blocked" || state.phase === "paused" || state.phase === "complete"
|
|
148
|
-
? null
|
|
149
|
-
: estimateTimeRemaining(),
|
|
150
|
-
};
|
|
151
|
-
} catch {
|
|
152
|
-
return baseData;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
71
|
// ── Widget init ────────────────────────────────────────────────────────────────
|
|
157
72
|
|
|
158
73
|
const REFRESH_INTERVAL_MS = 60_000;
|
|
@@ -167,7 +82,7 @@ export function initHealthWidget(ctx: ExtensionContext): void {
|
|
|
167
82
|
const basePath = projectRoot();
|
|
168
83
|
|
|
169
84
|
// String-array fallback — used in RPC mode (factory is a no-op there)
|
|
170
|
-
const initialData =
|
|
85
|
+
const initialData = loadHealthWidgetData(basePath);
|
|
171
86
|
ctx.ui.setWidget("gsd-health", buildHealthLines(initialData), { placement: "belowEditor" });
|
|
172
87
|
|
|
173
88
|
// Factory-based widget for TUI mode — replaces the string-array above
|
|
@@ -180,8 +95,7 @@ export function initHealthWidget(ctx: ExtensionContext): void {
|
|
|
180
95
|
if (refreshInFlight) return;
|
|
181
96
|
refreshInFlight = true;
|
|
182
97
|
try {
|
|
183
|
-
|
|
184
|
-
data = await enrichHealthWidgetData(basePath, baseData);
|
|
98
|
+
data = loadHealthWidgetData(basePath);
|
|
185
99
|
cachedLines = undefined;
|
|
186
100
|
_tui.requestRender();
|
|
187
101
|
} catch { /* non-fatal */ } finally {
|
|
@@ -60,6 +60,8 @@ import { join } from "node:path";
|
|
|
60
60
|
import { existsSync, readFileSync } from "node:fs";
|
|
61
61
|
import { homedir } from "node:os";
|
|
62
62
|
import { shortcutDesc } from "../shared/mod.js";
|
|
63
|
+
|
|
64
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
63
65
|
import { Text } from "@gsd/pi-tui";
|
|
64
66
|
import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
|
|
65
67
|
import { toPosixPath } from "../shared/mod.js";
|
|
@@ -73,7 +75,7 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../cmux/index.js"
|
|
|
73
75
|
|
|
74
76
|
function warnDeprecatedAgentInstructions(): void {
|
|
75
77
|
const paths = [
|
|
76
|
-
join(
|
|
78
|
+
join(gsdHome, "agent-instructions.md"),
|
|
77
79
|
join(process.cwd(), ".gsd", "agent-instructions.md"),
|
|
78
80
|
];
|
|
79
81
|
for (const p of paths) {
|
|
@@ -90,6 +92,23 @@ function warnDeprecatedAgentInstructions(): void {
|
|
|
90
92
|
// ── Depth verification state ──────────────────────────────────────────────
|
|
91
93
|
let depthVerificationDone = false;
|
|
92
94
|
|
|
95
|
+
// ── DB lazy-open helper ───────────────────────────────────────────────────
|
|
96
|
+
// In manual sessions (no auto-mode), the DB is never opened by bootstrapAutoSession.
|
|
97
|
+
// This helper ensures the DB is lazily opened on first tool call that needs it.
|
|
98
|
+
async function ensureDbOpen(): Promise<boolean> {
|
|
99
|
+
try {
|
|
100
|
+
const db = await import("./gsd-db.js");
|
|
101
|
+
if (db.isDbAvailable()) return true;
|
|
102
|
+
const dbPath = join(process.cwd(), ".gsd", "gsd.db");
|
|
103
|
+
if (existsSync(dbPath)) {
|
|
104
|
+
return db.openDatabase(dbPath);
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
93
112
|
// ── Queue phase tracking ──────────────────────────────────────────────────
|
|
94
113
|
// When true, the LLM is in a queue flow writing CONTEXT.md files.
|
|
95
114
|
// The write-gate applies during queue flows just like discussion flows.
|
|
@@ -298,12 +317,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
298
317
|
when_context: Type.Optional(Type.String({ description: "When/context for the decision (e.g. milestone ID)" })),
|
|
299
318
|
}),
|
|
300
319
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
const db = await import("./gsd-db.js");
|
|
305
|
-
dbAvailable = db.isDbAvailable();
|
|
306
|
-
} catch { /* dynamic import failed */ }
|
|
320
|
+
// Ensure DB is open (lazy-open on first tool call in manual sessions)
|
|
321
|
+
const dbAvailable = await ensureDbOpen();
|
|
307
322
|
|
|
308
323
|
if (!dbAvailable) {
|
|
309
324
|
return {
|
|
@@ -365,11 +380,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
365
380
|
supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
|
|
366
381
|
}),
|
|
367
382
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
368
|
-
|
|
369
|
-
try {
|
|
370
|
-
const db = await import("./gsd-db.js");
|
|
371
|
-
dbAvailable = db.isDbAvailable();
|
|
372
|
-
} catch { /* dynamic import failed */ }
|
|
383
|
+
const dbAvailable = await ensureDbOpen();
|
|
373
384
|
|
|
374
385
|
if (!dbAvailable) {
|
|
375
386
|
return {
|
|
@@ -439,11 +450,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
439
450
|
content: Type.String({ description: "The full markdown content of the artifact" }),
|
|
440
451
|
}),
|
|
441
452
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
442
|
-
|
|
443
|
-
try {
|
|
444
|
-
const db = await import("./gsd-db.js");
|
|
445
|
-
dbAvailable = db.isDbAvailable();
|
|
446
|
-
} catch { /* dynamic import failed */ }
|
|
453
|
+
const dbAvailable = await ensureDbOpen();
|
|
447
454
|
|
|
448
455
|
if (!dbAvailable) {
|
|
449
456
|
return {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Zero Pi dependencies — uses only exported helpers from files.ts.
|
|
4
4
|
|
|
5
5
|
import { splitFrontmatter, parseFrontmatterMap, extractBoldField } from '../files.js';
|
|
6
|
-
import { normalizeStringArray } from '../../shared/
|
|
6
|
+
import { normalizeStringArray } from '../../shared/format-utils.js';
|
|
7
7
|
|
|
8
8
|
import type {
|
|
9
9
|
PlanningRoadmap,
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
* symlink replaces the original directory so all paths remain valid.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
9
10
|
import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
|
|
10
11
|
import { join } from "node:path";
|
|
11
12
|
import { externalGsdRoot } from "./repo-identity.js";
|
|
12
13
|
import { getErrorMessage } from "./error-utils.js";
|
|
13
14
|
import { hasGitTrackedGsdFiles } from "./gitignore.js";
|
|
15
|
+
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
14
16
|
|
|
15
17
|
export interface MigrationResult {
|
|
16
18
|
migrated: boolean;
|
|
@@ -144,7 +146,22 @@ export function migrateToExternalState(basePath: string): MigrationResult {
|
|
|
144
146
|
return { migrated: false, error: `Migration verification failed: ${getErrorMessage(verifyErr)}` };
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
//
|
|
149
|
+
// Clean the git index — any .gsd/* files tracked before migration now
|
|
150
|
+
// sit behind the symlink and git can't follow it, causing them to show
|
|
151
|
+
// as deleted. Remove them from the index so the working tree stays clean.
|
|
152
|
+
// --ignore-unmatch makes this a no-op on fresh projects with no tracked .gsd/.
|
|
153
|
+
try {
|
|
154
|
+
execFileSync("git", ["rm", "-r", "--cached", "--ignore-unmatch", ".gsd"], {
|
|
155
|
+
cwd: basePath,
|
|
156
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
157
|
+
env: GIT_NO_PROMPT_ENV,
|
|
158
|
+
timeout: 10_000,
|
|
159
|
+
});
|
|
160
|
+
} catch {
|
|
161
|
+
// Non-fatal — git may be unavailable or nothing was tracked
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Remove .gsd.migrating only after symlink is verified and index is clean
|
|
148
165
|
rmSync(migratingPath, { recursive: true, force: true });
|
|
149
166
|
|
|
150
167
|
return { migrated: true };
|
|
@@ -671,6 +671,43 @@ export function nativeAddAll(basePath: string): void {
|
|
|
671
671
|
gitFileExec(basePath, ["add", "-A"]);
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Stage all files with pathspec exclusions (git add -A -- ':!pattern' ...).
|
|
676
|
+
* Excluded paths are never hashed by git, preventing hangs on large
|
|
677
|
+
* untracked artifact trees (57GB+, 11K+ files). See #1605.
|
|
678
|
+
*
|
|
679
|
+
* Falls back to plain `git add -A` when no exclusions are provided.
|
|
680
|
+
* Always uses the CLI path (not libgit2) because libgit2's add_all
|
|
681
|
+
* does not support pathspec exclusion syntax.
|
|
682
|
+
*
|
|
683
|
+
* When excluded paths are already covered by .gitignore, git may exit
|
|
684
|
+
* with code 1 and an "ignored by .gitignore" warning. This is harmless
|
|
685
|
+
* (the staging succeeds for all non-ignored files) and is suppressed.
|
|
686
|
+
*/
|
|
687
|
+
export function nativeAddAllWithExclusions(basePath: string, exclusions: readonly string[]): void {
|
|
688
|
+
if (exclusions.length === 0) {
|
|
689
|
+
nativeAddAll(basePath);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const pathspecs = exclusions.map(e => `:!${e}`);
|
|
693
|
+
try {
|
|
694
|
+
execFileSync("git", ["add", "-A", "--", ...pathspecs], {
|
|
695
|
+
cwd: basePath,
|
|
696
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
697
|
+
encoding: "utf-8",
|
|
698
|
+
env: GIT_NO_PROMPT_ENV,
|
|
699
|
+
});
|
|
700
|
+
} catch (err: unknown) {
|
|
701
|
+
// git exits 1 when pathspec exclusions reference paths already covered
|
|
702
|
+
// by .gitignore. The staging itself succeeds — only suppress that case.
|
|
703
|
+
const stderr = (err as { stderr?: string })?.stderr ?? "";
|
|
704
|
+
if (stderr.includes("ignored by one of your .gitignore files")) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
674
711
|
/**
|
|
675
712
|
* Stage specific files.
|
|
676
713
|
* Native: libgit2 index add.
|
|
@@ -356,6 +356,10 @@ export function milestonesDir(basePath: string): string {
|
|
|
356
356
|
return join(gsdRoot(basePath), "milestones");
|
|
357
357
|
}
|
|
358
358
|
|
|
359
|
+
export function resolveRuntimeFile(basePath: string): string {
|
|
360
|
+
return join(gsdRoot(basePath), "RUNTIME.md");
|
|
361
|
+
}
|
|
362
|
+
|
|
359
363
|
export function resolveGsdRootFile(basePath: string, key: GSDRootFileKey): string {
|
|
360
364
|
const root = gsdRoot(basePath);
|
|
361
365
|
const canonical = join(root, GSD_ROOT_FILES[key]);
|
|
@@ -295,18 +295,6 @@ export function resolveInlineLevel(): InlineLevel {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
/**
|
|
299
|
-
* Resolve the compression strategy from the active token profile.
|
|
300
|
-
* budget/balanced -> "compress", quality -> "truncate".
|
|
301
|
-
* Explicit preference always wins.
|
|
302
|
-
*/
|
|
303
|
-
export function resolveCompressionStrategy(): import("./types.js").CompressionStrategy {
|
|
304
|
-
const prefs = loadEffectiveGSDPreferences();
|
|
305
|
-
if (prefs?.preferences.compression_strategy) return prefs.preferences.compression_strategy;
|
|
306
|
-
const profile = resolveEffectiveProfile();
|
|
307
|
-
return profile === "quality" ? "truncate" : "compress";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
298
|
/**
|
|
311
299
|
* Resolve the context selection mode from the active token profile.
|
|
312
300
|
* budget -> "smart", balanced/quality -> "full".
|
|
@@ -16,11 +16,11 @@ import type {
|
|
|
16
16
|
InlineLevel,
|
|
17
17
|
PhaseSkipPreferences,
|
|
18
18
|
ParallelConfig,
|
|
19
|
-
CompressionStrategy,
|
|
20
19
|
ContextSelectionMode,
|
|
21
20
|
ReactiveExecutionConfig,
|
|
22
21
|
} from "./types.js";
|
|
23
22
|
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
23
|
+
import type { GitHubSyncConfig } from "../github-sync/types.js";
|
|
24
24
|
|
|
25
25
|
// ─── Workflow Modes ──────────────────────────────────────────────────────────
|
|
26
26
|
|
|
@@ -84,10 +84,10 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
84
84
|
"verification_auto_fix",
|
|
85
85
|
"verification_max_retries",
|
|
86
86
|
"search_provider",
|
|
87
|
-
"compression_strategy",
|
|
88
87
|
"context_selection",
|
|
89
88
|
"widget_mode",
|
|
90
89
|
"reactive_execution",
|
|
90
|
+
"github",
|
|
91
91
|
]);
|
|
92
92
|
|
|
93
93
|
/** Canonical list of all dispatch unit types. */
|
|
@@ -211,14 +211,14 @@ export interface GSDPreferences {
|
|
|
211
211
|
verification_max_retries?: number;
|
|
212
212
|
/** Search provider preference. "brave"/"tavily"/"ollama" force that backend and disable native Anthropic search. "native" forces native only. "auto" = current default behavior. */
|
|
213
213
|
search_provider?: "brave" | "tavily" | "ollama" | "native" | "auto";
|
|
214
|
-
/** Compression strategy for context that exceeds budget. "truncate" (default) drops sections, "compress" applies heuristic compression first. */
|
|
215
|
-
compression_strategy?: CompressionStrategy;
|
|
216
214
|
/** Context selection mode for file inlining. "full" inlines entire files, "smart" uses semantic chunking. Default derived from token profile. */
|
|
217
215
|
context_selection?: ContextSelectionMode;
|
|
218
216
|
/** Default widget display mode for auto-mode dashboard. "full" | "small" | "min" | "off". Default: "full". */
|
|
219
217
|
widget_mode?: "full" | "small" | "min" | "off";
|
|
220
218
|
/** Reactive (graph-derived parallel) task execution within slices. Disabled by default. */
|
|
221
219
|
reactive_execution?: ReactiveExecutionConfig;
|
|
220
|
+
/** GitHub sync configuration. Opt-in: syncs GSD events to GitHub Issues, Milestones, and PRs. */
|
|
221
|
+
github?: GitHubSyncConfig;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
export interface LoadedGSDPreferences {
|
|
@@ -10,7 +10,7 @@ import type { GitPreferences } from "./git-service.js";
|
|
|
10
10
|
import type { PostUnitHookConfig, PreDispatchHookConfig, TokenProfile, PhaseSkipPreferences } from "./types.js";
|
|
11
11
|
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
12
12
|
import { VALID_BRANCH_NAME } from "./git-service.js";
|
|
13
|
-
import { normalizeStringArray } from "../shared/
|
|
13
|
+
import { normalizeStringArray } from "../shared/format-utils.js";
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
KNOWN_PREFERENCE_KEYS,
|
|
@@ -686,16 +686,6 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
686
686
|
}
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
-
// ─── Compression Strategy ───────────────────────────────────────────
|
|
690
|
-
if (preferences.compression_strategy !== undefined) {
|
|
691
|
-
const validStrategies = new Set(["truncate", "compress"]);
|
|
692
|
-
if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
|
|
693
|
-
validated.compression_strategy = preferences.compression_strategy as GSDPreferences["compression_strategy"];
|
|
694
|
-
} else {
|
|
695
|
-
errors.push(`compression_strategy must be one of: truncate, compress`);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
689
|
// ─── Context Selection ──────────────────────────────────────────────
|
|
700
690
|
if (preferences.context_selection !== undefined) {
|
|
701
691
|
const validModes = new Set(["full", "smart"]);
|
|
@@ -706,5 +696,55 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
706
696
|
}
|
|
707
697
|
}
|
|
708
698
|
|
|
699
|
+
// ─── GitHub Sync ────────────────────────────────────────────────────────
|
|
700
|
+
if (preferences.github !== undefined) {
|
|
701
|
+
if (typeof preferences.github === "object" && preferences.github !== null) {
|
|
702
|
+
const gh = preferences.github as unknown as Record<string, unknown>;
|
|
703
|
+
const validGh: Record<string, unknown> = {};
|
|
704
|
+
|
|
705
|
+
if (gh.enabled !== undefined) {
|
|
706
|
+
if (typeof gh.enabled === "boolean") validGh.enabled = gh.enabled;
|
|
707
|
+
else errors.push("github.enabled must be a boolean");
|
|
708
|
+
}
|
|
709
|
+
if (gh.repo !== undefined) {
|
|
710
|
+
if (typeof gh.repo === "string" && gh.repo.includes("/")) validGh.repo = gh.repo;
|
|
711
|
+
else errors.push('github.repo must be a string in "owner/repo" format');
|
|
712
|
+
}
|
|
713
|
+
if (gh.project !== undefined) {
|
|
714
|
+
const p = typeof gh.project === "number" ? gh.project : Number(gh.project);
|
|
715
|
+
if (Number.isFinite(p) && p > 0) validGh.project = Math.floor(p);
|
|
716
|
+
else errors.push("github.project must be a positive number");
|
|
717
|
+
}
|
|
718
|
+
if (gh.labels !== undefined) {
|
|
719
|
+
if (Array.isArray(gh.labels) && gh.labels.every((l: unknown) => typeof l === "string")) {
|
|
720
|
+
validGh.labels = gh.labels;
|
|
721
|
+
} else {
|
|
722
|
+
errors.push("github.labels must be an array of strings");
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (gh.auto_link_commits !== undefined) {
|
|
726
|
+
if (typeof gh.auto_link_commits === "boolean") validGh.auto_link_commits = gh.auto_link_commits;
|
|
727
|
+
else errors.push("github.auto_link_commits must be a boolean");
|
|
728
|
+
}
|
|
729
|
+
if (gh.slice_prs !== undefined) {
|
|
730
|
+
if (typeof gh.slice_prs === "boolean") validGh.slice_prs = gh.slice_prs;
|
|
731
|
+
else errors.push("github.slice_prs must be a boolean");
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const knownGhKeys = new Set(["enabled", "repo", "project", "labels", "auto_link_commits", "slice_prs"]);
|
|
735
|
+
for (const key of Object.keys(gh)) {
|
|
736
|
+
if (!knownGhKeys.has(key)) {
|
|
737
|
+
warnings.push(`unknown github key "${key}" — ignored`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
if (Object.keys(validGh).length > 0) {
|
|
742
|
+
validated.github = validGh as unknown as import("../github-sync/types.js").GitHubSyncConfig;
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
errors.push("github must be an object");
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
709
749
|
return { preferences: validated, errors, warnings };
|
|
710
750
|
}
|