gsd-pi 2.76.0-dev.4100bd590 → 2.76.0-dev.4c866b677
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.d.ts +1 -1
- package/dist/resource-loader.js +2 -8
- package/dist/resources/extensions/gsd/auto/phases.js +4 -1
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto-model-selection.js +13 -2
- package/dist/resources/extensions/gsd/auto-start.js +28 -10
- package/dist/resources/extensions/gsd/auto.js +4 -1
- package/dist/resources/extensions/gsd/complexity-classifier.js +5 -3
- package/dist/resources/extensions/gsd/gsd-db.js +59 -3
- package/dist/resources/extensions/gsd/init-wizard.js +15 -1
- package/dist/resources/extensions/gsd/prompt-loader.js +22 -7
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +1 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +60 -15
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts +17 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js +75 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.js.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js +41 -0
- package/packages/pi-ai/dist/providers/think-tag-parser.test.js.map +1 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +57 -16
- package/packages/pi-ai/src/providers/think-tag-parser.test.ts +44 -0
- package/packages/pi-ai/src/providers/think-tag-parser.ts +94 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +3 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.js +92 -12
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +16 -1
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +61 -1
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +76 -10
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +5 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +13 -7
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts +7 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +29 -21
- package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +19 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +99 -12
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +75 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +86 -10
- package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +6 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -7
- package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +36 -22
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/scripts/link-workspace-packages.cjs +1 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +16 -1
- package/src/resources/extensions/gsd/auto-start.ts +28 -10
- package/src/resources/extensions/gsd/auto.ts +4 -1
- package/src/resources/extensions/gsd/complexity-classifier.ts +5 -3
- package/src/resources/extensions/gsd/gsd-db.ts +65 -3
- package/src/resources/extensions/gsd/init-wizard.ts +15 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +30 -7
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +33 -3
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +38 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/init-wizard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +49 -0
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → jDqWYbuP_CG6Kjc-uKwkN}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{YnUwu2WWaT0_hyTLUF4nq → jDqWYbuP_CG6Kjc-uKwkN}/_ssgManifest.js +0 -0
|
@@ -34,6 +34,7 @@ const packageMap = {
|
|
|
34
34
|
'pi-coding-agent': { scope: '@gsd', name: 'pi-coding-agent' },
|
|
35
35
|
'pi-tui': { scope: '@gsd', name: 'pi-tui' },
|
|
36
36
|
'rpc-client': { scope: '@gsd-build', name: 'rpc-client' },
|
|
37
|
+
'mcp-server': { scope: '@gsd-build', name: 'mcp-server' },
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
for (const scopeDir of Object.values(scopeDirs)) {
|
|
@@ -213,6 +213,7 @@ export interface LoopDeps {
|
|
|
213
213
|
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
214
214
|
isAutoMode?: boolean,
|
|
215
215
|
sessionModelOverride?: { provider: string; id: string } | null,
|
|
216
|
+
autoModeStartThinkingLevel?: ReturnType<ExtensionAPI["getThinkingLevel"]> | null,
|
|
216
217
|
) => Promise<{
|
|
217
218
|
routing: { tier: string; modelDowngraded: boolean } | null;
|
|
218
219
|
appliedModel: { provider: string; id: string } | null;
|
|
@@ -1471,6 +1471,7 @@ export async function runUnitPhase(
|
|
|
1471
1471
|
sidecarItem ? undefined : { isRetry, previousTier },
|
|
1472
1472
|
undefined,
|
|
1473
1473
|
s.manualSessionModelOverride,
|
|
1474
|
+
s.autoModeStartThinkingLevel,
|
|
1474
1475
|
);
|
|
1475
1476
|
s.currentUnitRouting =
|
|
1476
1477
|
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
@@ -1485,6 +1486,9 @@ export async function runUnitPhase(
|
|
|
1485
1486
|
if (match) {
|
|
1486
1487
|
const ok = await pi.setModel(match, { persist: false });
|
|
1487
1488
|
if (ok) {
|
|
1489
|
+
if (s.autoModeStartThinkingLevel) {
|
|
1490
|
+
pi.setThinkingLevel(s.autoModeStartThinkingLevel);
|
|
1491
|
+
}
|
|
1488
1492
|
s.currentUnitModel = match as AutoSession["currentUnitModel"];
|
|
1489
1493
|
ctx.ui.notify(`Hook model override: ${match.provider}/${match.id}`, "info");
|
|
1490
1494
|
} else {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { Api, Model } from "@gsd/pi-ai";
|
|
20
|
-
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
20
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
21
21
|
import type { GitServiceImpl } from "../git-service.js";
|
|
22
22
|
import type { CaptureEntry } from "../captures.js";
|
|
23
23
|
import type { BudgetAlertLevel } from "../auto-budget.js";
|
|
@@ -40,6 +40,8 @@ export interface StartModel {
|
|
|
40
40
|
id: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export type ThinkingLevelSnapshot = ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
44
|
+
|
|
43
45
|
export interface PendingVerificationRetry {
|
|
44
46
|
unitId: string;
|
|
45
47
|
failureContext: string;
|
|
@@ -120,6 +122,8 @@ export class AutoSession {
|
|
|
120
122
|
currentDispatchedModelId: string | null = null;
|
|
121
123
|
originalModelId: string | null = null;
|
|
122
124
|
originalModelProvider: string | null = null;
|
|
125
|
+
autoModeStartThinkingLevel: ThinkingLevelSnapshot | null = null;
|
|
126
|
+
originalThinkingLevel: ThinkingLevelSnapshot | null = null;
|
|
123
127
|
lastBudgetAlertLevel: BudgetAlertLevel = 0;
|
|
124
128
|
|
|
125
129
|
// ── Recovery ─────────────────────────────────────────────────────────────
|
|
@@ -241,6 +245,8 @@ export class AutoSession {
|
|
|
241
245
|
this.currentDispatchedModelId = null;
|
|
242
246
|
this.originalModelId = null;
|
|
243
247
|
this.originalModelProvider = null;
|
|
248
|
+
this.autoModeStartThinkingLevel = null;
|
|
249
|
+
this.originalThinkingLevel = null;
|
|
244
250
|
this.lastBudgetAlertLevel = 0;
|
|
245
251
|
|
|
246
252
|
// Recovery
|
|
@@ -33,6 +33,14 @@ export interface PreferredModelConfig {
|
|
|
33
33
|
source: "explicit" | "synthesized";
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function reapplyThinkingLevel(
|
|
37
|
+
pi: ExtensionAPI,
|
|
38
|
+
level: ReturnType<ExtensionAPI["getThinkingLevel"]> | null | undefined,
|
|
39
|
+
): void {
|
|
40
|
+
if (!level) return;
|
|
41
|
+
pi.setThinkingLevel(level);
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
export function resolvePreferredModelConfig(
|
|
37
45
|
unitType: string,
|
|
38
46
|
autoModeStartModel: { provider: string; id: string; flatRateCtx?: FlatRateContext } | null,
|
|
@@ -97,6 +105,8 @@ export async function selectAndApplyModel(
|
|
|
97
105
|
isAutoMode = true,
|
|
98
106
|
/** Explicit /gsd model pin captured at bootstrap for long-running auto loops. */
|
|
99
107
|
sessionModelOverride?: { provider: string; id: string } | null,
|
|
108
|
+
/** Thinking level captured at auto-mode start and re-applied after model swaps. */
|
|
109
|
+
autoModeStartThinkingLevel?: ReturnType<ExtensionAPI["getThinkingLevel"]> | null,
|
|
100
110
|
): Promise<ModelSelectionResult> {
|
|
101
111
|
const uokFlags = resolveUokFlags(prefs);
|
|
102
112
|
const effectiveSessionModelOverride = sessionModelOverride === undefined
|
|
@@ -380,6 +390,7 @@ export async function selectAndApplyModel(
|
|
|
380
390
|
const ok = await pi.setModel(model, { persist: false });
|
|
381
391
|
if (ok) {
|
|
382
392
|
appliedModel = model;
|
|
393
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
383
394
|
|
|
384
395
|
// ADR-005: Adjust active tool set for the selected model's provider capabilities.
|
|
385
396
|
// Hard-filter incompatible tools, then let extensions override via adjust_tool_set hook.
|
|
@@ -456,10 +467,14 @@ export async function selectAndApplyModel(
|
|
|
456
467
|
);
|
|
457
468
|
if (byId) {
|
|
458
469
|
const fallbackOk = await pi.setModel(byId, { persist: false });
|
|
459
|
-
if (fallbackOk)
|
|
470
|
+
if (fallbackOk) {
|
|
471
|
+
appliedModel = byId;
|
|
472
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
473
|
+
}
|
|
460
474
|
}
|
|
461
475
|
} else {
|
|
462
476
|
appliedModel = startModel;
|
|
477
|
+
reapplyThinkingLevel(pi, autoModeStartThinkingLevel);
|
|
463
478
|
}
|
|
464
479
|
}
|
|
465
480
|
}
|
|
@@ -60,7 +60,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|
|
60
60
|
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
|
61
61
|
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
|
62
62
|
import { snapshotSkills } from "./skill-discovery.js";
|
|
63
|
-
import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
|
|
63
|
+
import { isDbAvailable, getMilestone, openDatabase, getDbStatus } from "./gsd-db.js";
|
|
64
64
|
import { hideFooter } from "./auto-dashboard.js";
|
|
65
65
|
import {
|
|
66
66
|
debugLog,
|
|
@@ -273,8 +273,8 @@ export async function bootstrapAutoSession(
|
|
|
273
273
|
//
|
|
274
274
|
// Precedence:
|
|
275
275
|
// 1) Explicit session override via /gsd model (this session)
|
|
276
|
-
// 2)
|
|
277
|
-
// 3)
|
|
276
|
+
// 2) Current session model from settings/session restore (if provider ready)
|
|
277
|
+
// 3) GSD model preferences from PREFERENCES.md (validated against live auth)
|
|
278
278
|
//
|
|
279
279
|
// This preserves #3517 defaults while honoring explicit runtime model
|
|
280
280
|
// selection for subsequent /gsd runs in the same session.
|
|
@@ -314,11 +314,14 @@ export async function bootstrapAutoSession(
|
|
|
314
314
|
}
|
|
315
315
|
const sessionModelReady =
|
|
316
316
|
ctx.model && ctx.modelRegistry.isProviderRequestReady(ctx.model.provider);
|
|
317
|
+
const currentSessionModel = (sessionModelReady && ctx.model)
|
|
318
|
+
? { provider: ctx.model.provider, id: ctx.model.id }
|
|
319
|
+
: null;
|
|
320
|
+
const startThinkingSnapshot = pi.getThinkingLevel();
|
|
317
321
|
const startModelSnapshot = manualSessionOverride
|
|
322
|
+
?? currentSessionModel
|
|
318
323
|
?? validatedPreferredModel
|
|
319
|
-
??
|
|
320
|
-
? { provider: ctx.model.provider, id: ctx.model.id }
|
|
321
|
-
: null);
|
|
324
|
+
?? null;
|
|
322
325
|
|
|
323
326
|
try {
|
|
324
327
|
// Validate GSD_PROJECT_ID early so the user gets immediate feedback
|
|
@@ -664,8 +667,9 @@ export async function bootstrapAutoSession(
|
|
|
664
667
|
s.pendingQuickTasks = [];
|
|
665
668
|
s.currentUnit = null;
|
|
666
669
|
s.currentMilestoneId = state.activeMilestone?.id ?? null;
|
|
667
|
-
s.originalModelId = ctx.model?.id ?? null;
|
|
668
|
-
s.originalModelProvider = ctx.model?.provider ?? null;
|
|
670
|
+
s.originalModelId = startModelSnapshot?.id ?? ctx.model?.id ?? null;
|
|
671
|
+
s.originalModelProvider = startModelSnapshot?.provider ?? ctx.model?.provider ?? null;
|
|
672
|
+
s.originalThinkingLevel = startThinkingSnapshot ?? null;
|
|
669
673
|
|
|
670
674
|
// Register SIGTERM handler
|
|
671
675
|
registerSigtermHandler(base);
|
|
@@ -758,9 +762,22 @@ export async function bootstrapAutoSession(
|
|
|
758
762
|
// call returns "db_unavailable", triggering artifact-retry which
|
|
759
763
|
// re-dispatches the same task — producing an infinite loop (#2419).
|
|
760
764
|
if (existsSync(gsdDbPath) && !isDbAvailable()) {
|
|
765
|
+
const dbStatus = getDbStatus();
|
|
766
|
+
const phaseHint = dbStatus.lastPhase === "open"
|
|
767
|
+
? "The database file could not be opened"
|
|
768
|
+
: dbStatus.lastPhase === "initSchema"
|
|
769
|
+
? "The database schema could not be initialized"
|
|
770
|
+
: dbStatus.lastPhase === "vacuum-recovery"
|
|
771
|
+
? "Corruption recovery (VACUUM) failed"
|
|
772
|
+
: dbStatus.attempted
|
|
773
|
+
? "The database could not be opened (phase unknown)"
|
|
774
|
+
: "The database provider could not be loaded";
|
|
775
|
+
const errorDetail = dbStatus.lastError ? ` (${dbStatus.lastError.message})` : "";
|
|
776
|
+
const providerHint = dbStatus.provider
|
|
777
|
+
? ` Provider: ${dbStatus.provider}.`
|
|
778
|
+
: " No SQLite provider available — check Node >= 22 or install better-sqlite3.";
|
|
761
779
|
ctx.ui.notify(
|
|
762
|
-
|
|
763
|
-
"Check for corrupt gsd.db or missing native SQLite bindings.",
|
|
780
|
+
`SQLite database exists but failed to open: ${gsdDbPath}. ${phaseHint}${errorDetail}.${providerHint}`,
|
|
764
781
|
"error",
|
|
765
782
|
);
|
|
766
783
|
return releaseLockAndReturn();
|
|
@@ -779,6 +796,7 @@ export async function bootstrapAutoSession(
|
|
|
779
796
|
id: startModelSnapshot.id,
|
|
780
797
|
};
|
|
781
798
|
}
|
|
799
|
+
s.autoModeStartThinkingLevel = startThinkingSnapshot ?? null;
|
|
782
800
|
s.manualSessionModelOverride = manualSessionOverride ?? null;
|
|
783
801
|
|
|
784
802
|
// Apply worker model override from parallel orchestrator (#worker-model).
|
|
@@ -969,7 +969,7 @@ export async function stopAuto(
|
|
|
969
969
|
logWarning("engine", `file unlink failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
970
970
|
}
|
|
971
971
|
|
|
972
|
-
// ── Step 13: Restore original model (before reset clears IDs) ──
|
|
972
|
+
// ── Step 13: Restore original model + thinking (before reset clears IDs) ──
|
|
973
973
|
try {
|
|
974
974
|
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
975
975
|
const original = ctx.modelRegistry.find(
|
|
@@ -978,6 +978,9 @@ export async function stopAuto(
|
|
|
978
978
|
);
|
|
979
979
|
if (original) await pi.setModel(original);
|
|
980
980
|
}
|
|
981
|
+
if (pi && s.originalThinkingLevel) {
|
|
982
|
+
pi.setThinkingLevel(s.originalThinkingLevel);
|
|
983
|
+
}
|
|
981
984
|
} catch (e) {
|
|
982
985
|
debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
|
|
983
986
|
}
|
|
@@ -32,11 +32,13 @@ export interface TaskMetadata {
|
|
|
32
32
|
// ─── Unit Type → Default Tier Mapping ────────────────────────────────────────
|
|
33
33
|
|
|
34
34
|
const UNIT_TYPE_TIERS: Record<string, ComplexityTier> = {
|
|
35
|
-
// Tier 1 — Light:
|
|
36
|
-
"complete-slice": "light",
|
|
35
|
+
// Tier 1 — Light: compact verification turns
|
|
37
36
|
"run-uat": "light",
|
|
38
37
|
|
|
39
|
-
// Tier 2 — Standard: research, routine discussion
|
|
38
|
+
// Tier 2 — Standard: research, routine discussion, slice completion
|
|
39
|
+
// complete-slice can carry large inlined context; avoid routing it to the
|
|
40
|
+
// cheapest "light" model by default (#4520).
|
|
41
|
+
"complete-slice": "standard",
|
|
40
42
|
"discuss-milestone": "standard",
|
|
41
43
|
"discuss-slice": "standard",
|
|
42
44
|
"research-milestone": "standard",
|
|
@@ -1199,6 +1199,8 @@ let currentPath: string | null = null;
|
|
|
1199
1199
|
let currentPid: number = 0;
|
|
1200
1200
|
let _exitHandlerRegistered = false;
|
|
1201
1201
|
let _dbOpenAttempted = false;
|
|
1202
|
+
let _lastDbError: Error | null = null;
|
|
1203
|
+
let _lastDbPhase: "open" | "initSchema" | "vacuum-recovery" | null = null;
|
|
1202
1204
|
|
|
1203
1205
|
export function getDbProvider(): ProviderName | null {
|
|
1204
1206
|
loadProvider();
|
|
@@ -1219,12 +1221,58 @@ export function wasDbOpenAttempted(): boolean {
|
|
|
1219
1221
|
return _dbOpenAttempted;
|
|
1220
1222
|
}
|
|
1221
1223
|
|
|
1224
|
+
export function getDbStatus(): {
|
|
1225
|
+
available: boolean;
|
|
1226
|
+
provider: ProviderName | null;
|
|
1227
|
+
attempted: boolean;
|
|
1228
|
+
lastError: Error | null;
|
|
1229
|
+
lastPhase: "open" | "initSchema" | "vacuum-recovery" | null;
|
|
1230
|
+
} {
|
|
1231
|
+
loadProvider();
|
|
1232
|
+
return {
|
|
1233
|
+
available: currentDb !== null,
|
|
1234
|
+
provider: providerName,
|
|
1235
|
+
attempted: _dbOpenAttempted,
|
|
1236
|
+
lastError: _lastDbError,
|
|
1237
|
+
lastPhase: _lastDbPhase,
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1222
1241
|
export function openDatabase(path: string): boolean {
|
|
1223
1242
|
_dbOpenAttempted = true;
|
|
1224
1243
|
if (currentDb && currentPath !== path) closeDatabase();
|
|
1225
1244
|
if (currentDb && currentPath === path) return true;
|
|
1226
1245
|
|
|
1227
|
-
|
|
1246
|
+
// Reset error state only when a new open attempt is actually going to run.
|
|
1247
|
+
_lastDbError = null;
|
|
1248
|
+
_lastDbPhase = null;
|
|
1249
|
+
|
|
1250
|
+
let rawDb: unknown;
|
|
1251
|
+
let fallbackProvider: ProviderName | null = null;
|
|
1252
|
+
let fallbackModule: unknown = null;
|
|
1253
|
+
try {
|
|
1254
|
+
rawDb = openRawDb(path);
|
|
1255
|
+
} catch (primaryErr) {
|
|
1256
|
+
_lastDbPhase = "open";
|
|
1257
|
+
_lastDbError = primaryErr instanceof Error ? primaryErr : new Error(String(primaryErr));
|
|
1258
|
+
// node:sqlite loaded but failed to open this file — try better-sqlite3 as fallback.
|
|
1259
|
+
if (providerName === "node:sqlite") {
|
|
1260
|
+
try {
|
|
1261
|
+
const mod = _require("better-sqlite3");
|
|
1262
|
+
const Db = (mod && mod.default) ? mod.default : mod;
|
|
1263
|
+
if (typeof Db === "function") {
|
|
1264
|
+
rawDb = new Db(path);
|
|
1265
|
+
fallbackProvider = "better-sqlite3";
|
|
1266
|
+
fallbackModule = Db;
|
|
1267
|
+
_lastDbError = null;
|
|
1268
|
+
_lastDbPhase = null;
|
|
1269
|
+
}
|
|
1270
|
+
} catch {
|
|
1271
|
+
// fallback unavailable; surface original error
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (!rawDb) throw primaryErr;
|
|
1275
|
+
}
|
|
1228
1276
|
if (!rawDb) return false;
|
|
1229
1277
|
|
|
1230
1278
|
const adapter = createAdapter(rawDb);
|
|
@@ -1240,15 +1288,25 @@ export function openDatabase(path: string): boolean {
|
|
|
1240
1288
|
initSchema(adapter, fileBacked);
|
|
1241
1289
|
process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
|
|
1242
1290
|
} catch (retryErr) {
|
|
1291
|
+
_lastDbPhase = "vacuum-recovery";
|
|
1292
|
+
_lastDbError = retryErr instanceof Error ? retryErr : new Error(String(retryErr));
|
|
1243
1293
|
try { adapter.close(); } catch (e) { logWarning("db", `close after VACUUM failed: ${(e as Error).message}`); }
|
|
1244
1294
|
throw retryErr;
|
|
1245
1295
|
}
|
|
1246
1296
|
} else {
|
|
1247
|
-
|
|
1297
|
+
_lastDbPhase = "initSchema";
|
|
1298
|
+
_lastDbError = err instanceof Error ? err : new Error(String(err));
|
|
1299
|
+
try { adapter.close(); } catch (e) { logWarning("db", `close after initSchema failed: ${(e as Error).message}`); }
|
|
1248
1300
|
throw err;
|
|
1249
1301
|
}
|
|
1250
1302
|
}
|
|
1251
1303
|
|
|
1304
|
+
// Commit fallback provider switch only after open + schema both succeeded.
|
|
1305
|
+
if (fallbackProvider) {
|
|
1306
|
+
providerName = fallbackProvider;
|
|
1307
|
+
providerModule = fallbackModule;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1252
1310
|
currentDb = adapter;
|
|
1253
1311
|
currentPath = path;
|
|
1254
1312
|
currentPid = process.pid;
|
|
@@ -1276,8 +1334,12 @@ export function closeDatabase(): void {
|
|
|
1276
1334
|
currentDb = null;
|
|
1277
1335
|
currentPath = null;
|
|
1278
1336
|
currentPid = 0;
|
|
1279
|
-
_dbOpenAttempted = false;
|
|
1280
1337
|
}
|
|
1338
|
+
// Reset session-scoped state unconditionally so stale error info from a
|
|
1339
|
+
// failed open doesn't persist into the next open attempt or status check.
|
|
1340
|
+
_dbOpenAttempted = false;
|
|
1341
|
+
_lastDbError = null;
|
|
1342
|
+
_lastDbPhase = null;
|
|
1281
1343
|
}
|
|
1282
1344
|
|
|
1283
1345
|
/** Run a full VACUUM — call sparingly (e.g. after milestone completion). */
|
|
@@ -10,7 +10,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
|
|
|
10
10
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { showNextAction } from "../shared/tui.js";
|
|
13
|
-
import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
|
|
13
|
+
import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
|
|
14
14
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
16
16
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
@@ -74,6 +74,7 @@ export async function showProjectInit(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// ── Step 2: Git setup ──────────────────────────────────────────────────────
|
|
77
|
+
let didInitGit = false;
|
|
77
78
|
if (!signals.isGitRepo) {
|
|
78
79
|
const gitChoice = await showNextAction(ctx, {
|
|
79
80
|
title: "GSD — Project Setup",
|
|
@@ -89,6 +90,7 @@ export async function showProjectInit(
|
|
|
89
90
|
|
|
90
91
|
if (gitChoice === "init_git") {
|
|
91
92
|
nativeInit(basePath, prefs.mainBranch);
|
|
93
|
+
didInitGit = true;
|
|
92
94
|
}
|
|
93
95
|
} else {
|
|
94
96
|
// Auto-detect main branch from existing repo
|
|
@@ -295,6 +297,18 @@ export async function showProjectInit(
|
|
|
295
297
|
ensureGitignore(basePath);
|
|
296
298
|
untrackRuntimeFiles(basePath);
|
|
297
299
|
|
|
300
|
+
// Create initial commit so git log and git worktree work immediately (#4530).
|
|
301
|
+
// Without this, the branch is "unborn" (zero commits) and downstream operations
|
|
302
|
+
// like `git log` and `git worktree add` fail.
|
|
303
|
+
if (didInitGit) {
|
|
304
|
+
try {
|
|
305
|
+
nativeAddAll(basePath);
|
|
306
|
+
nativeCommit(basePath, "chore: init project");
|
|
307
|
+
} catch {
|
|
308
|
+
// Non-fatal — user can commit manually; don't block project init
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
298
312
|
// Auto-generate codebase map for instant agent orientation
|
|
299
313
|
try {
|
|
300
314
|
const result = generateCodebaseMap(basePath);
|
|
@@ -24,6 +24,35 @@ import { fileURLToPath } from "node:url";
|
|
|
24
24
|
import { homedir } from "node:os";
|
|
25
25
|
import { logWarning } from "./workflow-logger.js";
|
|
26
26
|
|
|
27
|
+
type ExistsFn = (path: string) => boolean;
|
|
28
|
+
|
|
29
|
+
function hasRequiredExtensionAssets(rootDir: string, exists: ExistsFn = existsSync): boolean {
|
|
30
|
+
return (
|
|
31
|
+
exists(join(rootDir, "prompts")) &&
|
|
32
|
+
exists(join(rootDir, "templates", "task-summary.md"))
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveExtensionDirFromCandidates(
|
|
37
|
+
moduleDir: string,
|
|
38
|
+
agentGsdDir: string,
|
|
39
|
+
exists: ExistsFn = existsSync,
|
|
40
|
+
): string {
|
|
41
|
+
const moduleUsable = hasRequiredExtensionAssets(moduleDir, exists);
|
|
42
|
+
const agentUsable = hasRequiredExtensionAssets(agentGsdDir, exists);
|
|
43
|
+
|
|
44
|
+
// Prefer the user-local extension tree when both are valid. This avoids
|
|
45
|
+
// leaking npm/global-install paths into prompts on Windows.
|
|
46
|
+
if (agentUsable) return agentGsdDir;
|
|
47
|
+
if (moduleUsable) return moduleDir;
|
|
48
|
+
|
|
49
|
+
// Degraded fallback: if required template is missing in both locations,
|
|
50
|
+
// keep previous behavior and prefer whichever still has prompts/.
|
|
51
|
+
if (exists(join(moduleDir, "prompts"))) return moduleDir;
|
|
52
|
+
if (exists(join(agentGsdDir, "prompts"))) return agentGsdDir;
|
|
53
|
+
return moduleDir;
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
/**
|
|
28
57
|
* Resolve the GSD extension directory.
|
|
29
58
|
*
|
|
@@ -36,15 +65,9 @@ import { logWarning } from "./workflow-logger.js";
|
|
|
36
65
|
*/
|
|
37
66
|
function resolveExtensionDir(): string {
|
|
38
67
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
39
|
-
if (existsSync(join(moduleDir, "prompts"))) return moduleDir;
|
|
40
|
-
|
|
41
|
-
// Fallback: user-local agent directory
|
|
42
68
|
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
43
69
|
const agentGsdDir = join(gsdHome, "agent", "extensions", "gsd");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Last resort: return the module dir (warmCache will silently handle the miss)
|
|
47
|
-
return moduleDir;
|
|
70
|
+
return resolveExtensionDirFromCandidates(moduleDir, agentGsdDir);
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
const __extensionDir = resolveExtensionDir();
|
|
@@ -100,7 +100,7 @@ function getChangedFilesFromLastCommit(basePath: string): string[] | null {
|
|
|
100
100
|
try {
|
|
101
101
|
const result = execFileSync(
|
|
102
102
|
"git",
|
|
103
|
-
["diff", "--
|
|
103
|
+
["diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "HEAD"],
|
|
104
104
|
{ cwd: basePath, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" },
|
|
105
105
|
).trim();
|
|
106
106
|
return result ? result.split("\n").filter(Boolean) : [];
|
|
@@ -341,6 +341,18 @@ test("dynamic routing passes provider-qualified model keys to the router", () =>
|
|
|
341
341
|
);
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
+
test("selectAndApplyModel re-applies captured thinking level after setModel success", () => {
|
|
345
|
+
const src = readFileSync(join(__dirname, "..", "auto-model-selection.ts"), "utf-8");
|
|
346
|
+
assert.ok(
|
|
347
|
+
src.includes("autoModeStartThinkingLevel?: ReturnType<ExtensionAPI[\"getThinkingLevel\"]> | null"),
|
|
348
|
+
"selectAndApplyModel should accept an autoModeStartThinkingLevel parameter",
|
|
349
|
+
);
|
|
350
|
+
assert.ok(
|
|
351
|
+
src.includes("reapplyThinkingLevel(pi, autoModeStartThinkingLevel)"),
|
|
352
|
+
"selectAndApplyModel should re-apply captured thinking level after model changes",
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
344
356
|
test("resolveModelId: anthropic wins over claude-code when session provider is not claude-code", () => {
|
|
345
357
|
const availableModels = [
|
|
346
358
|
{ id: "claude-sonnet-4-6", provider: "claude-code" },
|
|
@@ -52,9 +52,7 @@ test("bootstrapAutoSession checks manual session override before preferences", (
|
|
|
52
52
|
"manual override and preference fallback must be resolved before building startModelSnapshot",
|
|
53
53
|
);
|
|
54
54
|
|
|
55
|
-
//
|
|
56
|
-
// sources so PREFERENCES.md continues to win over a stale settings.json
|
|
57
|
-
// default for built-in providers.
|
|
55
|
+
// Preferred model should still be part of fallback resolution.
|
|
58
56
|
const snapshotBlock = source.slice(snapshotIdx, snapshotIdx + 400);
|
|
59
57
|
assert.ok(
|
|
60
58
|
snapshotBlock.includes("validatedPreferredModel") || snapshotBlock.includes("preferredModel"),
|
|
@@ -62,6 +60,22 @@ test("bootstrapAutoSession checks manual session override before preferences", (
|
|
|
62
60
|
);
|
|
63
61
|
});
|
|
64
62
|
|
|
63
|
+
test("bootstrapAutoSession prioritizes current session model over PREFERENCES.md default", () => {
|
|
64
|
+
const snapshotIdx = source.indexOf("const startModelSnapshot = manualSessionOverride");
|
|
65
|
+
assert.ok(snapshotIdx > -1, "auto-start.ts should build startModelSnapshot");
|
|
66
|
+
|
|
67
|
+
const snapshotBlock = source.slice(snapshotIdx, snapshotIdx + 500);
|
|
68
|
+
const currentIdx = snapshotBlock.indexOf("currentSessionModel");
|
|
69
|
+
const preferredIdx = snapshotBlock.indexOf("validatedPreferredModel");
|
|
70
|
+
|
|
71
|
+
assert.ok(currentIdx > -1, "startModelSnapshot should include currentSessionModel");
|
|
72
|
+
assert.ok(preferredIdx > -1, "startModelSnapshot should include validatedPreferredModel");
|
|
73
|
+
assert.ok(
|
|
74
|
+
currentIdx < preferredIdx,
|
|
75
|
+
"startModelSnapshot should prefer currentSessionModel before validatedPreferredModel",
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
65
79
|
test("bootstrapAutoSession prefers session model over PREFERENCES.md when provider is custom (#4122)", () => {
|
|
66
80
|
// Custom providers (Ollama, vLLM, OpenAI-compatible proxies) live in
|
|
67
81
|
// ~/.gsd/agent/models.json, not PREFERENCES.md. When the user picks one
|
|
@@ -111,3 +125,19 @@ test("bootstrapAutoSession validates preferred model against live registry auth
|
|
|
111
125
|
const warningIdx = source.indexOf("is not configured; falling back to session default");
|
|
112
126
|
assert.ok(warningIdx > -1, "auto-start.ts should warn when preferred model is unconfigured");
|
|
113
127
|
});
|
|
128
|
+
|
|
129
|
+
test("bootstrapAutoSession snapshots and persists thinking level for auto-mode lifecycle", () => {
|
|
130
|
+
const captureIdx = source.indexOf("const startThinkingSnapshot = pi.getThinkingLevel()");
|
|
131
|
+
assert.ok(captureIdx > -1, "auto-start.ts should snapshot thinking level at bootstrap start");
|
|
132
|
+
|
|
133
|
+
const originalThinkingIdx = source.indexOf("s.originalThinkingLevel = startThinkingSnapshot ?? null");
|
|
134
|
+
assert.ok(originalThinkingIdx > -1, "auto-start.ts should store originalThinkingLevel from snapshot");
|
|
135
|
+
|
|
136
|
+
const autoThinkingIdx = source.indexOf("s.autoModeStartThinkingLevel = startThinkingSnapshot ?? null");
|
|
137
|
+
assert.ok(autoThinkingIdx > -1, "auto-start.ts should store autoModeStartThinkingLevel from snapshot");
|
|
138
|
+
|
|
139
|
+
assert.ok(
|
|
140
|
+
captureIdx < originalThinkingIdx && captureIdx < autoThinkingIdx,
|
|
141
|
+
"thinking snapshot must be captured before session state assignment",
|
|
142
|
+
);
|
|
143
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const autoSrc = readFileSync(join(import.meta.dirname, "..", "auto.ts"), "utf-8");
|
|
7
|
+
const phasesSrc = readFileSync(join(import.meta.dirname, "..", "auto", "phases.ts"), "utf-8");
|
|
8
|
+
|
|
9
|
+
test("stopAuto restores original thinking level", () => {
|
|
10
|
+
assert.ok(
|
|
11
|
+
autoSrc.includes("if (pi && s.originalThinkingLevel)"),
|
|
12
|
+
"auto.ts should conditionally restore original thinking level in stopAuto",
|
|
13
|
+
);
|
|
14
|
+
assert.ok(
|
|
15
|
+
autoSrc.includes("pi.setThinkingLevel(s.originalThinkingLevel)"),
|
|
16
|
+
"auto.ts should call pi.setThinkingLevel with originalThinkingLevel",
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("runUnitPhase threads captured thinking level into selectAndApplyModel", () => {
|
|
21
|
+
const callIdx = phasesSrc.indexOf("deps.selectAndApplyModel(");
|
|
22
|
+
assert.ok(callIdx > -1, "phases.ts should call selectAndApplyModel");
|
|
23
|
+
const callBlock = phasesSrc.slice(callIdx, callIdx + 600);
|
|
24
|
+
assert.ok(
|
|
25
|
+
callBlock.includes("s.autoModeStartThinkingLevel"),
|
|
26
|
+
"runUnitPhase should pass autoModeStartThinkingLevel to selectAndApplyModel",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("hook model override preserves captured thinking level", () => {
|
|
31
|
+
const hookIdx = phasesSrc.indexOf("const hookModelOverride = sidecarItem?.model ?? iterData.hookModelOverride;");
|
|
32
|
+
assert.ok(hookIdx > -1, "phases.ts should include hook model override handling");
|
|
33
|
+
const hookBlock = phasesSrc.slice(hookIdx, hookIdx + 600);
|
|
34
|
+
assert.ok(
|
|
35
|
+
hookBlock.includes("pi.setThinkingLevel(s.autoModeStartThinkingLevel)"),
|
|
36
|
+
"hook model override should re-apply captured thinking level after setModel",
|
|
37
|
+
);
|
|
38
|
+
});
|
|
@@ -21,9 +21,9 @@ test("tierOrdinal returns correct ordering", () => {
|
|
|
21
21
|
|
|
22
22
|
// ─── Unit Type Classification ────────────────────────────────────────────────
|
|
23
23
|
|
|
24
|
-
test("complete-slice classifies as
|
|
24
|
+
test("complete-slice classifies as standard", () => {
|
|
25
25
|
const result = classifyUnitComplexity("complete-slice", "M001/S01", "/tmp/fake");
|
|
26
|
-
assert.equal(result.tier, "
|
|
26
|
+
assert.equal(result.tier, "standard");
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test("run-uat classifies as light", () => {
|
|
@@ -145,7 +145,7 @@ test("budget pressure at 90% downgrades standard to light", () => {
|
|
|
145
145
|
assert.equal(result.downgraded, true);
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
test("budget pressure at 90% downgrades
|
|
148
|
+
test("budget pressure at 90% downgrades complete-slice standard to light", () => {
|
|
149
149
|
const result = classifyUnitComplexity("complete-slice", "M001/S01", "/tmp/fake", 0.95);
|
|
150
150
|
assert.equal(result.tier, "light");
|
|
151
151
|
});
|
|
@@ -15,6 +15,26 @@ function git(cwd: string, ...args: string[]): string {
|
|
|
15
15
|
}).trim();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
test("validateFileChanges works on repos with a single commit (no HEAD~1)", (t) => {
|
|
19
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-file-change-validator-"));
|
|
20
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
21
|
+
|
|
22
|
+
git(base, "init");
|
|
23
|
+
git(base, "config", "user.email", "test@example.com");
|
|
24
|
+
git(base, "config", "user.name", "Test User");
|
|
25
|
+
|
|
26
|
+
writeFileSync(join(base, "foo.ts"), "export const x = 1;\n");
|
|
27
|
+
git(base, "add", ".");
|
|
28
|
+
git(base, "commit", "-m", "initial");
|
|
29
|
+
|
|
30
|
+
// With only one commit, HEAD~1 doesn't exist — this must not throw
|
|
31
|
+
const audit = validateFileChanges(base, ["foo.ts"], []);
|
|
32
|
+
|
|
33
|
+
assert.ok(audit, "audit should be produced for single-commit repo");
|
|
34
|
+
assert.deepEqual(audit.unexpectedFiles, []);
|
|
35
|
+
assert.deepEqual(audit.missingFiles, []);
|
|
36
|
+
});
|
|
37
|
+
|
|
18
38
|
test("validateFileChanges ignores inline descriptions in expected output paths", (t) => {
|
|
19
39
|
const base = mkdtempSync(join(tmpdir(), "gsd-file-change-validator-"));
|
|
20
40
|
t.after(() => rmSync(base, { recursive: true, force: true }));
|