gsd-pi 2.38.0-dev.bc2e21e → 2.38.0-dev.d533afb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resource-loader.js +34 -1
- 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 +3 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-loop.js +292 -263
- package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
- package/dist/resources/extensions/gsd/auto-start.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -80
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +2 -1
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-providers.js +27 -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/files.js +4 -0
- package/dist/resources/extensions/gsd/git-service.js +15 -12
- package/dist/resources/extensions/gsd/guided-flow.js +82 -32
- package/dist/resources/extensions/gsd/index.js +22 -19
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -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 +58 -10
- package/dist/resources/extensions/gsd/preferences.js +4 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/dist/resources/extensions/gsd/repo-identity.js +19 -3
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -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/src/core/extensions/loader.ts +223 -7
- 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 +3 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-loop.ts +382 -360
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
- package/src/resources/extensions/gsd/auto-start.ts +11 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -86
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +2 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-providers.ts +26 -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/files.ts +3 -1
- package/src/resources/extensions/gsd/git-service.ts +20 -10
- package/src/resources/extensions/gsd/guided-flow.ts +110 -38
- package/src/resources/extensions/gsd/index.ts +21 -16
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -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 +50 -10
- package/src/resources/extensions/gsd/preferences.ts +3 -2
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/src/resources/extensions/gsd/repo-identity.ts +20 -3
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -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/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/mcp-client/index.ts +17 -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
|
@@ -51,10 +51,12 @@ function modelToProviderId(model: string): string | null {
|
|
|
51
51
|
const prefix = model.split("/")[0].toLowerCase();
|
|
52
52
|
// Map known prefixes to registry IDs
|
|
53
53
|
const prefixMap: Record<string, string> = {
|
|
54
|
+
"anthropic-vertex": "anthropic-vertex",
|
|
54
55
|
openrouter: "openrouter",
|
|
55
56
|
groq: "groq",
|
|
56
57
|
mistral: "mistral",
|
|
57
58
|
google: "google",
|
|
59
|
+
"google-vertex": "google-vertex",
|
|
58
60
|
anthropic: "anthropic",
|
|
59
61
|
openai: "openai",
|
|
60
62
|
"github-copilot": "github-copilot",
|
|
@@ -88,11 +90,20 @@ function collectConfiguredModelProviders(): Set<string> {
|
|
|
88
90
|
|
|
89
91
|
const modelEntries = typeof models === "object" ? Object.values(models) : [];
|
|
90
92
|
for (const entry of modelEntries) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if (typeof entry === "string") {
|
|
94
|
+
const pid = modelToProviderId(entry);
|
|
95
|
+
if (pid) providers.add(pid);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof entry === "object" && entry !== null && "model" in entry) {
|
|
100
|
+
const configuredProvider = "provider" in entry ? (entry as { provider?: unknown }).provider : undefined;
|
|
101
|
+
if (typeof configuredProvider === "string" && configuredProvider.trim().length > 0) {
|
|
102
|
+
providers.add(configuredProvider);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const modelId = String((entry as { model: unknown }).model);
|
|
96
107
|
const pid = modelToProviderId(modelId);
|
|
97
108
|
if (pid) providers.add(pid);
|
|
98
109
|
}
|
|
@@ -175,7 +186,9 @@ function checkLlmProviders(): ProviderCheckResult[] {
|
|
|
175
186
|
|
|
176
187
|
for (const providerId of required) {
|
|
177
188
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
178
|
-
const label =
|
|
189
|
+
const label = providerId === "anthropic-vertex"
|
|
190
|
+
? "Anthropic Vertex"
|
|
191
|
+
: info?.label ?? providerId;
|
|
179
192
|
const lookup = resolveKey(providerId);
|
|
180
193
|
|
|
181
194
|
if (!lookup.found) {
|
|
@@ -196,14 +209,18 @@ function checkLlmProviders(): ProviderCheckResult[] {
|
|
|
196
209
|
continue;
|
|
197
210
|
}
|
|
198
211
|
|
|
199
|
-
const envVar =
|
|
212
|
+
const envVar = providerId === "anthropic-vertex"
|
|
213
|
+
? "ANTHROPIC_VERTEX_PROJECT_ID"
|
|
214
|
+
: info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
|
|
200
215
|
results.push({
|
|
201
216
|
name: providerId,
|
|
202
217
|
label,
|
|
203
218
|
category: "llm",
|
|
204
219
|
status: "error",
|
|
205
|
-
message: `${label} —
|
|
206
|
-
detail:
|
|
220
|
+
message: `${label} — not configured`,
|
|
221
|
+
detail: providerId === "anthropic-vertex"
|
|
222
|
+
? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
|
|
223
|
+
: info?.hasOAuth
|
|
207
224
|
? `Run /gsd keys to authenticate`
|
|
208
225
|
: `Set ${envVar} or run /gsd keys`,
|
|
209
226
|
required: true,
|
|
@@ -280,9 +280,24 @@ async function markSliceDoneInRoadmap(basePath: string, milestoneId: string, sli
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
async function markSliceUndoneInRoadmap(basePath: string, milestoneId: string, sliceId: string, fixesApplied: string[]): Promise<void> {
|
|
284
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
285
|
+
if (!roadmapPath) return;
|
|
286
|
+
const content = await loadFile(roadmapPath);
|
|
287
|
+
if (!content) return;
|
|
288
|
+
const updated = content.replace(
|
|
289
|
+
new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"),
|
|
290
|
+
`$1[ ] **${sliceId}:`,
|
|
291
|
+
);
|
|
292
|
+
if (updated !== content) {
|
|
293
|
+
await saveFile(roadmapPath, updated);
|
|
294
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
283
298
|
function matchesScope(unitId: string, scope?: string): boolean {
|
|
284
299
|
if (!scope) return true;
|
|
285
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
300
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
286
301
|
}
|
|
287
302
|
|
|
288
303
|
function auditRequirements(content: string | null): DoctorIssue[] {
|
|
@@ -863,6 +878,12 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
863
878
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
864
879
|
fixable: true,
|
|
865
880
|
});
|
|
881
|
+
if (!allTasksDone) {
|
|
882
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
883
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
884
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
866
887
|
}
|
|
867
888
|
|
|
868
889
|
if (slice.done && !hasSliceUat) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { importExtensionModule, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI, reason?: string) => Promise<void>;
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ export function registerExitCommand(
|
|
|
10
10
|
description: "Exit GSD gracefully",
|
|
11
11
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
12
12
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
13
|
-
const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
|
|
13
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule<typeof import("./auto.js")>(import.meta.url, "./auto.js")).stopAuto;
|
|
14
14
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
15
15
|
ctx.shutdown();
|
|
16
16
|
},
|
|
@@ -775,7 +775,7 @@ export function parseTaskPlanIO(content: string): { inputFiles: string[]; output
|
|
|
775
775
|
* The four UAT classification types recognised by GSD auto-mode.
|
|
776
776
|
* `undefined` is returned (not this union) when no type can be determined.
|
|
777
777
|
*/
|
|
778
|
-
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed';
|
|
778
|
+
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed' | 'browser-executable' | 'runtime-executable';
|
|
779
779
|
|
|
780
780
|
/**
|
|
781
781
|
* Extract the UAT type from a UAT file's raw content.
|
|
@@ -799,6 +799,8 @@ export function extractUatType(content: string): UatType | undefined {
|
|
|
799
799
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
800
800
|
|
|
801
801
|
if (rawValue.startsWith('artifact-driven')) return 'artifact-driven';
|
|
802
|
+
if (rawValue.startsWith('browser-executable')) return 'browser-executable';
|
|
803
|
+
if (rawValue.startsWith('runtime-executable')) return 'runtime-executable';
|
|
802
804
|
if (rawValue.startsWith('live-runtime')) return 'live-runtime';
|
|
803
805
|
if (rawValue.startsWith('human-experience')) return 'human-experience';
|
|
804
806
|
if (rawValue.startsWith('mixed')) return 'mixed';
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
nativeDetectMainBranch,
|
|
25
25
|
nativeBranchExists,
|
|
26
26
|
nativeHasChanges,
|
|
27
|
-
|
|
27
|
+
nativeAddAllWithExclusions,
|
|
28
28
|
nativeResetPaths,
|
|
29
29
|
nativeHasStagedChanges,
|
|
30
30
|
nativeCommit,
|
|
@@ -95,6 +95,8 @@ export interface TaskCommitContext {
|
|
|
95
95
|
oneLiner?: string;
|
|
96
96
|
/** Files modified by this task (from task summary frontmatter) */
|
|
97
97
|
keyFiles?: string[];
|
|
98
|
+
/** GitHub issue number — appends "Resolves #N" trailer when set. */
|
|
99
|
+
issueNumber?: number;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
/**
|
|
@@ -118,12 +120,22 @@ export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
|
|
|
118
120
|
const subject = `${type}(${scope}): ${truncated}`;
|
|
119
121
|
|
|
120
122
|
// Build body with key files if available
|
|
123
|
+
const bodyParts: string[] = [];
|
|
124
|
+
|
|
121
125
|
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
122
126
|
const fileLines = ctx.keyFiles
|
|
123
127
|
.slice(0, 8) // cap at 8 files to keep commit concise
|
|
124
128
|
.map(f => `- ${f}`)
|
|
125
129
|
.join("\n");
|
|
126
|
-
|
|
130
|
+
bodyParts.push(fileLines);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (ctx.issueNumber) {
|
|
134
|
+
bodyParts.push(`Resolves #${ctx.issueNumber}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (bodyParts.length > 0) {
|
|
138
|
+
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
return subject;
|
|
@@ -373,7 +385,9 @@ export class GitServiceImpl {
|
|
|
373
385
|
this._runtimeFilesCleanedUp = true;
|
|
374
386
|
}
|
|
375
387
|
|
|
376
|
-
// Stage everything
|
|
388
|
+
// Stage everything using pathspec exclusions so excluded paths are never
|
|
389
|
+
// hashed by git. The old approach of `git add -A` followed by unstaging
|
|
390
|
+
// hangs indefinitely on repos with large untracked artifact trees (#1605).
|
|
377
391
|
//
|
|
378
392
|
// Exclude only RUNTIME paths from staging — not the entire .gsd/ directory.
|
|
379
393
|
// When .gsd/milestones/ files are already tracked in the index (projects
|
|
@@ -383,13 +397,9 @@ export class GitServiceImpl {
|
|
|
383
397
|
// the second half of a milestone's artifacts are never committed (#1326).
|
|
384
398
|
//
|
|
385
399
|
// If .gsd/ IS in .gitignore (the default for external state projects),
|
|
386
|
-
// git add -A already skips it and the
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const runtimeExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
390
|
-
for (const exclusion of runtimeExclusions) {
|
|
391
|
-
try { nativeResetPaths(this.basePath, [exclusion]); } catch { /* path not staged — ignore */ }
|
|
392
|
-
}
|
|
400
|
+
// git add -A already skips it and the exclusions are harmless no-ops.
|
|
401
|
+
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
402
|
+
nativeAddAllWithExclusions(this.basePath, allExclusions);
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
@@ -34,6 +34,7 @@ import { showConfirm } from "../shared/mod.js";
|
|
|
34
34
|
import { debugLog } from "./debug-logger.js";
|
|
35
35
|
import { findMilestoneIds, nextMilestoneId } from "./milestone-ids.js";
|
|
36
36
|
import { parkMilestone, discardMilestone } from "./milestone-actions.js";
|
|
37
|
+
import { resolveModelWithFallbacksForUnit } from "./preferences-models.js";
|
|
37
38
|
|
|
38
39
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
39
40
|
export {
|
|
@@ -190,8 +191,40 @@ type UIContext = ExtensionContext;
|
|
|
190
191
|
/**
|
|
191
192
|
* Read GSD-WORKFLOW.md and dispatch it to the LLM with a contextual note.
|
|
192
193
|
* This is the only way the wizard triggers work — everything else is the LLM's job.
|
|
194
|
+
*
|
|
195
|
+
* When a unitType is provided, resolves the user's model preference for that
|
|
196
|
+
* phase (e.g., models.planning → "plan-milestone") and applies it before
|
|
197
|
+
* dispatching. This ensures guided-flow dispatches respect the same
|
|
198
|
+
* per-phase model preferences that auto-mode uses.
|
|
193
199
|
*/
|
|
194
|
-
function dispatchWorkflow(
|
|
200
|
+
async function dispatchWorkflow(
|
|
201
|
+
pi: ExtensionAPI,
|
|
202
|
+
note: string,
|
|
203
|
+
customType = "gsd-run",
|
|
204
|
+
ctx?: ExtensionContext,
|
|
205
|
+
unitType?: string,
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
// Apply model preference for this unit type (if configured)
|
|
208
|
+
if (ctx && unitType) {
|
|
209
|
+
const modelConfig = resolveModelWithFallbacksForUnit(unitType);
|
|
210
|
+
if (modelConfig) {
|
|
211
|
+
const availableModels = ctx.modelRegistry.getAvailable();
|
|
212
|
+
const modelsToTry = [modelConfig.primary, ...modelConfig.fallbacks];
|
|
213
|
+
|
|
214
|
+
for (const modelId of modelsToTry) {
|
|
215
|
+
// Resolve model from available models (same logic as auto-model-selection)
|
|
216
|
+
const model = resolveAvailableModel(modelId, availableModels, ctx.model?.provider);
|
|
217
|
+
if (!model) continue;
|
|
218
|
+
|
|
219
|
+
const ok = await pi.setModel(model, { persist: false });
|
|
220
|
+
if (ok) {
|
|
221
|
+
debugLog("guided-flow-model-applied", { unitType, model: `${model.provider}/${model.id}` });
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
195
228
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|
|
196
229
|
const workflow = readFileSync(workflowPath, "utf-8");
|
|
197
230
|
|
|
@@ -205,6 +238,45 @@ function dispatchWorkflow(pi: ExtensionAPI, note: string, customType = "gsd-run"
|
|
|
205
238
|
);
|
|
206
239
|
}
|
|
207
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Resolve a model ID string to a model object from available models.
|
|
243
|
+
* Handles "provider/model" and bare ID formats.
|
|
244
|
+
*/
|
|
245
|
+
function resolveAvailableModel<T extends { id: string; provider: string }>(
|
|
246
|
+
modelId: string,
|
|
247
|
+
availableModels: T[],
|
|
248
|
+
currentProvider: string | undefined,
|
|
249
|
+
): T | undefined {
|
|
250
|
+
const slashIdx = modelId.indexOf("/");
|
|
251
|
+
|
|
252
|
+
if (slashIdx !== -1) {
|
|
253
|
+
const maybeProvider = modelId.substring(0, slashIdx);
|
|
254
|
+
const id = modelId.substring(slashIdx + 1);
|
|
255
|
+
|
|
256
|
+
const knownProviders = new Set(availableModels.map(m => m.provider.toLowerCase()));
|
|
257
|
+
if (knownProviders.has(maybeProvider.toLowerCase())) {
|
|
258
|
+
const match = availableModels.find(
|
|
259
|
+
m => m.provider.toLowerCase() === maybeProvider.toLowerCase()
|
|
260
|
+
&& m.id.toLowerCase() === id.toLowerCase(),
|
|
261
|
+
);
|
|
262
|
+
if (match) return match;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Try matching the full string as a model ID (OpenRouter-style)
|
|
266
|
+
const lower = modelId.toLowerCase();
|
|
267
|
+
return availableModels.find(
|
|
268
|
+
m => m.id.toLowerCase() === lower
|
|
269
|
+
|| `${m.provider}/${m.id}`.toLowerCase() === lower,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Bare ID — prefer current provider, then first available
|
|
274
|
+
const exactProviderMatch = availableModels.find(
|
|
275
|
+
m => m.id === modelId && m.provider === currentProvider,
|
|
276
|
+
);
|
|
277
|
+
return exactProviderMatch ?? availableModels.find(m => m.id === modelId);
|
|
278
|
+
}
|
|
279
|
+
|
|
208
280
|
/**
|
|
209
281
|
* Build the discuss-and-plan prompt for a new milestone.
|
|
210
282
|
* Used by all three "new milestone" paths (first ever, no active, all complete).
|
|
@@ -301,8 +373,8 @@ export async function showHeadlessMilestoneCreation(
|
|
|
301
373
|
// Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
|
|
302
374
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId };
|
|
303
375
|
|
|
304
|
-
// Dispatch
|
|
305
|
-
dispatchWorkflow(pi, prompt);
|
|
376
|
+
// Dispatch — headless milestone creation is a planning activity
|
|
377
|
+
await dispatchWorkflow(pi, prompt, "gsd-run", ctx, "plan-milestone");
|
|
306
378
|
}
|
|
307
379
|
|
|
308
380
|
|
|
@@ -467,21 +539,21 @@ export async function showDiscuss(
|
|
|
467
539
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
468
540
|
: basePrompt;
|
|
469
541
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: false };
|
|
470
|
-
dispatchWorkflow(pi, seed, "gsd-discuss");
|
|
542
|
+
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "plan-milestone");
|
|
471
543
|
} else if (choice === "discuss_fresh") {
|
|
472
544
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
473
545
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
474
546
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: false };
|
|
475
|
-
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
547
|
+
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
476
548
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
477
549
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
478
|
-
}), "gsd-discuss");
|
|
550
|
+
}), "gsd-discuss", ctx, "plan-milestone");
|
|
479
551
|
} else if (choice === "skip_milestone") {
|
|
480
552
|
const milestoneIds = findMilestoneIds(basePath);
|
|
481
553
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
482
554
|
const nextId = nextMilestoneId(milestoneIds, uniqueMilestoneIds);
|
|
483
555
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: false };
|
|
484
|
-
dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath));
|
|
556
|
+
await dispatchWorkflow(pi, buildDiscussPrompt(nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "plan-milestone");
|
|
485
557
|
}
|
|
486
558
|
return;
|
|
487
559
|
}
|
|
@@ -580,7 +652,7 @@ export async function showDiscuss(
|
|
|
580
652
|
}
|
|
581
653
|
|
|
582
654
|
const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss });
|
|
583
|
-
dispatchWorkflow(pi, prompt, "gsd-discuss");
|
|
655
|
+
await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "plan-slice");
|
|
584
656
|
|
|
585
657
|
// Wait for the discuss session to finish, then loop back to the picker
|
|
586
658
|
await ctx.waitForIdle();
|
|
@@ -722,10 +794,10 @@ async function handleMilestoneActions(
|
|
|
722
794
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
723
795
|
const nextId = nextMilestoneId(milestoneIds, uniqueMilestoneIds);
|
|
724
796
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
725
|
-
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
797
|
+
await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
726
798
|
`New milestone ${nextId}.`,
|
|
727
799
|
basePath
|
|
728
|
-
));
|
|
800
|
+
), "gsd-run", ctx, "plan-milestone");
|
|
729
801
|
return true;
|
|
730
802
|
}
|
|
731
803
|
|
|
@@ -866,10 +938,10 @@ export async function showSmartEntry(
|
|
|
866
938
|
if (isFirst) {
|
|
867
939
|
// First ever — skip wizard, just ask directly
|
|
868
940
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
869
|
-
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
941
|
+
await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
870
942
|
`New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
|
|
871
943
|
basePath
|
|
872
|
-
));
|
|
944
|
+
), "gsd-run", ctx, "plan-milestone");
|
|
873
945
|
} else {
|
|
874
946
|
const choice = await showNextAction(ctx, {
|
|
875
947
|
title: "GSD — Get Shit Done",
|
|
@@ -887,10 +959,10 @@ export async function showSmartEntry(
|
|
|
887
959
|
|
|
888
960
|
if (choice === "new_milestone") {
|
|
889
961
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
890
|
-
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
962
|
+
await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
891
963
|
`New milestone ${nextId}.`,
|
|
892
964
|
basePath
|
|
893
|
-
));
|
|
965
|
+
), "gsd-run", ctx, "plan-milestone");
|
|
894
966
|
}
|
|
895
967
|
}
|
|
896
968
|
return;
|
|
@@ -926,10 +998,10 @@ export async function showSmartEntry(
|
|
|
926
998
|
const nextId = nextMilestoneId(milestoneIds, uniqueMilestoneIds);
|
|
927
999
|
|
|
928
1000
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
929
|
-
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
1001
|
+
await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
930
1002
|
`New milestone ${nextId}.`,
|
|
931
1003
|
basePath
|
|
932
|
-
));
|
|
1004
|
+
), "gsd-run", ctx, "plan-milestone");
|
|
933
1005
|
} else if (choice === "status") {
|
|
934
1006
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
935
1007
|
await fireStatusViaCommand(ctx);
|
|
@@ -977,24 +1049,24 @@ export async function showSmartEntry(
|
|
|
977
1049
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
978
1050
|
: basePrompt;
|
|
979
1051
|
pendingAutoStart = { ctx, pi, basePath, milestoneId, step: stepMode };
|
|
980
|
-
dispatchWorkflow(pi, seed, "gsd-discuss");
|
|
1052
|
+
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "plan-milestone");
|
|
981
1053
|
} else if (choice === "discuss_fresh") {
|
|
982
1054
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
983
1055
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
984
1056
|
pendingAutoStart = { ctx, pi, basePath, milestoneId, step: stepMode };
|
|
985
|
-
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1057
|
+
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
986
1058
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
987
1059
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
988
|
-
}), "gsd-discuss");
|
|
1060
|
+
}), "gsd-discuss", ctx, "plan-milestone");
|
|
989
1061
|
} else if (choice === "skip_milestone") {
|
|
990
1062
|
const milestoneIds = findMilestoneIds(basePath);
|
|
991
1063
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
992
1064
|
const nextId = nextMilestoneId(milestoneIds, uniqueMilestoneIds);
|
|
993
1065
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
994
|
-
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
1066
|
+
await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
995
1067
|
`New milestone ${nextId}.`,
|
|
996
1068
|
basePath
|
|
997
|
-
));
|
|
1069
|
+
), "gsd-run", ctx, "plan-milestone");
|
|
998
1070
|
}
|
|
999
1071
|
return;
|
|
1000
1072
|
}
|
|
@@ -1051,25 +1123,25 @@ export async function showSmartEntry(
|
|
|
1051
1123
|
inlineTemplate("secrets-manifest", "Secrets Manifest"),
|
|
1052
1124
|
].join("\n\n---\n\n");
|
|
1053
1125
|
const secretsOutputPath = relMilestoneFile(basePath, milestoneId, "SECRETS");
|
|
1054
|
-
dispatchWorkflow(pi, loadPrompt("guided-plan-milestone", {
|
|
1126
|
+
await dispatchWorkflow(pi, loadPrompt("guided-plan-milestone", {
|
|
1055
1127
|
milestoneId, milestoneTitle, secretsOutputPath, inlinedTemplates: planMilestoneTemplates,
|
|
1056
|
-
}));
|
|
1128
|
+
}), "gsd-run", ctx, "plan-milestone");
|
|
1057
1129
|
} else if (choice === "discuss") {
|
|
1058
1130
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1059
1131
|
const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
|
|
1060
|
-
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1132
|
+
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1061
1133
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1062
1134
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
1063
|
-
}));
|
|
1135
|
+
}), "gsd-run", ctx, "plan-milestone");
|
|
1064
1136
|
} else if (choice === "skip_milestone") {
|
|
1065
1137
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1066
1138
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1067
1139
|
const nextId = nextMilestoneId(milestoneIds, uniqueMilestoneIds);
|
|
1068
1140
|
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
1069
|
-
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
1141
|
+
await dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
1070
1142
|
`New milestone ${nextId}.`,
|
|
1071
1143
|
basePath
|
|
1072
|
-
));
|
|
1144
|
+
), "gsd-run", ctx, "plan-milestone");
|
|
1073
1145
|
} else if (choice === "discard_milestone") {
|
|
1074
1146
|
const confirmed = await showConfirm(ctx, {
|
|
1075
1147
|
title: "Discard milestone?",
|
|
@@ -1181,16 +1253,16 @@ export async function showSmartEntry(
|
|
|
1181
1253
|
inlineTemplate("plan", "Slice Plan"),
|
|
1182
1254
|
inlineTemplate("task-plan", "Task Plan"),
|
|
1183
1255
|
].join("\n\n---\n\n");
|
|
1184
|
-
dispatchWorkflow(pi, loadPrompt("guided-plan-slice", {
|
|
1256
|
+
await dispatchWorkflow(pi, loadPrompt("guided-plan-slice", {
|
|
1185
1257
|
milestoneId, sliceId, sliceTitle, inlinedTemplates: planSliceTemplates,
|
|
1186
|
-
}));
|
|
1258
|
+
}), "gsd-run", ctx, "plan-slice");
|
|
1187
1259
|
} else if (choice === "discuss") {
|
|
1188
|
-
dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext }));
|
|
1260
|
+
await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext }), "gsd-run", ctx, "plan-slice");
|
|
1189
1261
|
} else if (choice === "research") {
|
|
1190
1262
|
const researchTemplates = inlineTemplate("research", "Research");
|
|
1191
|
-
dispatchWorkflow(pi, loadPrompt("guided-research-slice", {
|
|
1263
|
+
await dispatchWorkflow(pi, loadPrompt("guided-research-slice", {
|
|
1192
1264
|
milestoneId, sliceId, sliceTitle, inlinedTemplates: researchTemplates,
|
|
1193
|
-
}));
|
|
1265
|
+
}), "gsd-run", ctx, "research-slice");
|
|
1194
1266
|
} else if (choice === "status") {
|
|
1195
1267
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
1196
1268
|
await fireStatusViaCommand(ctx);
|
|
@@ -1232,9 +1304,9 @@ export async function showSmartEntry(
|
|
|
1232
1304
|
inlineTemplate("slice-summary", "Slice Summary"),
|
|
1233
1305
|
inlineTemplate("uat", "UAT"),
|
|
1234
1306
|
].join("\n\n---\n\n");
|
|
1235
|
-
dispatchWorkflow(pi, loadPrompt("guided-complete-slice", {
|
|
1307
|
+
await dispatchWorkflow(pi, loadPrompt("guided-complete-slice", {
|
|
1236
1308
|
workingDirectory: basePath, milestoneId, sliceId, sliceTitle, inlinedTemplates: completeSliceTemplates,
|
|
1237
|
-
}));
|
|
1309
|
+
}), "gsd-run", ctx, "complete-slice");
|
|
1238
1310
|
} else if (choice === "status") {
|
|
1239
1311
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
1240
1312
|
await fireStatusViaCommand(ctx);
|
|
@@ -1297,14 +1369,14 @@ export async function showSmartEntry(
|
|
|
1297
1369
|
|
|
1298
1370
|
if (choice === "execute") {
|
|
1299
1371
|
if (hasInterrupted) {
|
|
1300
|
-
dispatchWorkflow(pi, loadPrompt("guided-resume-task", {
|
|
1372
|
+
await dispatchWorkflow(pi, loadPrompt("guided-resume-task", {
|
|
1301
1373
|
milestoneId, sliceId,
|
|
1302
|
-
}));
|
|
1374
|
+
}), "gsd-run", ctx, "execute-task");
|
|
1303
1375
|
} else {
|
|
1304
1376
|
const executeTaskTemplates = inlineTemplate("task-summary", "Task Summary");
|
|
1305
|
-
dispatchWorkflow(pi, loadPrompt("guided-execute-task", {
|
|
1377
|
+
await dispatchWorkflow(pi, loadPrompt("guided-execute-task", {
|
|
1306
1378
|
milestoneId, sliceId, taskId, taskTitle, inlinedTemplates: executeTaskTemplates,
|
|
1307
|
-
}));
|
|
1379
|
+
}), "gsd-run", ctx, "execute-task");
|
|
1308
1380
|
}
|
|
1309
1381
|
} else if (choice === "status") {
|
|
1310
1382
|
const { fireStatusViaCommand } = await import("./commands.js");
|
|
@@ -92,6 +92,23 @@ function warnDeprecatedAgentInstructions(): void {
|
|
|
92
92
|
// ── Depth verification state ──────────────────────────────────────────────
|
|
93
93
|
let depthVerificationDone = false;
|
|
94
94
|
|
|
95
|
+
// ── DB lazy-open helper ───────────────────────────────────────────────────
|
|
96
|
+
// In manual sessions (no auto-mode), the DB is never opened by bootstrapAutoSession.
|
|
97
|
+
// This helper ensures the DB is lazily opened on first tool call that needs it.
|
|
98
|
+
async function ensureDbOpen(): Promise<boolean> {
|
|
99
|
+
try {
|
|
100
|
+
const db = await import("./gsd-db.js");
|
|
101
|
+
if (db.isDbAvailable()) return true;
|
|
102
|
+
const dbPath = join(process.cwd(), ".gsd", "gsd.db");
|
|
103
|
+
if (existsSync(dbPath)) {
|
|
104
|
+
return db.openDatabase(dbPath);
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
95
112
|
// ── Queue phase tracking ──────────────────────────────────────────────────
|
|
96
113
|
// When true, the LLM is in a queue flow writing CONTEXT.md files.
|
|
97
114
|
// The write-gate applies during queue flows just like discussion flows.
|
|
@@ -300,12 +317,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
300
317
|
when_context: Type.Optional(Type.String({ description: "When/context for the decision (e.g. milestone ID)" })),
|
|
301
318
|
}),
|
|
302
319
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
const db = await import("./gsd-db.js");
|
|
307
|
-
dbAvailable = db.isDbAvailable();
|
|
308
|
-
} catch { /* dynamic import failed */ }
|
|
320
|
+
// Ensure DB is open (lazy-open on first tool call in manual sessions)
|
|
321
|
+
const dbAvailable = await ensureDbOpen();
|
|
309
322
|
|
|
310
323
|
if (!dbAvailable) {
|
|
311
324
|
return {
|
|
@@ -367,11 +380,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
367
380
|
supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
|
|
368
381
|
}),
|
|
369
382
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
const db = await import("./gsd-db.js");
|
|
373
|
-
dbAvailable = db.isDbAvailable();
|
|
374
|
-
} catch { /* dynamic import failed */ }
|
|
383
|
+
const dbAvailable = await ensureDbOpen();
|
|
375
384
|
|
|
376
385
|
if (!dbAvailable) {
|
|
377
386
|
return {
|
|
@@ -441,11 +450,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
441
450
|
content: Type.String({ description: "The full markdown content of the artifact" }),
|
|
442
451
|
}),
|
|
443
452
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
const db = await import("./gsd-db.js");
|
|
447
|
-
dbAvailable = db.isDbAvailable();
|
|
448
|
-
} catch { /* dynamic import failed */ }
|
|
453
|
+
const dbAvailable = await ensureDbOpen();
|
|
449
454
|
|
|
450
455
|
if (!dbAvailable) {
|
|
451
456
|
return {
|
|
@@ -671,6 +671,43 @@ export function nativeAddAll(basePath: string): void {
|
|
|
671
671
|
gitFileExec(basePath, ["add", "-A"]);
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
+
/**
|
|
675
|
+
* Stage all files with pathspec exclusions (git add -A -- ':!pattern' ...).
|
|
676
|
+
* Excluded paths are never hashed by git, preventing hangs on large
|
|
677
|
+
* untracked artifact trees (57GB+, 11K+ files). See #1605.
|
|
678
|
+
*
|
|
679
|
+
* Falls back to plain `git add -A` when no exclusions are provided.
|
|
680
|
+
* Always uses the CLI path (not libgit2) because libgit2's add_all
|
|
681
|
+
* does not support pathspec exclusion syntax.
|
|
682
|
+
*
|
|
683
|
+
* When excluded paths are already covered by .gitignore, git may exit
|
|
684
|
+
* with code 1 and an "ignored by .gitignore" warning. This is harmless
|
|
685
|
+
* (the staging succeeds for all non-ignored files) and is suppressed.
|
|
686
|
+
*/
|
|
687
|
+
export function nativeAddAllWithExclusions(basePath: string, exclusions: readonly string[]): void {
|
|
688
|
+
if (exclusions.length === 0) {
|
|
689
|
+
nativeAddAll(basePath);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const pathspecs = exclusions.map(e => `:!${e}`);
|
|
693
|
+
try {
|
|
694
|
+
execFileSync("git", ["add", "-A", "--", ...pathspecs], {
|
|
695
|
+
cwd: basePath,
|
|
696
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
697
|
+
encoding: "utf-8",
|
|
698
|
+
env: GIT_NO_PROMPT_ENV,
|
|
699
|
+
});
|
|
700
|
+
} catch (err: unknown) {
|
|
701
|
+
// git exits 1 when pathspec exclusions reference paths already covered
|
|
702
|
+
// by .gitignore. The staging itself succeeds — only suppress that case.
|
|
703
|
+
const stderr = (err as { stderr?: string })?.stderr ?? "";
|
|
704
|
+
if (stderr.includes("ignored by one of your .gitignore files")) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
674
711
|
/**
|
|
675
712
|
* Stage specific files.
|
|
676
713
|
* Native: libgit2 index add.
|
|
@@ -295,18 +295,6 @@ export function resolveInlineLevel(): InlineLevel {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
/**
|
|
299
|
-
* Resolve the compression strategy from the active token profile.
|
|
300
|
-
* budget/balanced -> "compress", quality -> "truncate".
|
|
301
|
-
* Explicit preference always wins.
|
|
302
|
-
*/
|
|
303
|
-
export function resolveCompressionStrategy(): import("./types.js").CompressionStrategy {
|
|
304
|
-
const prefs = loadEffectiveGSDPreferences();
|
|
305
|
-
if (prefs?.preferences.compression_strategy) return prefs.preferences.compression_strategy;
|
|
306
|
-
const profile = resolveEffectiveProfile();
|
|
307
|
-
return profile === "quality" ? "truncate" : "compress";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
298
|
/**
|
|
311
299
|
* Resolve the context selection mode from the active token profile.
|
|
312
300
|
* budget -> "smart", balanced/quality -> "full".
|