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
|
@@ -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) {
|
|
@@ -74,6 +74,10 @@ export function loadPrompt(name, vars = {}) {
|
|
|
74
74
|
content = readFileSync(path, "utf-8");
|
|
75
75
|
templateCache.set(name, content);
|
|
76
76
|
}
|
|
77
|
+
const effectiveVars = {
|
|
78
|
+
skillActivation: "If a `GSD Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.",
|
|
79
|
+
...vars,
|
|
80
|
+
};
|
|
77
81
|
// Check BEFORE substitution: find all {{varName}} placeholders the template
|
|
78
82
|
// declares and verify every one has a value in vars. Checking after substitution
|
|
79
83
|
// would also flag {{...}} patterns injected by inlined content (e.g. template
|
|
@@ -82,14 +86,14 @@ export function loadPrompt(name, vars = {}) {
|
|
|
82
86
|
if (declared) {
|
|
83
87
|
const missing = [...new Set(declared)]
|
|
84
88
|
.map(m => m.slice(2, -2))
|
|
85
|
-
.filter(key => !(key in
|
|
89
|
+
.filter(key => !(key in effectiveVars));
|
|
86
90
|
if (missing.length > 0) {
|
|
87
91
|
throw new GSDError(GSD_PARSE_ERROR, `loadPrompt("${name}"): template declares {{${missing.join("}}, {{")}}}} but no value was provided. ` +
|
|
88
92
|
`This usually means the extension code in memory is older than the template on disk. ` +
|
|
89
93
|
`Restart pi to reload the extension.`);
|
|
90
94
|
}
|
|
91
95
|
}
|
|
92
|
-
for (const [key, value] of Object.entries(
|
|
96
|
+
for (const [key, value] of Object.entries(effectiveVars)) {
|
|
93
97
|
content = content.replaceAll(`{{${key}}}`, value);
|
|
94
98
|
}
|
|
95
99
|
return content.trim();
|
|
@@ -16,7 +16,7 @@ All relevant context has been preloaded below — the roadmap, all slice summari
|
|
|
16
16
|
|
|
17
17
|
Then:
|
|
18
18
|
1. Use the **Milestone Summary** output template from the inlined context above
|
|
19
|
-
2.
|
|
19
|
+
2. {{skillActivation}}
|
|
20
20
|
3. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. List any criterion that was NOT met.
|
|
21
21
|
4. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
|
|
22
22
|
5. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
|
|
@@ -20,7 +20,7 @@ All relevant context has been preloaded below — the slice plan, all task summa
|
|
|
20
20
|
|
|
21
21
|
Then:
|
|
22
22
|
1. Use the **Slice Summary** and **UAT** output templates from the inlined context above
|
|
23
|
-
2.
|
|
23
|
+
2. {{skillActivation}}
|
|
24
24
|
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first.
|
|
25
25
|
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
|
|
26
26
|
5. If `.gsd/REQUIREMENTS.md` exists, update it based on what this slice actually proved. Move requirements between Active, Validated, Deferred, Blocked, or Out of Scope only when the evidence from execution supports that change.
|
|
@@ -11,7 +11,7 @@ After the user describes their idea, **do not ask questions yet**. First, prove
|
|
|
11
11
|
1. Summarize what you understood in your own words — concretely, not abstractly.
|
|
12
12
|
2. Give an honest size read: roughly how many milestones, roughly how many slices in the first one. Base this on the actual work involved, not a classification label. A config change might be 1 milestone with 1 slice. A social network might be 5 milestones with 8+ slices each. Use your judgment.
|
|
13
13
|
3. Include scope honesty — a bullet list of the major capabilities you're hearing: "Here's what I'm hearing: [bullet list of major capabilities]."
|
|
14
|
-
4.
|
|
14
|
+
4. Invite correction in one plain sentence: "Here's my read. Correct anything important I missed." — plain text, not `ask_user_questions`.
|
|
15
15
|
|
|
16
16
|
This prevents runaway questioning by forcing comprehension proof before anything else. Do not skip this step. Do not combine it with the first question round.
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ After reflection is confirmed, decide the approach based on the actual scope —
|
|
|
21
21
|
|
|
22
22
|
**If the work spans multiple milestones:** Before drilling into details, map the full landscape:
|
|
23
23
|
1. Propose a milestone sequence — names, one-line intents, rough dependencies
|
|
24
|
-
2. Present this
|
|
24
|
+
2. Present this as the working milestone sequence. Adjust it if the user objects, sharpens it, or adds constraints; otherwise keep moving.
|
|
25
25
|
3. Only then begin the deep Q&A — and scope the Q&A to the full vision, not just M001
|
|
26
26
|
|
|
27
27
|
**If the work fits in a single milestone:** Proceed directly to questioning.
|
|
@@ -48,7 +48,7 @@ You are a thinking partner, not an interviewer.
|
|
|
48
48
|
|
|
49
49
|
**Challenge vagueness, make abstract concrete.** When the user says something abstract ("it should be smart" / "it needs to handle edge cases" / "good UX"), push for specifics. What does "smart" mean in practice? Which edge cases? What does good UX look like for this specific interaction?
|
|
50
50
|
|
|
51
|
-
**
|
|
51
|
+
**Lead with experience, but ask implementation when it materially matters.** Default questions should target the experience and outcome. But when implementation choices materially change scope, proof, compliance, integration, deployment, or irreversible architecture, ask them directly instead of forcing a fake UX phrasing.
|
|
52
52
|
|
|
53
53
|
**Freeform rule:** When the user selects "Other" or clearly wants to explain something freely, stop using `ask_user_questions` and switch to plain text follow-ups. Let them talk. Resume structured questions when appropriate.
|
|
54
54
|
|
|
@@ -105,16 +105,13 @@ Example flow:
|
|
|
105
105
|
|
|
106
106
|
If they clarify, absorb the correction and re-verify.
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Only after the depth checklist is fully satisfied and you genuinely understand the work, offer to proceed.
|
|
108
|
+
The depth verification is the required write-gate. Do **not** add another meta "ready to proceed?" checkpoint immediately after it unless there is still material ambiguity.
|
|
111
109
|
|
|
112
|
-
|
|
113
|
-
"Here's what I'm planning to build: [list of capabilities with rough complexity]. Does this match your vision, or did I miss something?"
|
|
110
|
+
## Wrap-up Gate
|
|
114
111
|
|
|
115
|
-
|
|
112
|
+
Once the depth checklist is fully satisfied, move directly into requirements and roadmap preview. Do not insert a separate "are you ready to continue?" gate unless the user explicitly wants to keep brainstorming or you still see material ambiguity.
|
|
116
113
|
|
|
117
|
-
If
|
|
114
|
+
If you need a final scope reflection, fold it into the depth summary or roadmap preview rather than asking for permission twice.
|
|
118
115
|
|
|
119
116
|
## Focused Research
|
|
120
117
|
|
|
@@ -165,9 +162,9 @@ Rules:
|
|
|
165
162
|
|
|
166
163
|
For multi-milestone projects, requirements should span the full vision. Requirements owned by later milestones get provisional ownership. The full requirement set captures the user's complete vision — milestones are the sequencing strategy, not the scope boundary.
|
|
167
164
|
|
|
168
|
-
If the project is new or has no `REQUIREMENTS.md`,
|
|
165
|
+
If the project is new or has no `REQUIREMENTS.md`, surface candidate requirements in chat before writing the roadmap. Ask for correction only on material omissions, wrong ownership, or wrong scope. If the user has already been specific and raises no substantive objection, treat the requirement set as confirmed and continue.
|
|
169
166
|
|
|
170
|
-
**Print the requirements in chat before
|
|
167
|
+
**Print the requirements in chat before writing the roadmap.** Do not say "here are the requirements" and then only write them to a file. The user must see them in the terminal. Print a markdown table with columns: ID, Title, Status, Owner, Source. Group by status (Active, Deferred, Out of Scope). After the table, ask: "Confirm, adjust, or add?"
|
|
171
168
|
|
|
172
169
|
## Scope Assessment
|
|
173
170
|
|
|
@@ -179,7 +176,7 @@ Before moving to output, confirm the size estimate from your reflection still ho
|
|
|
179
176
|
|
|
180
177
|
Before writing any files, **print the planned roadmap in chat** so the user can see and approve it. Print a markdown table with columns: Slice, Title, Risk, Depends, Demo. One row per slice. Below the table, print the milestone definition of done as a bullet list.
|
|
181
178
|
|
|
182
|
-
|
|
179
|
+
If the user raises a substantive objection, adjust the roadmap. Otherwise, present the roadmap and ask: "Ready to write, or want to adjust?" — one gate, not two.
|
|
183
180
|
|
|
184
181
|
### Naming Convention
|
|
185
182
|
|
|
@@ -236,7 +233,7 @@ If a milestone has no dependencies, omit the frontmatter. The dependency chain f
|
|
|
236
233
|
|
|
237
234
|
#### Phase 3: Sequential readiness gate for remaining milestones
|
|
238
235
|
|
|
239
|
-
For each remaining milestone **one at a time, in sequence**, use `ask_user_questions` to
|
|
236
|
+
For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then use `ask_user_questions` to let the user correct that recommendation. Present three options:
|
|
240
237
|
|
|
241
238
|
- **"Discuss now"** — The user wants to conduct a focused discussion for this milestone in the current session, while the context from the broader discussion is still fresh. Proceed with a focused discussion for this milestone (reflection → investigation → questioning → depth verification). When the discussion concludes, write a full `CONTEXT.md`. Then move to the gate for the next milestone.
|
|
242
239
|
- **"Write draft for later"** — This milestone has seed material from the current conversation but needs its own dedicated discussion in a future session. Write a `CONTEXT-DRAFT.md` capturing the seed material (what was discussed, key ideas, provisional scope, open questions). Mark it clearly as a draft, not a finalized context. **What happens downstream:** When auto-mode reaches this milestone, it pauses and notifies the user: "M00x has draft context — needs discussion. Run /gsd." The `/gsd` wizard shows a "Discuss from draft" option that seeds the new discussion with this draft, so nothing from the current conversation is lost. After the dedicated discussion produces a full CONTEXT.md, the draft file is automatically deleted.
|