gsd-pi 2.19.0 → 2.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/cli.js +3 -3
- package/dist/onboarding.d.ts +3 -1
- package/dist/onboarding.js +77 -3
- package/dist/remote-questions-config.d.ts +1 -1
- package/dist/resources/extensions/google-search/index.ts +164 -47
- package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/dist/resources/extensions/gsd/auto.ts +424 -30
- package/dist/resources/extensions/gsd/commands.ts +518 -36
- package/dist/resources/extensions/gsd/context-budget.ts +243 -0
- package/dist/resources/extensions/gsd/context-store.ts +195 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/dist/resources/extensions/gsd/db-writer.ts +341 -0
- package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/dist/resources/extensions/gsd/doctor.ts +283 -2
- package/dist/resources/extensions/gsd/export.ts +81 -2
- package/dist/resources/extensions/gsd/files.ts +39 -9
- package/dist/resources/extensions/gsd/git-service.ts +6 -0
- package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
- package/dist/resources/extensions/gsd/history.ts +0 -1
- package/dist/resources/extensions/gsd/index.ts +277 -1
- package/dist/resources/extensions/gsd/md-importer.ts +526 -0
- package/dist/resources/extensions/gsd/metrics.ts +39 -3
- package/dist/resources/extensions/gsd/notifications.ts +0 -1
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/dist/resources/extensions/gsd/preferences.ts +125 -150
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -1
- package/dist/resources/extensions/gsd/quick.ts +156 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/dist/resources/extensions/gsd/skill-health.ts +417 -0
- package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/dist/resources/extensions/gsd/state.ts +30 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
- package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/dist/resources/extensions/gsd/types.ts +29 -0
- package/dist/resources/extensions/gsd/undo.ts +0 -1
- package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/dist/resources/extensions/remote-questions/config.ts +4 -2
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/dist/resources/extensions/remote-questions/format.ts +154 -8
- package/dist/resources/extensions/remote-questions/manager.ts +9 -7
- package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/dist/resources/extensions/remote-questions/types.ts +2 -1
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/dist/resources/extensions/voice/index.ts +4 -3
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
- package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
- package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
- package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
- package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
- package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
- package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
- package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
- package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
- package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
- package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
- package/src/resources/extensions/google-search/index.ts +164 -47
- package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
- package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
- package/src/resources/extensions/gsd/auto.ts +424 -30
- package/src/resources/extensions/gsd/commands.ts +518 -36
- package/src/resources/extensions/gsd/context-budget.ts +243 -0
- package/src/resources/extensions/gsd/context-store.ts +195 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
- package/src/resources/extensions/gsd/db-writer.ts +341 -0
- package/src/resources/extensions/gsd/debug-logger.ts +178 -0
- package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
- package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
- package/src/resources/extensions/gsd/doctor.ts +283 -2
- package/src/resources/extensions/gsd/export.ts +81 -2
- package/src/resources/extensions/gsd/files.ts +39 -9
- package/src/resources/extensions/gsd/git-service.ts +6 -0
- package/src/resources/extensions/gsd/gsd-db.ts +752 -0
- package/src/resources/extensions/gsd/guided-flow.ts +26 -1
- package/src/resources/extensions/gsd/history.ts +0 -1
- package/src/resources/extensions/gsd/index.ts +277 -1
- package/src/resources/extensions/gsd/md-importer.ts +526 -0
- package/src/resources/extensions/gsd/metrics.ts +39 -3
- package/src/resources/extensions/gsd/notifications.ts +0 -1
- package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
- package/src/resources/extensions/gsd/preferences.ts +125 -150
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
- package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
- package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -1
- package/src/resources/extensions/gsd/quick.ts +156 -0
- package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
- package/src/resources/extensions/gsd/skill-health.ts +417 -0
- package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
- package/src/resources/extensions/gsd/state.ts +30 -0
- package/src/resources/extensions/gsd/templates/preferences.md +1 -0
- package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
- package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
- package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
- package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
- package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
- package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
- package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
- package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
- package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
- package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
- package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
- package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
- package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
- package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
- package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
- package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
- package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
- package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
- package/src/resources/extensions/gsd/types.ts +29 -0
- package/src/resources/extensions/gsd/undo.ts +0 -1
- package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
- package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
- package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
- package/src/resources/extensions/gsd/worktree-command.ts +18 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
- package/src/resources/extensions/remote-questions/config.ts +4 -2
- package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
- package/src/resources/extensions/remote-questions/format.ts +154 -8
- package/src/resources/extensions/remote-questions/manager.ts +9 -7
- package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
- package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
- package/src/resources/extensions/remote-questions/types.ts +2 -1
- package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
- package/src/resources/extensions/voice/index.ts +4 -3
|
@@ -68,6 +68,101 @@ async function getClient(): Promise<GoogleGenAIClient> {
|
|
|
68
68
|
return client;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Perform a search using OAuth credentials via the Cloud Code Assist API.
|
|
73
|
+
* This is used as a fallback when GEMINI_API_KEY is not set.
|
|
74
|
+
*/
|
|
75
|
+
async function searchWithOAuth(
|
|
76
|
+
query: string,
|
|
77
|
+
accessToken: string,
|
|
78
|
+
projectId: string,
|
|
79
|
+
signal?: AbortSignal,
|
|
80
|
+
): Promise<SearchResult> {
|
|
81
|
+
const model = process.env.GEMINI_SEARCH_MODEL || "gemini-2.5-flash";
|
|
82
|
+
const url = `https://cloudcode-pa.googleapis.com/v1internal:streamGenerateContent`;
|
|
83
|
+
|
|
84
|
+
const GEMINI_CLI_HEADERS = {
|
|
85
|
+
ideType: "IDE_UNSPECIFIED",
|
|
86
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
87
|
+
pluginType: "GEMINI",
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const executeFetch = async (retries = 3): Promise<Response> => {
|
|
91
|
+
const response = await fetch(url, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: `Bearer ${accessToken}`,
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
"User-Agent": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
|
97
|
+
"X-Goog-Api-Client": "gl-node/22.17.0",
|
|
98
|
+
"Client-Metadata": JSON.stringify(GEMINI_CLI_HEADERS),
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
project: projectId,
|
|
102
|
+
model,
|
|
103
|
+
request: {
|
|
104
|
+
contents: [{ parts: [{ text: query }] }],
|
|
105
|
+
tools: [{ googleSearch: {} }],
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
signal,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!response.ok && retries > 0 && (response.status === 429 || response.status >= 500)) {
|
|
112
|
+
await new Promise((resolve) => setTimeout(resolve, 1000 * (4 - retries)));
|
|
113
|
+
return executeFetch(retries - 1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return response;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const response = await executeFetch();
|
|
120
|
+
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const errorText = await response.text();
|
|
123
|
+
throw new Error(`Cloud Code Assist API error (${response.status}): ${errorText}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Note: streamGenerateContent returns SSE; for now, we consume all chunks.
|
|
127
|
+
// For simplicity and to match the previous structure, we'll read to end.
|
|
128
|
+
const text = await response.text();
|
|
129
|
+
const jsonLines = text.split("\n")
|
|
130
|
+
.filter(l => l.startsWith("data:"))
|
|
131
|
+
.map(l => l.slice(5).trim())
|
|
132
|
+
.filter(l => l.length > 0);
|
|
133
|
+
|
|
134
|
+
let data;
|
|
135
|
+
if (jsonLines.length > 0) {
|
|
136
|
+
// Aggregate chunks if needed, but for now we take the last chunk or assume it's one
|
|
137
|
+
data = JSON.parse(jsonLines[jsonLines.length - 1]);
|
|
138
|
+
} else {
|
|
139
|
+
data = JSON.parse(text);
|
|
140
|
+
} const candidate = data.response?.candidates?.[0];
|
|
141
|
+
const answer = candidate?.content?.parts?.find((p: any) => p.text)?.text ?? "";
|
|
142
|
+
const grounding = candidate?.groundingMetadata;
|
|
143
|
+
|
|
144
|
+
const sources: SearchSource[] = [];
|
|
145
|
+
const seenTitles = new Set<string>();
|
|
146
|
+
if (grounding?.groundingChunks) {
|
|
147
|
+
for (const chunk of grounding.groundingChunks) {
|
|
148
|
+
if (chunk.web) {
|
|
149
|
+
const title = chunk.web.title ?? "Untitled";
|
|
150
|
+
if (seenTitles.has(title)) continue;
|
|
151
|
+
seenTitles.add(title);
|
|
152
|
+
const domain = chunk.web.domain ?? title;
|
|
153
|
+
sources.push({
|
|
154
|
+
title,
|
|
155
|
+
uri: chunk.web.uri ?? "",
|
|
156
|
+
domain,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const searchQueries = grounding?.webSearchQueries ?? [];
|
|
163
|
+
return { answer, sources, searchQueries, cached: false };
|
|
164
|
+
}
|
|
165
|
+
|
|
71
166
|
// ── In-session cache ─────────────────────────────────────────────────────────
|
|
72
167
|
|
|
73
168
|
const resultCache = new Map<string, SearchResult>();
|
|
@@ -87,7 +182,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
87
182
|
"Returns an AI-synthesized answer grounded in Google Search results, plus source URLs. " +
|
|
88
183
|
"Use this when you need current information from the web: recent events, documentation, " +
|
|
89
184
|
"product details, technical references, news, etc. " +
|
|
90
|
-
"Requires GEMINI_API_KEY. Alternative to Brave-based search tools
|
|
185
|
+
"Requires GEMINI_API_KEY or Google login. Alternative to Brave-based search tools.",
|
|
91
186
|
promptSnippet: "Search the web via Google Search to get current information with sources",
|
|
92
187
|
promptGuidelines: [
|
|
93
188
|
"Use google_search when you need up-to-date web information that isn't in your training data.",
|
|
@@ -109,17 +204,33 @@ export default function (pi: ExtensionAPI) {
|
|
|
109
204
|
),
|
|
110
205
|
}),
|
|
111
206
|
|
|
112
|
-
async execute(_toolCallId, params, signal, _onUpdate,
|
|
207
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
113
208
|
const startTime = Date.now();
|
|
114
209
|
const maxSources = Math.min(Math.max(params.maxSources ?? 5, 1), 10);
|
|
115
210
|
|
|
116
|
-
// Check for
|
|
211
|
+
// Check for credentials
|
|
212
|
+
let oauthToken: string | undefined;
|
|
213
|
+
let projectId: string | undefined;
|
|
214
|
+
|
|
117
215
|
if (!process.env.GEMINI_API_KEY) {
|
|
216
|
+
const oauthRaw = await ctx.modelRegistry.getApiKeyForProvider("google-gemini-cli");
|
|
217
|
+
if (oauthRaw) {
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(oauthRaw);
|
|
220
|
+
oauthToken = parsed.token;
|
|
221
|
+
projectId = parsed.projectId;
|
|
222
|
+
} catch {
|
|
223
|
+
// Fall through to error
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!process.env.GEMINI_API_KEY && (!oauthToken || !projectId)) {
|
|
118
229
|
return {
|
|
119
230
|
content: [
|
|
120
231
|
{
|
|
121
232
|
type: "text",
|
|
122
|
-
text: "Error:
|
|
233
|
+
text: "Error: No authentication found for Google Search. Please set GEMINI_API_KEY or log in via Google.\n\nExample: export GEMINI_API_KEY=your_key or use /login google",
|
|
123
234
|
},
|
|
124
235
|
],
|
|
125
236
|
isError: true,
|
|
@@ -128,7 +239,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
128
239
|
sourceCount: 0,
|
|
129
240
|
cached: false,
|
|
130
241
|
durationMs: Date.now() - startTime,
|
|
131
|
-
error: "auth_error:
|
|
242
|
+
error: "auth_error: No credentials set",
|
|
132
243
|
} as SearchDetails,
|
|
133
244
|
};
|
|
134
245
|
}
|
|
@@ -152,49 +263,52 @@ export default function (pi: ExtensionAPI) {
|
|
|
152
263
|
// Call Gemini with Google Search grounding
|
|
153
264
|
let result: SearchResult;
|
|
154
265
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
266
|
+
if (process.env.GEMINI_API_KEY) {
|
|
267
|
+
const ai = await getClient();
|
|
268
|
+
const response = await ai.models.generateContent({
|
|
269
|
+
model: process.env.GEMINI_SEARCH_MODEL || "gemini-2.5-flash",
|
|
270
|
+
contents: params.query,
|
|
271
|
+
config: {
|
|
272
|
+
tools: [{ googleSearch: {} }],
|
|
273
|
+
abortSignal: signal,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Extract answer text
|
|
278
|
+
const answer = response.text ?? "";
|
|
279
|
+
|
|
280
|
+
// Extract grounding metadata
|
|
281
|
+
const candidate = response.candidates?.[0];
|
|
282
|
+
const grounding = candidate?.groundingMetadata;
|
|
283
|
+
|
|
284
|
+
// Parse sources from grounding chunks
|
|
285
|
+
const sources: SearchSource[] = [];
|
|
286
|
+
const seenTitles = new Set<string>();
|
|
287
|
+
if (grounding?.groundingChunks) {
|
|
288
|
+
for (const chunk of grounding.groundingChunks) {
|
|
289
|
+
if (chunk.web) {
|
|
290
|
+
const title = chunk.web.title ?? "Untitled";
|
|
291
|
+
// Dedupe by title since URIs are redirect URLs that differ per call
|
|
292
|
+
if (seenTitles.has(title)) continue;
|
|
293
|
+
seenTitles.add(title);
|
|
294
|
+
// domain field is not available via Gemini API, use title as fallback
|
|
295
|
+
// (title is typically the domain name, e.g. "wikipedia.org")
|
|
296
|
+
const domain = chunk.web.domain ?? title;
|
|
297
|
+
sources.push({
|
|
298
|
+
title,
|
|
299
|
+
uri: chunk.web.uri ?? "",
|
|
300
|
+
domain,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
190
303
|
}
|
|
191
304
|
}
|
|
192
|
-
}
|
|
193
305
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
306
|
+
// Extract search queries Gemini actually performed
|
|
307
|
+
const searchQueries = grounding?.webSearchQueries ?? [];
|
|
308
|
+
result = { answer, sources, searchQueries, cached: false };
|
|
309
|
+
} else {
|
|
310
|
+
result = await searchWithOAuth(params.query, oauthToken!, projectId!, signal);
|
|
311
|
+
}
|
|
198
312
|
} catch (err: unknown) {
|
|
199
313
|
const msg = err instanceof Error ? err.message : String(err);
|
|
200
314
|
|
|
@@ -287,9 +401,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
287
401
|
// ── Startup notification ─────────────────────────────────────────────────
|
|
288
402
|
|
|
289
403
|
pi.on("session_start", async (_event, ctx) => {
|
|
290
|
-
if (
|
|
404
|
+
if (process.env.GEMINI_API_KEY) return;
|
|
405
|
+
|
|
406
|
+
const hasOAuth = await ctx.modelRegistry.authStorage.hasAuth("google-gemini-cli");
|
|
407
|
+
if (!hasOAuth) {
|
|
291
408
|
ctx.ui.notify(
|
|
292
|
-
"Google Search: No
|
|
409
|
+
"Google Search: No authentication set. Log in via Google or set GEMINI_API_KEY to use google_search.",
|
|
293
410
|
"warning",
|
|
294
411
|
);
|
|
295
412
|
}
|
|
@@ -55,7 +55,7 @@ export async function inlineFileOptional(
|
|
|
55
55
|
* Load and inline dependency slice summaries (full content, not just paths).
|
|
56
56
|
*/
|
|
57
57
|
export async function inlineDependencySummaries(
|
|
58
|
-
mid: string, sid: string, base: string,
|
|
58
|
+
mid: string, sid: string, base: string, budgetChars?: number,
|
|
59
59
|
): Promise<string> {
|
|
60
60
|
const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
61
61
|
const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
|
|
@@ -79,7 +79,14 @@ export async function inlineDependencySummaries(
|
|
|
79
79
|
sections.push(`- \`${relPath}\` _(not found)_`);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
|
|
82
|
+
|
|
83
|
+
const result = sections.join("\n\n");
|
|
84
|
+
// When a budget is provided, truncate at section boundaries to fit
|
|
85
|
+
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
86
|
+
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
87
|
+
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
/**
|
|
@@ -95,6 +102,76 @@ export async function inlineGsdRootFile(
|
|
|
95
102
|
return inlineFileOptional(absPath, relGsdRootFile(key), label);
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
// ─── DB-Aware Inline Helpers ──────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Inline decisions with optional milestone scoping from the DB.
|
|
109
|
+
* Falls back to filesystem via inlineGsdRootFile when DB unavailable or empty.
|
|
110
|
+
*/
|
|
111
|
+
export async function inlineDecisionsFromDb(
|
|
112
|
+
base: string, milestoneId?: string, scope?: string,
|
|
113
|
+
): Promise<string | null> {
|
|
114
|
+
try {
|
|
115
|
+
const { isDbAvailable } = await import("./gsd-db.js");
|
|
116
|
+
if (isDbAvailable()) {
|
|
117
|
+
const { queryDecisions, formatDecisionsForPrompt } = await import("./context-store.js");
|
|
118
|
+
const decisions = queryDecisions({ milestoneId, scope });
|
|
119
|
+
if (decisions.length > 0) {
|
|
120
|
+
const formatted = formatDecisionsForPrompt(decisions);
|
|
121
|
+
return `### Decisions\nSource: \`.gsd/DECISIONS.md\`\n\n${formatted}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
// DB not available — fall through to filesystem
|
|
126
|
+
}
|
|
127
|
+
return inlineGsdRootFile(base, "decisions.md", "Decisions");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Inline requirements with optional slice scoping from the DB.
|
|
132
|
+
* Falls back to filesystem via inlineGsdRootFile when DB unavailable or empty.
|
|
133
|
+
*/
|
|
134
|
+
export async function inlineRequirementsFromDb(
|
|
135
|
+
base: string, sliceId?: string,
|
|
136
|
+
): Promise<string | null> {
|
|
137
|
+
try {
|
|
138
|
+
const { isDbAvailable } = await import("./gsd-db.js");
|
|
139
|
+
if (isDbAvailable()) {
|
|
140
|
+
const { queryRequirements, formatRequirementsForPrompt } = await import("./context-store.js");
|
|
141
|
+
const requirements = queryRequirements({ sliceId });
|
|
142
|
+
if (requirements.length > 0) {
|
|
143
|
+
const formatted = formatRequirementsForPrompt(requirements);
|
|
144
|
+
return `### Requirements\nSource: \`.gsd/REQUIREMENTS.md\`\n\n${formatted}`;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
// DB not available — fall through to filesystem
|
|
149
|
+
}
|
|
150
|
+
return inlineGsdRootFile(base, "requirements.md", "Requirements");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Inline project context from the DB.
|
|
155
|
+
* Falls back to filesystem via inlineGsdRootFile when DB unavailable or empty.
|
|
156
|
+
*/
|
|
157
|
+
export async function inlineProjectFromDb(
|
|
158
|
+
base: string,
|
|
159
|
+
): Promise<string | null> {
|
|
160
|
+
try {
|
|
161
|
+
const { isDbAvailable } = await import("./gsd-db.js");
|
|
162
|
+
if (isDbAvailable()) {
|
|
163
|
+
const { queryProject } = await import("./context-store.js");
|
|
164
|
+
const content = queryProject();
|
|
165
|
+
if (content) {
|
|
166
|
+
return `### Project\nSource: \`.gsd/PROJECT.md\`\n\n${content}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// DB not available — fall through to filesystem
|
|
171
|
+
}
|
|
172
|
+
return inlineGsdRootFile(base, "project.md", "Project");
|
|
173
|
+
}
|
|
174
|
+
|
|
98
175
|
// ─── Skill Discovery ──────────────────────────────────────────────────────
|
|
99
176
|
|
|
100
177
|
/**
|
|
@@ -371,11 +448,11 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
|
|
|
371
448
|
|
|
372
449
|
const inlined: string[] = [];
|
|
373
450
|
inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
|
|
374
|
-
const projectInline = await
|
|
451
|
+
const projectInline = await inlineProjectFromDb(base);
|
|
375
452
|
if (projectInline) inlined.push(projectInline);
|
|
376
|
-
const requirementsInline = await
|
|
453
|
+
const requirementsInline = await inlineRequirementsFromDb(base);
|
|
377
454
|
if (requirementsInline) inlined.push(requirementsInline);
|
|
378
|
-
const decisionsInline = await
|
|
455
|
+
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
379
456
|
if (decisionsInline) inlined.push(decisionsInline);
|
|
380
457
|
const knowledgeInlineRM = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
381
458
|
if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
|
|
@@ -409,12 +486,14 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
409
486
|
const { inlinePriorMilestoneSummary } = await import("./files.js");
|
|
410
487
|
const priorSummaryInline = await inlinePriorMilestoneSummary(mid, base);
|
|
411
488
|
if (priorSummaryInline) inlined.push(priorSummaryInline);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
489
|
+
if (inlineLevel !== "minimal") {
|
|
490
|
+
const projectInline = await inlineProjectFromDb(base);
|
|
491
|
+
if (projectInline) inlined.push(projectInline);
|
|
492
|
+
const requirementsInline = await inlineRequirementsFromDb(base);
|
|
493
|
+
if (requirementsInline) inlined.push(requirementsInline);
|
|
494
|
+
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
495
|
+
if (decisionsInline) inlined.push(decisionsInline);
|
|
496
|
+
}
|
|
418
497
|
const knowledgeInlinePM = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
419
498
|
if (knowledgeInlinePM) inlined.push(knowledgeInlinePM);
|
|
420
499
|
inlined.push(inlineTemplate("roadmap", "Roadmap"));
|
|
@@ -461,9 +540,9 @@ export async function buildResearchSlicePrompt(
|
|
|
461
540
|
if (contextInline) inlined.push(contextInline);
|
|
462
541
|
const researchInline = await inlineFileOptional(milestoneResearchPath, milestoneResearchRel, "Milestone Research");
|
|
463
542
|
if (researchInline) inlined.push(researchInline);
|
|
464
|
-
const decisionsInline = await
|
|
543
|
+
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
465
544
|
if (decisionsInline) inlined.push(decisionsInline);
|
|
466
|
-
const requirementsInline = await
|
|
545
|
+
const requirementsInline = await inlineRequirementsFromDb(base, sid);
|
|
467
546
|
if (requirementsInline) inlined.push(requirementsInline);
|
|
468
547
|
const knowledgeInlineRS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
469
548
|
if (knowledgeInlineRS) inlined.push(knowledgeInlineRS);
|
|
@@ -505,9 +584,9 @@ export async function buildPlanSlicePrompt(
|
|
|
505
584
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
|
|
506
585
|
if (researchInline) inlined.push(researchInline);
|
|
507
586
|
if (inlineLevel !== "minimal") {
|
|
508
|
-
const decisionsInline = await
|
|
587
|
+
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
509
588
|
if (decisionsInline) inlined.push(decisionsInline);
|
|
510
|
-
const requirementsInline = await
|
|
589
|
+
const requirementsInline = await inlineRequirementsFromDb(base, sid);
|
|
511
590
|
if (requirementsInline) inlined.push(requirementsInline);
|
|
512
591
|
}
|
|
513
592
|
const knowledgeInlinePS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
@@ -634,7 +713,7 @@ export async function buildCompleteSlicePrompt(
|
|
|
634
713
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
635
714
|
inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan"));
|
|
636
715
|
if (inlineLevel !== "minimal") {
|
|
637
|
-
const requirementsInline = await
|
|
716
|
+
const requirementsInline = await inlineRequirementsFromDb(base, sid);
|
|
638
717
|
if (requirementsInline) inlined.push(requirementsInline);
|
|
639
718
|
}
|
|
640
719
|
const knowledgeInlineCS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
@@ -705,11 +784,11 @@ export async function buildCompleteMilestonePrompt(
|
|
|
705
784
|
|
|
706
785
|
// Inline root GSD files (skip for minimal — completion can read these if needed)
|
|
707
786
|
if (inlineLevel !== "minimal") {
|
|
708
|
-
const requirementsInline = await
|
|
787
|
+
const requirementsInline = await inlineRequirementsFromDb(base);
|
|
709
788
|
if (requirementsInline) inlined.push(requirementsInline);
|
|
710
|
-
const decisionsInline = await
|
|
789
|
+
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
711
790
|
if (decisionsInline) inlined.push(decisionsInline);
|
|
712
|
-
const projectInline = await
|
|
791
|
+
const projectInline = await inlineProjectFromDb(base);
|
|
713
792
|
if (projectInline) inlined.push(projectInline);
|
|
714
793
|
}
|
|
715
794
|
const knowledgeInlineCM = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
@@ -767,7 +846,7 @@ export async function buildReplanSlicePrompt(
|
|
|
767
846
|
}
|
|
768
847
|
|
|
769
848
|
// Inline decisions
|
|
770
|
-
const decisionsInline = await
|
|
849
|
+
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
771
850
|
if (decisionsInline) inlined.push(decisionsInline);
|
|
772
851
|
const replanActiveOverrides = await loadActiveOverrides(base);
|
|
773
852
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
@@ -818,7 +897,7 @@ export async function buildRunUatPrompt(
|
|
|
818
897
|
if (summaryInline) inlined.push(summaryInline);
|
|
819
898
|
}
|
|
820
899
|
|
|
821
|
-
const projectInline = await
|
|
900
|
+
const projectInline = await inlineProjectFromDb(base);
|
|
822
901
|
if (projectInline) inlined.push(projectInline);
|
|
823
902
|
|
|
824
903
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
@@ -850,11 +929,11 @@ export async function buildReassessRoadmapPrompt(
|
|
|
850
929
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Current Roadmap"));
|
|
851
930
|
inlined.push(await inlineFile(summaryPath, summaryRel, `${completedSliceId} Summary`));
|
|
852
931
|
if (inlineLevel !== "minimal") {
|
|
853
|
-
const projectInline = await
|
|
932
|
+
const projectInline = await inlineProjectFromDb(base);
|
|
854
933
|
if (projectInline) inlined.push(projectInline);
|
|
855
|
-
const requirementsInline = await
|
|
934
|
+
const requirementsInline = await inlineRequirementsFromDb(base);
|
|
856
935
|
if (requirementsInline) inlined.push(requirementsInline);
|
|
857
|
-
const decisionsInline = await
|
|
936
|
+
const decisionsInline = await inlineDecisionsFromDb(base, mid);
|
|
858
937
|
if (decisionsInline) inlined.push(decisionsInline);
|
|
859
938
|
}
|
|
860
939
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { existsSync, cpSync, readFileSync, realpathSync, utimesSync } from "node:fs";
|
|
10
|
-
import { join, resolve } from "node:path";
|
|
11
|
-
import {
|
|
10
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
11
|
+
import { copyWorktreeDb, reconcileWorktreeDb, isDbAvailable } from "./gsd-db.js";
|
|
12
|
+
import { execSync, execFileSync } from "node:child_process";
|
|
12
13
|
import {
|
|
13
14
|
createWorktree,
|
|
14
15
|
removeWorktree,
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
nativeAddPaths,
|
|
34
35
|
nativeRmForce,
|
|
35
36
|
nativeBranchDelete,
|
|
37
|
+
nativeBranchExists,
|
|
36
38
|
} from "./native-git-bridge.js";
|
|
37
39
|
|
|
38
40
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
@@ -75,6 +77,48 @@ function nudgeGitBranchCache(previousCwd: string): void {
|
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
// ─── Worktree Post-Create Hook (#597) ────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run the user-configured post-create hook script after worktree creation.
|
|
84
|
+
* The script receives SOURCE_DIR and WORKTREE_DIR as environment variables.
|
|
85
|
+
* Failure is non-fatal — returns the error message or null on success.
|
|
86
|
+
*
|
|
87
|
+
* Reads the hook path from git.worktree_post_create in preferences.
|
|
88
|
+
* Pass hookPath directly to bypass preference loading (useful for testing).
|
|
89
|
+
*/
|
|
90
|
+
export function runWorktreePostCreateHook(sourceDir: string, worktreeDir: string, hookPath?: string): string | null {
|
|
91
|
+
if (hookPath === undefined) {
|
|
92
|
+
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
93
|
+
hookPath = prefs?.worktree_post_create;
|
|
94
|
+
}
|
|
95
|
+
if (!hookPath) return null;
|
|
96
|
+
|
|
97
|
+
// Resolve relative paths against the source project root
|
|
98
|
+
const resolved = isAbsolute(hookPath) ? hookPath : join(sourceDir, hookPath);
|
|
99
|
+
if (!existsSync(resolved)) {
|
|
100
|
+
return `Worktree post-create hook not found: ${resolved}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
execSync(resolved, {
|
|
105
|
+
cwd: worktreeDir,
|
|
106
|
+
env: {
|
|
107
|
+
...process.env,
|
|
108
|
+
SOURCE_DIR: sourceDir,
|
|
109
|
+
WORKTREE_DIR: worktreeDir,
|
|
110
|
+
},
|
|
111
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
112
|
+
encoding: "utf-8",
|
|
113
|
+
timeout: 30_000, // 30 second timeout
|
|
114
|
+
});
|
|
115
|
+
return null;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
118
|
+
return `Worktree post-create hook failed: ${msg}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
78
122
|
// ─── Auto-Worktree Branch Naming ───────────────────────────────────────────
|
|
79
123
|
|
|
80
124
|
export function autoWorktreeBranch(milestoneId: string): string {
|
|
@@ -93,11 +137,21 @@ export function autoWorktreeBranch(milestoneId: string): string {
|
|
|
93
137
|
export function createAutoWorktree(basePath: string, milestoneId: string): string {
|
|
94
138
|
const branch = autoWorktreeBranch(milestoneId);
|
|
95
139
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
const
|
|
140
|
+
// Check if the milestone branch already exists — it survives auto-mode
|
|
141
|
+
// stop/pause and contains committed work from prior sessions. If it exists,
|
|
142
|
+
// re-attach the worktree to it WITHOUT resetting. Only create a fresh branch
|
|
143
|
+
// from the integration branch when no prior work exists.
|
|
144
|
+
const branchExists = nativeBranchExists(basePath, branch);
|
|
145
|
+
|
|
146
|
+
let info: { name: string; path: string; branch: string; exists: boolean };
|
|
147
|
+
if (branchExists) {
|
|
148
|
+
// Re-attach worktree to the existing milestone branch (preserving commits)
|
|
149
|
+
info = createWorktree(basePath, milestoneId, { branch, reuseExistingBranch: true });
|
|
150
|
+
} else {
|
|
151
|
+
// Fresh start — create branch from integration branch
|
|
152
|
+
const integrationBranch = readIntegrationBranch(basePath, milestoneId) ?? undefined;
|
|
153
|
+
info = createWorktree(basePath, milestoneId, { branch, startPoint: integrationBranch });
|
|
154
|
+
}
|
|
101
155
|
|
|
102
156
|
// Copy .gsd/ planning artifacts from the source repo into the new worktree.
|
|
103
157
|
// Worktrees are fresh git checkouts — untracked files don't carry over.
|
|
@@ -106,6 +160,13 @@ export function createAutoWorktree(basePath: string, milestoneId: string): strin
|
|
|
106
160
|
// on plan-slice because the plan file doesn't exist in the worktree.
|
|
107
161
|
copyPlanningArtifacts(basePath, info.path);
|
|
108
162
|
|
|
163
|
+
// Run user-configured post-create hook (#597) — e.g. copy .env, symlink assets
|
|
164
|
+
const hookError = runWorktreePostCreateHook(basePath, info.path);
|
|
165
|
+
if (hookError) {
|
|
166
|
+
// Non-fatal — log but don't prevent worktree usage
|
|
167
|
+
console.error(`[GSD] ${hookError}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
109
170
|
const previousCwd = process.cwd();
|
|
110
171
|
|
|
111
172
|
try {
|
|
@@ -151,14 +212,28 @@ function copyPlanningArtifacts(srcBase: string, wtPath: string): void {
|
|
|
151
212
|
} catch { /* non-fatal */ }
|
|
152
213
|
}
|
|
153
214
|
}
|
|
215
|
+
|
|
216
|
+
// Copy gsd.db if present in source
|
|
217
|
+
const srcDb = join(srcGsd, "gsd.db");
|
|
218
|
+
const destDb = join(dstGsd, "gsd.db");
|
|
219
|
+
if (existsSync(srcDb)) {
|
|
220
|
+
try {
|
|
221
|
+
copyWorktreeDb(srcDb, destDb);
|
|
222
|
+
} catch { /* non-fatal */ }
|
|
223
|
+
}
|
|
154
224
|
}
|
|
155
225
|
|
|
156
226
|
/**
|
|
157
227
|
* Teardown an auto-worktree: chdir back to original base, then remove
|
|
158
228
|
* the worktree and its branch.
|
|
159
229
|
*/
|
|
160
|
-
export function teardownAutoWorktree(
|
|
230
|
+
export function teardownAutoWorktree(
|
|
231
|
+
originalBasePath: string,
|
|
232
|
+
milestoneId: string,
|
|
233
|
+
opts: { preserveBranch?: boolean } = {},
|
|
234
|
+
): void {
|
|
161
235
|
const branch = autoWorktreeBranch(milestoneId);
|
|
236
|
+
const { preserveBranch = false } = opts;
|
|
162
237
|
const previousCwd = process.cwd();
|
|
163
238
|
|
|
164
239
|
try {
|
|
@@ -171,7 +246,7 @@ export function teardownAutoWorktree(originalBasePath: string, milestoneId: stri
|
|
|
171
246
|
}
|
|
172
247
|
|
|
173
248
|
nudgeGitBranchCache(previousCwd);
|
|
174
|
-
removeWorktree(originalBasePath, milestoneId, { branch });
|
|
249
|
+
removeWorktree(originalBasePath, milestoneId, { branch, deleteBranch: !preserveBranch });
|
|
175
250
|
}
|
|
176
251
|
|
|
177
252
|
/**
|
|
@@ -299,6 +374,15 @@ export function mergeMilestoneToMain(
|
|
|
299
374
|
// 1. Auto-commit dirty state in worktree before leaving
|
|
300
375
|
autoCommitDirtyState(worktreeCwd);
|
|
301
376
|
|
|
377
|
+
// Reconcile worktree DB into main DB before leaving worktree context
|
|
378
|
+
if (isDbAvailable()) {
|
|
379
|
+
try {
|
|
380
|
+
const worktreeDbPath = join(worktreeCwd, ".gsd", "gsd.db");
|
|
381
|
+
const mainDbPath = join(originalBasePath_, ".gsd", "gsd.db");
|
|
382
|
+
reconcileWorktreeDb(mainDbPath, worktreeDbPath);
|
|
383
|
+
} catch { /* non-fatal */ }
|
|
384
|
+
}
|
|
385
|
+
|
|
302
386
|
// 2. Parse roadmap for slice listing
|
|
303
387
|
const roadmap = parseRoadmap(roadmapContent);
|
|
304
388
|
const completedSlices = roadmap.slices.filter(s => s.done);
|