gsd-pi 2.38.0-dev.eeb3520 → 2.39.0-dev.64cd3ed
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/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- 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/async-jobs/index.js +10 -0
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- 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/google-search/package.json +3 -1
- 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 +650 -588
- 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 +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +24 -3
- 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-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
- package/dist/resources/extensions/gsd/doctor.js +204 -12
- 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/package.json +1 -1
- 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/gsd/worktree.js +35 -16
- 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/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +1 -1
- package/packages/pi-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/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/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/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/packages/pi-coding-agent/src/core/skills.ts +9 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/index.ts +11 -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/cmux/index.ts +57 -1
- 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 +553 -546
- 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 +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +26 -4
- 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-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +199 -14
- 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/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -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/tests/worktree.test.ts +47 -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/gsd/worktree.ts +35 -15
- 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/index.ts +12 -3
- 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
|
@@ -4,65 +4,18 @@
|
|
|
4
4
|
* Separates project-state detection and line rendering from the widget's
|
|
5
5
|
* runtime integrations so the regressions can be tested directly.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { detectProjectState } from "./detection.js";
|
|
8
9
|
import { gsdRoot } from "./paths.js";
|
|
9
|
-
import { join } from "node:path";
|
|
10
10
|
export function detectHealthWidgetProjectState(basePath) {
|
|
11
|
-
|
|
12
|
-
if (!existsSync(root))
|
|
11
|
+
if (!existsSync(gsdRoot(basePath)))
|
|
13
12
|
return "none";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
const milestonesDir = join(root, "milestones");
|
|
18
|
-
if (existsSync(milestonesDir)) {
|
|
19
|
-
const entries = readdirSync(milestonesDir, { withFileTypes: true });
|
|
20
|
-
if (entries.some(e => e.isDirectory()))
|
|
21
|
-
return "active";
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
catch { /* non-fatal */ }
|
|
25
|
-
return "initialized";
|
|
13
|
+
const { state } = detectProjectState(basePath);
|
|
14
|
+
return state === "v2-gsd" ? "active" : "initialized";
|
|
26
15
|
}
|
|
27
16
|
function formatCost(n) {
|
|
28
17
|
return n >= 1 ? `$${n.toFixed(2)}` : `${(n * 100).toFixed(1)}¢`;
|
|
29
18
|
}
|
|
30
|
-
function formatProgress(progress) {
|
|
31
|
-
if (!progress)
|
|
32
|
-
return null;
|
|
33
|
-
const parts = [];
|
|
34
|
-
parts.push(`M ${progress.milestones.done}/${progress.milestones.total}`);
|
|
35
|
-
if (progress.slices)
|
|
36
|
-
parts.push(`S ${progress.slices.done}/${progress.slices.total}`);
|
|
37
|
-
if (progress.tasks)
|
|
38
|
-
parts.push(`T ${progress.tasks.done}/${progress.tasks.total}`);
|
|
39
|
-
return parts.length > 0 ? `Progress: ${parts.join(" · ")}` : null;
|
|
40
|
-
}
|
|
41
|
-
function formatEnvironmentSummary(errorCount, warningCount) {
|
|
42
|
-
if (errorCount <= 0 && warningCount <= 0)
|
|
43
|
-
return null;
|
|
44
|
-
const parts = [];
|
|
45
|
-
if (errorCount > 0)
|
|
46
|
-
parts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
|
|
47
|
-
if (warningCount > 0)
|
|
48
|
-
parts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
|
|
49
|
-
return `Env: ${parts.join(", ")}`;
|
|
50
|
-
}
|
|
51
|
-
function formatBudgetSummary(data) {
|
|
52
|
-
if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
|
|
53
|
-
const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
|
|
54
|
-
return `Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`;
|
|
55
|
-
}
|
|
56
|
-
if (data.budgetSpent > 0) {
|
|
57
|
-
return `Spent: ${formatCost(data.budgetSpent)}`;
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
function buildExecutionHeadline(data) {
|
|
62
|
-
const status = data.executionStatus ?? "Active project";
|
|
63
|
-
const target = data.executionTarget ?? data.blocker ?? "loading status…";
|
|
64
|
-
return ` GSD ${status}${target ? ` - ${target}` : ""}`;
|
|
65
|
-
}
|
|
66
19
|
/**
|
|
67
20
|
* Build compact health lines for the widget.
|
|
68
21
|
* Returns a string array suitable for setWidget().
|
|
@@ -74,23 +27,32 @@ export function buildHealthLines(data) {
|
|
|
74
27
|
if (data.projectState === "initialized") {
|
|
75
28
|
return [" GSD Project initialized — run /gsd to continue setup"];
|
|
76
29
|
}
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (data.providerIssue)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
30
|
+
const parts = [];
|
|
31
|
+
const totalIssues = data.environmentErrorCount + data.environmentWarningCount + (data.providerIssue ? 1 : 0);
|
|
32
|
+
if (totalIssues === 0) {
|
|
33
|
+
parts.push("● System OK");
|
|
34
|
+
}
|
|
35
|
+
else if (data.environmentErrorCount > 0 || data.providerIssue?.includes("✗")) {
|
|
36
|
+
parts.push(`✗ ${totalIssues} issue${totalIssues > 1 ? "s" : ""}`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
parts.push(`⚠ ${totalIssues} warning${totalIssues > 1 ? "s" : ""}`);
|
|
40
|
+
}
|
|
41
|
+
if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
|
|
42
|
+
const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
|
|
43
|
+
parts.push(`Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`);
|
|
44
|
+
}
|
|
45
|
+
else if (data.budgetSpent > 0) {
|
|
46
|
+
parts.push(`Spent: ${formatCost(data.budgetSpent)}`);
|
|
47
|
+
}
|
|
48
|
+
if (data.providerIssue) {
|
|
49
|
+
parts.push(data.providerIssue);
|
|
50
|
+
}
|
|
51
|
+
if (data.environmentErrorCount > 0) {
|
|
52
|
+
parts.push(`Env: ${data.environmentErrorCount} error${data.environmentErrorCount > 1 ? "s" : ""}`);
|
|
53
|
+
}
|
|
54
|
+
else if (data.environmentWarningCount > 0) {
|
|
55
|
+
parts.push(`Env: ${data.environmentWarningCount} warning${data.environmentWarningCount > 1 ? "s" : ""}`);
|
|
94
56
|
}
|
|
95
|
-
return
|
|
57
|
+
return [` ${parts.join(" │ ")}`];
|
|
96
58
|
}
|
|
@@ -11,12 +11,10 @@ import { runProviderChecks, summariseProviderIssues } from "./doctor-providers.j
|
|
|
11
11
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
12
12
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
13
13
|
import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
|
|
14
|
-
import { describeNextUnit, estimateTimeRemaining, updateSliceProgressCache } from "./auto-dashboard.js";
|
|
15
14
|
import { projectRoot } from "./commands.js";
|
|
16
|
-
import { deriveState, invalidateStateCache } from "./state.js";
|
|
17
15
|
import { buildHealthLines, detectHealthWidgetProjectState, } from "./health-widget-core.js";
|
|
18
16
|
// ── Data loader ────────────────────────────────────────────────────────────────
|
|
19
|
-
function
|
|
17
|
+
function loadHealthWidgetData(basePath) {
|
|
20
18
|
let budgetCeiling;
|
|
21
19
|
let budgetSpent = 0;
|
|
22
20
|
let providerIssue = null;
|
|
@@ -58,86 +56,6 @@ function loadBaseHealthWidgetData(basePath) {
|
|
|
58
56
|
lastRefreshed: Date.now(),
|
|
59
57
|
};
|
|
60
58
|
}
|
|
61
|
-
function compactText(text, max = 64) {
|
|
62
|
-
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
63
|
-
if (trimmed.length <= max)
|
|
64
|
-
return trimmed;
|
|
65
|
-
return `${trimmed.slice(0, max - 1).trimEnd()}…`;
|
|
66
|
-
}
|
|
67
|
-
function summarizeExecutionStatus(state) {
|
|
68
|
-
switch (state.phase) {
|
|
69
|
-
case "blocked": return "Blocked";
|
|
70
|
-
case "paused": return "Paused";
|
|
71
|
-
case "complete": return "Complete";
|
|
72
|
-
case "executing": return "Executing";
|
|
73
|
-
case "planning": return "Planning";
|
|
74
|
-
case "pre-planning": return "Pre-planning";
|
|
75
|
-
case "summarizing": return "Summarizing";
|
|
76
|
-
case "validating-milestone": return "Validating";
|
|
77
|
-
case "completing-milestone": return "Completing";
|
|
78
|
-
case "needs-discussion": return "Needs discussion";
|
|
79
|
-
case "replanning-slice": return "Replanning";
|
|
80
|
-
default: return "Active";
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function summarizeExecutionTarget(state) {
|
|
84
|
-
switch (state.phase) {
|
|
85
|
-
case "needs-discussion":
|
|
86
|
-
return state.activeMilestone ? `Discuss ${state.activeMilestone.id}` : "Discuss milestone draft";
|
|
87
|
-
case "pre-planning":
|
|
88
|
-
return state.activeMilestone ? `Plan ${state.activeMilestone.id}` : "Research & plan milestone";
|
|
89
|
-
case "planning":
|
|
90
|
-
return state.activeSlice ? `Plan ${state.activeSlice.id}` : "Plan next slice";
|
|
91
|
-
case "executing":
|
|
92
|
-
return state.activeTask ? `Execute ${state.activeTask.id}` : "Execute next task";
|
|
93
|
-
case "summarizing":
|
|
94
|
-
return state.activeSlice ? `Complete ${state.activeSlice.id}` : "Complete current slice";
|
|
95
|
-
case "validating-milestone":
|
|
96
|
-
return state.activeMilestone ? `Validate ${state.activeMilestone.id}` : "Validate milestone";
|
|
97
|
-
case "completing-milestone":
|
|
98
|
-
return state.activeMilestone ? `Complete ${state.activeMilestone.id}` : "Complete milestone";
|
|
99
|
-
case "replanning-slice":
|
|
100
|
-
return state.activeSlice ? `Replan ${state.activeSlice.id}` : "Replan current slice";
|
|
101
|
-
case "blocked":
|
|
102
|
-
return `waiting on ${compactText(state.blockers[0] ?? state.nextAction, 56)}`;
|
|
103
|
-
case "paused":
|
|
104
|
-
return compactText(state.nextAction || "waiting to resume", 56);
|
|
105
|
-
case "complete":
|
|
106
|
-
return "All milestones complete";
|
|
107
|
-
default:
|
|
108
|
-
return compactText(describeNextUnit(state).label, 56);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
async function enrichHealthWidgetData(basePath, baseData) {
|
|
112
|
-
if (baseData.projectState !== "active")
|
|
113
|
-
return baseData;
|
|
114
|
-
try {
|
|
115
|
-
invalidateStateCache();
|
|
116
|
-
const state = await deriveState(basePath);
|
|
117
|
-
if (state.activeMilestone) {
|
|
118
|
-
// Warm the slice-progress cache so estimateTimeRemaining() has data
|
|
119
|
-
updateSliceProgressCache(basePath, state.activeMilestone.id, state.activeSlice?.id);
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
...baseData,
|
|
123
|
-
executionPhase: state.phase,
|
|
124
|
-
executionStatus: summarizeExecutionStatus(state),
|
|
125
|
-
executionTarget: summarizeExecutionTarget(state),
|
|
126
|
-
nextAction: state.nextAction,
|
|
127
|
-
blocker: state.blockers[0] ?? null,
|
|
128
|
-
activeMilestoneId: state.activeMilestone?.id,
|
|
129
|
-
activeSliceId: state.activeSlice?.id,
|
|
130
|
-
activeTaskId: state.activeTask?.id,
|
|
131
|
-
progress: state.progress,
|
|
132
|
-
eta: state.phase === "blocked" || state.phase === "paused" || state.phase === "complete"
|
|
133
|
-
? null
|
|
134
|
-
: estimateTimeRemaining(),
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
return baseData;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
59
|
// ── Widget init ────────────────────────────────────────────────────────────────
|
|
142
60
|
const REFRESH_INTERVAL_MS = 60_000;
|
|
143
61
|
/**
|
|
@@ -149,7 +67,7 @@ export function initHealthWidget(ctx) {
|
|
|
149
67
|
return;
|
|
150
68
|
const basePath = projectRoot();
|
|
151
69
|
// String-array fallback — used in RPC mode (factory is a no-op there)
|
|
152
|
-
const initialData =
|
|
70
|
+
const initialData = loadHealthWidgetData(basePath);
|
|
153
71
|
ctx.ui.setWidget("gsd-health", buildHealthLines(initialData), { placement: "belowEditor" });
|
|
154
72
|
// Factory-based widget for TUI mode — replaces the string-array above
|
|
155
73
|
ctx.ui.setWidget("gsd-health", (_tui, _theme) => {
|
|
@@ -161,8 +79,7 @@ export function initHealthWidget(ctx) {
|
|
|
161
79
|
return;
|
|
162
80
|
refreshInFlight = true;
|
|
163
81
|
try {
|
|
164
|
-
|
|
165
|
-
data = await enrichHealthWidgetData(basePath, baseData);
|
|
82
|
+
data = loadHealthWidgetData(basePath);
|
|
166
83
|
cachedLines = undefined;
|
|
167
84
|
_tui.requestRender();
|
|
168
85
|
}
|
|
@@ -41,6 +41,7 @@ import { join } from "node:path";
|
|
|
41
41
|
import { existsSync, readFileSync } from "node:fs";
|
|
42
42
|
import { homedir } from "node:os";
|
|
43
43
|
import { shortcutDesc } from "../shared/mod.js";
|
|
44
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
44
45
|
import { Text } from "@gsd/pi-tui";
|
|
45
46
|
import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
|
|
46
47
|
import { toPosixPath } from "../shared/mod.js";
|
|
@@ -52,7 +53,7 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../cmux/index.js"
|
|
|
52
53
|
// Pi core natively supports AGENTS.md (with CLAUDE.md fallback) per directory.
|
|
53
54
|
function warnDeprecatedAgentInstructions() {
|
|
54
55
|
const paths = [
|
|
55
|
-
join(
|
|
56
|
+
join(gsdHome, "agent-instructions.md"),
|
|
56
57
|
join(process.cwd(), ".gsd", "agent-instructions.md"),
|
|
57
58
|
];
|
|
58
59
|
for (const p of paths) {
|
|
@@ -65,6 +66,24 @@ function warnDeprecatedAgentInstructions() {
|
|
|
65
66
|
}
|
|
66
67
|
// ── Depth verification state ──────────────────────────────────────────────
|
|
67
68
|
let depthVerificationDone = false;
|
|
69
|
+
// ── DB lazy-open helper ───────────────────────────────────────────────────
|
|
70
|
+
// In manual sessions (no auto-mode), the DB is never opened by bootstrapAutoSession.
|
|
71
|
+
// This helper ensures the DB is lazily opened on first tool call that needs it.
|
|
72
|
+
async function ensureDbOpen() {
|
|
73
|
+
try {
|
|
74
|
+
const db = await import("./gsd-db.js");
|
|
75
|
+
if (db.isDbAvailable())
|
|
76
|
+
return true;
|
|
77
|
+
const dbPath = join(process.cwd(), ".gsd", "gsd.db");
|
|
78
|
+
if (existsSync(dbPath)) {
|
|
79
|
+
return db.openDatabase(dbPath);
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
68
87
|
// ── Queue phase tracking ──────────────────────────────────────────────────
|
|
69
88
|
// When true, the LLM is in a queue flow writing CONTEXT.md files.
|
|
70
89
|
// The write-gate applies during queue flows just like discussion flows.
|
|
@@ -227,13 +246,8 @@ export default function (pi) {
|
|
|
227
246
|
when_context: Type.Optional(Type.String({ description: "When/context for the decision (e.g. milestone ID)" })),
|
|
228
247
|
}),
|
|
229
248
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
const db = await import("./gsd-db.js");
|
|
234
|
-
dbAvailable = db.isDbAvailable();
|
|
235
|
-
}
|
|
236
|
-
catch { /* dynamic import failed */ }
|
|
249
|
+
// Ensure DB is open (lazy-open on first tool call in manual sessions)
|
|
250
|
+
const dbAvailable = await ensureDbOpen();
|
|
237
251
|
if (!dbAvailable) {
|
|
238
252
|
return {
|
|
239
253
|
content: [{ type: "text", text: "Error: GSD database is not available. Cannot save decision." }],
|
|
@@ -289,12 +303,7 @@ export default function (pi) {
|
|
|
289
303
|
supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
|
|
290
304
|
}),
|
|
291
305
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
const db = await import("./gsd-db.js");
|
|
295
|
-
dbAvailable = db.isDbAvailable();
|
|
296
|
-
}
|
|
297
|
-
catch { /* dynamic import failed */ }
|
|
306
|
+
const dbAvailable = await ensureDbOpen();
|
|
298
307
|
if (!dbAvailable) {
|
|
299
308
|
return {
|
|
300
309
|
content: [{ type: "text", text: "Error: GSD database is not available. Cannot update requirement." }],
|
|
@@ -364,12 +373,7 @@ export default function (pi) {
|
|
|
364
373
|
content: Type.String({ description: "The full markdown content of the artifact" }),
|
|
365
374
|
}),
|
|
366
375
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
const db = await import("./gsd-db.js");
|
|
370
|
-
dbAvailable = db.isDbAvailable();
|
|
371
|
-
}
|
|
372
|
-
catch { /* dynamic import failed */ }
|
|
376
|
+
const dbAvailable = await ensureDbOpen();
|
|
373
377
|
if (!dbAvailable) {
|
|
374
378
|
return {
|
|
375
379
|
content: [{ type: "text", text: "Error: GSD database is not available. Cannot save artifact." }],
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Pure functions that take file content (string) and return typed data.
|
|
3
3
|
// Zero Pi dependencies — uses only exported helpers from files.ts.
|
|
4
4
|
import { splitFrontmatter, parseFrontmatterMap, extractBoldField } from '../files.js';
|
|
5
|
-
import { normalizeStringArray } from '../../shared/
|
|
5
|
+
import { normalizeStringArray } from '../../shared/format-utils.js';
|
|
6
6
|
// Re-export PlanningProjectMeta — not in types.ts yet, use string for project field
|
|
7
7
|
// Actually PlanningProjectMeta isn't in types.ts — project is stored as string | null.
|
|
8
8
|
// We'll keep parseOldProject returning a simple shape.
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* `~/.gsd/projects/<hash>/` state directory. After migration, a
|
|
6
6
|
* symlink replaces the original directory so all paths remain valid.
|
|
7
7
|
*/
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
8
9
|
import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
import { externalGsdRoot } from "./repo-identity.js";
|
|
11
12
|
import { getErrorMessage } from "./error-utils.js";
|
|
12
13
|
import { hasGitTrackedGsdFiles } from "./gitignore.js";
|
|
14
|
+
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
13
15
|
/**
|
|
14
16
|
* Migrate a legacy in-project `.gsd/` directory to external storage.
|
|
15
17
|
*
|
|
@@ -142,7 +144,22 @@ export function migrateToExternalState(basePath) {
|
|
|
142
144
|
catch { /* best-effort restore */ }
|
|
143
145
|
return { migrated: false, error: `Migration verification failed: ${getErrorMessage(verifyErr)}` };
|
|
144
146
|
}
|
|
145
|
-
//
|
|
147
|
+
// Clean the git index — any .gsd/* files tracked before migration now
|
|
148
|
+
// sit behind the symlink and git can't follow it, causing them to show
|
|
149
|
+
// as deleted. Remove them from the index so the working tree stays clean.
|
|
150
|
+
// --ignore-unmatch makes this a no-op on fresh projects with no tracked .gsd/.
|
|
151
|
+
try {
|
|
152
|
+
execFileSync("git", ["rm", "-r", "--cached", "--ignore-unmatch", ".gsd"], {
|
|
153
|
+
cwd: basePath,
|
|
154
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
155
|
+
env: GIT_NO_PROMPT_ENV,
|
|
156
|
+
timeout: 10_000,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// Non-fatal — git may be unavailable or nothing was tracked
|
|
161
|
+
}
|
|
162
|
+
// Remove .gsd.migrating only after symlink is verified and index is clean
|
|
146
163
|
rmSync(migratingPath, { recursive: true, force: true });
|
|
147
164
|
return { migrated: true };
|
|
148
165
|
}
|
|
@@ -518,6 +518,43 @@ export function nativeAddAll(basePath) {
|
|
|
518
518
|
}
|
|
519
519
|
gitFileExec(basePath, ["add", "-A"]);
|
|
520
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Stage all files with pathspec exclusions (git add -A -- ':!pattern' ...).
|
|
523
|
+
* Excluded paths are never hashed by git, preventing hangs on large
|
|
524
|
+
* untracked artifact trees (57GB+, 11K+ files). See #1605.
|
|
525
|
+
*
|
|
526
|
+
* Falls back to plain `git add -A` when no exclusions are provided.
|
|
527
|
+
* Always uses the CLI path (not libgit2) because libgit2's add_all
|
|
528
|
+
* does not support pathspec exclusion syntax.
|
|
529
|
+
*
|
|
530
|
+
* When excluded paths are already covered by .gitignore, git may exit
|
|
531
|
+
* with code 1 and an "ignored by .gitignore" warning. This is harmless
|
|
532
|
+
* (the staging succeeds for all non-ignored files) and is suppressed.
|
|
533
|
+
*/
|
|
534
|
+
export function nativeAddAllWithExclusions(basePath, exclusions) {
|
|
535
|
+
if (exclusions.length === 0) {
|
|
536
|
+
nativeAddAll(basePath);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const pathspecs = exclusions.map(e => `:!${e}`);
|
|
540
|
+
try {
|
|
541
|
+
execFileSync("git", ["add", "-A", "--", ...pathspecs], {
|
|
542
|
+
cwd: basePath,
|
|
543
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
544
|
+
encoding: "utf-8",
|
|
545
|
+
env: GIT_NO_PROMPT_ENV,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
// git exits 1 when pathspec exclusions reference paths already covered
|
|
550
|
+
// by .gitignore. The staging itself succeeds — only suppress that case.
|
|
551
|
+
const stderr = err?.stderr ?? "";
|
|
552
|
+
if (stderr.includes("ignored by one of your .gitignore files")) {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
521
558
|
/**
|
|
522
559
|
* Stage specific files.
|
|
523
560
|
* Native: libgit2 index add.
|
|
@@ -343,6 +343,9 @@ function probeGsdRoot(rawBasePath) {
|
|
|
343
343
|
export function milestonesDir(basePath) {
|
|
344
344
|
return join(gsdRoot(basePath), "milestones");
|
|
345
345
|
}
|
|
346
|
+
export function resolveRuntimeFile(basePath) {
|
|
347
|
+
return join(gsdRoot(basePath), "RUNTIME.md");
|
|
348
|
+
}
|
|
346
349
|
export function resolveGsdRootFile(basePath, key) {
|
|
347
350
|
const root = gsdRoot(basePath);
|
|
348
351
|
const canonical = join(root, GSD_ROOT_FILES[key]);
|
|
@@ -260,18 +260,6 @@ export function resolveInlineLevel() {
|
|
|
260
260
|
case "quality": return "full";
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
|
-
/**
|
|
264
|
-
* Resolve the compression strategy from the active token profile.
|
|
265
|
-
* budget/balanced -> "compress", quality -> "truncate".
|
|
266
|
-
* Explicit preference always wins.
|
|
267
|
-
*/
|
|
268
|
-
export function resolveCompressionStrategy() {
|
|
269
|
-
const prefs = loadEffectiveGSDPreferences();
|
|
270
|
-
if (prefs?.preferences.compression_strategy)
|
|
271
|
-
return prefs.preferences.compression_strategy;
|
|
272
|
-
const profile = resolveEffectiveProfile();
|
|
273
|
-
return profile === "quality" ? "truncate" : "compress";
|
|
274
|
-
}
|
|
275
263
|
/**
|
|
276
264
|
* Resolve the context selection mode from the active token profile.
|
|
277
265
|
* budget -> "smart", balanced/quality -> "full".
|
|
@@ -62,10 +62,10 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
|
|
|
62
62
|
"verification_auto_fix",
|
|
63
63
|
"verification_max_retries",
|
|
64
64
|
"search_provider",
|
|
65
|
-
"compression_strategy",
|
|
66
65
|
"context_selection",
|
|
67
66
|
"widget_mode",
|
|
68
67
|
"reactive_execution",
|
|
68
|
+
"github",
|
|
69
69
|
]);
|
|
70
70
|
/** Canonical list of all dispatch unit types. */
|
|
71
71
|
export const KNOWN_UNIT_TYPES = [
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* together with any errors and warnings.
|
|
7
7
|
*/
|
|
8
8
|
import { VALID_BRANCH_NAME } from "./git-service.js";
|
|
9
|
-
import { normalizeStringArray } from "../shared/
|
|
9
|
+
import { normalizeStringArray } from "../shared/format-utils.js";
|
|
10
10
|
import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_TYPES, SKILL_ACTIONS, } from "./preferences-types.js";
|
|
11
11
|
const VALID_TOKEN_PROFILES = new Set(["budget", "balanced", "quality"]);
|
|
12
12
|
export function validatePreferences(preferences) {
|
|
@@ -707,16 +707,6 @@ export function validatePreferences(preferences) {
|
|
|
707
707
|
errors.push("auto_report must be a boolean");
|
|
708
708
|
}
|
|
709
709
|
}
|
|
710
|
-
// ─── Compression Strategy ───────────────────────────────────────────
|
|
711
|
-
if (preferences.compression_strategy !== undefined) {
|
|
712
|
-
const validStrategies = new Set(["truncate", "compress"]);
|
|
713
|
-
if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
|
|
714
|
-
validated.compression_strategy = preferences.compression_strategy;
|
|
715
|
-
}
|
|
716
|
-
else {
|
|
717
|
-
errors.push(`compression_strategy must be one of: truncate, compress`);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
710
|
// ─── Context Selection ──────────────────────────────────────────────
|
|
721
711
|
if (preferences.context_selection !== undefined) {
|
|
722
712
|
const validModes = new Set(["full", "smart"]);
|
|
@@ -727,5 +717,63 @@ export function validatePreferences(preferences) {
|
|
|
727
717
|
errors.push(`context_selection must be one of: full, smart`);
|
|
728
718
|
}
|
|
729
719
|
}
|
|
720
|
+
// ─── GitHub Sync ────────────────────────────────────────────────────────
|
|
721
|
+
if (preferences.github !== undefined) {
|
|
722
|
+
if (typeof preferences.github === "object" && preferences.github !== null) {
|
|
723
|
+
const gh = preferences.github;
|
|
724
|
+
const validGh = {};
|
|
725
|
+
if (gh.enabled !== undefined) {
|
|
726
|
+
if (typeof gh.enabled === "boolean")
|
|
727
|
+
validGh.enabled = gh.enabled;
|
|
728
|
+
else
|
|
729
|
+
errors.push("github.enabled must be a boolean");
|
|
730
|
+
}
|
|
731
|
+
if (gh.repo !== undefined) {
|
|
732
|
+
if (typeof gh.repo === "string" && gh.repo.includes("/"))
|
|
733
|
+
validGh.repo = gh.repo;
|
|
734
|
+
else
|
|
735
|
+
errors.push('github.repo must be a string in "owner/repo" format');
|
|
736
|
+
}
|
|
737
|
+
if (gh.project !== undefined) {
|
|
738
|
+
const p = typeof gh.project === "number" ? gh.project : Number(gh.project);
|
|
739
|
+
if (Number.isFinite(p) && p > 0)
|
|
740
|
+
validGh.project = Math.floor(p);
|
|
741
|
+
else
|
|
742
|
+
errors.push("github.project must be a positive number");
|
|
743
|
+
}
|
|
744
|
+
if (gh.labels !== undefined) {
|
|
745
|
+
if (Array.isArray(gh.labels) && gh.labels.every((l) => typeof l === "string")) {
|
|
746
|
+
validGh.labels = gh.labels;
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
errors.push("github.labels must be an array of strings");
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
if (gh.auto_link_commits !== undefined) {
|
|
753
|
+
if (typeof gh.auto_link_commits === "boolean")
|
|
754
|
+
validGh.auto_link_commits = gh.auto_link_commits;
|
|
755
|
+
else
|
|
756
|
+
errors.push("github.auto_link_commits must be a boolean");
|
|
757
|
+
}
|
|
758
|
+
if (gh.slice_prs !== undefined) {
|
|
759
|
+
if (typeof gh.slice_prs === "boolean")
|
|
760
|
+
validGh.slice_prs = gh.slice_prs;
|
|
761
|
+
else
|
|
762
|
+
errors.push("github.slice_prs must be a boolean");
|
|
763
|
+
}
|
|
764
|
+
const knownGhKeys = new Set(["enabled", "repo", "project", "labels", "auto_link_commits", "slice_prs"]);
|
|
765
|
+
for (const key of Object.keys(gh)) {
|
|
766
|
+
if (!knownGhKeys.has(key)) {
|
|
767
|
+
warnings.push(`unknown github key "${key}" — ignored`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (Object.keys(validGh).length > 0) {
|
|
771
|
+
validated.github = validGh;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
errors.push("github must be an object");
|
|
776
|
+
}
|
|
777
|
+
}
|
|
730
778
|
return { preferences: validated, errors, warnings };
|
|
731
779
|
}
|
|
@@ -14,7 +14,7 @@ import { homedir } from "node:os";
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
16
16
|
import { parse as parseYaml } from "yaml";
|
|
17
|
-
import { normalizeStringArray } from "../shared/
|
|
17
|
+
import { normalizeStringArray } from "../shared/format-utils.js";
|
|
18
18
|
import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
|
|
19
19
|
import { MODE_DEFAULTS, } from "./preferences-types.js";
|
|
20
20
|
import { validatePreferences } from "./preferences-validation.js";
|
|
@@ -24,33 +24,42 @@ export { validatePreferences } from "./preferences-validation.js";
|
|
|
24
24
|
// ─── Re-exports: skills ─────────────────────────────────────────────────────
|
|
25
25
|
export { resolveAllSkillReferences, resolveSkillDiscoveryMode, resolveSkillStalenessDays, } from "./preferences-skills.js";
|
|
26
26
|
// ─── Re-exports: models ─────────────────────────────────────────────────────
|
|
27
|
-
export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel,
|
|
27
|
+
export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
|
|
28
28
|
// ─── Path Constants & Getters ───────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
function gsdHome() {
|
|
30
|
+
return process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
31
|
+
}
|
|
32
|
+
function globalPreferencesPath() {
|
|
33
|
+
return join(gsdHome(), "preferences.md");
|
|
34
|
+
}
|
|
35
|
+
function legacyGlobalPreferencesPath() {
|
|
36
|
+
return join(homedir(), ".pi", "agent", "gsd-preferences.md");
|
|
37
|
+
}
|
|
31
38
|
function projectPreferencesPath() {
|
|
32
39
|
return join(gsdRoot(process.cwd()), "preferences.md");
|
|
33
40
|
}
|
|
34
41
|
// Bootstrap in gitignore.ts historically created PREFERENCES.md (uppercase) by mistake.
|
|
35
42
|
// Check uppercase as a fallback so those files aren't silently ignored.
|
|
36
|
-
|
|
43
|
+
function globalPreferencesPathUppercase() {
|
|
44
|
+
return join(gsdHome(), "PREFERENCES.md");
|
|
45
|
+
}
|
|
37
46
|
function projectPreferencesPathUppercase() {
|
|
38
47
|
return join(gsdRoot(process.cwd()), "PREFERENCES.md");
|
|
39
48
|
}
|
|
40
49
|
export function getGlobalGSDPreferencesPath() {
|
|
41
|
-
return
|
|
50
|
+
return globalPreferencesPath();
|
|
42
51
|
}
|
|
43
52
|
export function getLegacyGlobalGSDPreferencesPath() {
|
|
44
|
-
return
|
|
53
|
+
return legacyGlobalPreferencesPath();
|
|
45
54
|
}
|
|
46
55
|
export function getProjectGSDPreferencesPath() {
|
|
47
56
|
return projectPreferencesPath();
|
|
48
57
|
}
|
|
49
58
|
// ─── Loading ────────────────────────────────────────────────────────────────
|
|
50
59
|
export function loadGlobalGSDPreferences() {
|
|
51
|
-
return loadPreferencesFile(
|
|
52
|
-
?? loadPreferencesFile(
|
|
53
|
-
?? loadPreferencesFile(
|
|
60
|
+
return loadPreferencesFile(globalPreferencesPath(), "global")
|
|
61
|
+
?? loadPreferencesFile(globalPreferencesPathUppercase(), "global")
|
|
62
|
+
?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
|
|
54
63
|
}
|
|
55
64
|
export function loadProjectGSDPreferences() {
|
|
56
65
|
return loadPreferencesFile(projectPreferencesPath(), "project")
|
|
@@ -199,10 +208,12 @@ function mergePreferences(base, override) {
|
|
|
199
208
|
verification_auto_fix: override.verification_auto_fix ?? base.verification_auto_fix,
|
|
200
209
|
verification_max_retries: override.verification_max_retries ?? base.verification_max_retries,
|
|
201
210
|
search_provider: override.search_provider ?? base.search_provider,
|
|
202
|
-
compression_strategy: override.compression_strategy ?? base.compression_strategy,
|
|
203
211
|
context_selection: override.context_selection ?? base.context_selection,
|
|
204
212
|
auto_visualize: override.auto_visualize ?? base.auto_visualize,
|
|
205
213
|
auto_report: override.auto_report ?? base.auto_report,
|
|
214
|
+
github: (base.github || override.github)
|
|
215
|
+
? { ...(base.github ?? {}), ...(override.github ?? {}) }
|
|
216
|
+
: undefined,
|
|
206
217
|
};
|
|
207
218
|
}
|
|
208
219
|
function mergeStringLists(base, override) {
|