gsd-pi 2.35.0-dev.39eee32 → 2.35.0-dev.4bbf377
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 +3 -1
- package/dist/resources/extensions/gsd/auto-loop.js +7 -2
- package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
- package/dist/resources/extensions/gsd/commands-rate.js +31 -0
- package/dist/resources/extensions/gsd/commands.js +43 -1
- package/dist/resources/extensions/gsd/guided-flow.js +7 -1
- package/dist/resources/extensions/gsd/index.js +16 -32
- package/dist/resources/extensions/gsd/preferences.js +12 -0
- package/dist/resources/extensions/gsd/session-lock.js +27 -0
- package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/dist/resources/skills/github-workflows/SKILL.md +0 -2
- package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +10 -2
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +19 -8
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/src/agent.ts +31 -10
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
- package/src/resources/extensions/gsd/auto-loop.ts +11 -1
- package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
- package/src/resources/extensions/gsd/commands-rate.ts +55 -0
- package/src/resources/extensions/gsd/commands.ts +43 -1
- package/src/resources/extensions/gsd/guided-flow.ts +7 -1
- package/src/resources/extensions/gsd/index.ts +20 -32
- package/src/resources/extensions/gsd/preferences.ts +14 -1
- package/src/resources/extensions/gsd/session-lock.ts +30 -0
- package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
- package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
- package/src/resources/skills/github-workflows/SKILL.md +0 -2
- package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
- package/dist/resources/skills/swiftui/SKILL.md +0 -208
- package/dist/resources/skills/swiftui/references/animations.md +0 -921
- package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
- package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
- package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
- package/dist/resources/skills/swiftui/references/performance.md +0 -1706
- package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
- package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
- package/src/resources/skills/swiftui/SKILL.md +0 -208
- package/src/resources/skills/swiftui/references/animations.md +0 -921
- package/src/resources/skills/swiftui/references/architecture.md +0 -1561
- package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
- package/src/resources/skills/swiftui/references/navigation.md +0 -1492
- package/src/resources/skills/swiftui/references/networking-async.md +0 -214
- package/src/resources/skills/swiftui/references/performance.md +0 -1706
- package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
- package/src/resources/skills/swiftui/references/state-management.md +0 -1443
- package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
- package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
- package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
- package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
- package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
- package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
- package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
- package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
- package/src/resources/skills/swiftui/workflows/write-tests.md +0 -235
|
@@ -1171,11 +1171,14 @@ export class AgentSession {
|
|
|
1171
1171
|
if (images) {
|
|
1172
1172
|
content.push(...images);
|
|
1173
1173
|
}
|
|
1174
|
-
this.agent.steer(
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1174
|
+
this.agent.steer(
|
|
1175
|
+
{
|
|
1176
|
+
role: "user",
|
|
1177
|
+
content,
|
|
1178
|
+
timestamp: Date.now(),
|
|
1179
|
+
},
|
|
1180
|
+
"user",
|
|
1181
|
+
);
|
|
1179
1182
|
}
|
|
1180
1183
|
|
|
1181
1184
|
/**
|
|
@@ -1187,11 +1190,14 @@ export class AgentSession {
|
|
|
1187
1190
|
if (images) {
|
|
1188
1191
|
content.push(...images);
|
|
1189
1192
|
}
|
|
1190
|
-
this.agent.followUp(
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1193
|
+
this.agent.followUp(
|
|
1194
|
+
{
|
|
1195
|
+
role: "user",
|
|
1196
|
+
content,
|
|
1197
|
+
timestamp: Date.now(),
|
|
1198
|
+
},
|
|
1199
|
+
"user",
|
|
1200
|
+
);
|
|
1195
1201
|
}
|
|
1196
1202
|
|
|
1197
1203
|
/**
|
|
@@ -1304,10 +1310,28 @@ export class AgentSession {
|
|
|
1304
1310
|
* @returns Object with steering and followUp arrays
|
|
1305
1311
|
*/
|
|
1306
1312
|
clearQueue(): { steering: string[]; followUp: string[] } {
|
|
1307
|
-
|
|
1308
|
-
|
|
1313
|
+
// Drain user-origin messages from agent queues before clearing.
|
|
1314
|
+
// This preserves messages the user explicitly typed during streaming,
|
|
1315
|
+
// while system-generated messages (extension notifications, etc.) are discarded.
|
|
1316
|
+
const userMessages = this.agent.drainUserMessages();
|
|
1317
|
+
|
|
1318
|
+
// Extract text content from preserved user messages
|
|
1319
|
+
const extractText = (m: AgentMessage): string => {
|
|
1320
|
+
if (!("content" in m) || !Array.isArray(m.content)) return "";
|
|
1321
|
+
const textPart = m.content.find((c: { type: string }) => c.type === "text");
|
|
1322
|
+
return textPart && "text" in textPart ? (textPart as { text: string }).text : "";
|
|
1323
|
+
};
|
|
1324
|
+
const preservedSteering = userMessages.steering.map(extractText).filter((t) => t.length > 0);
|
|
1325
|
+
const preservedFollowUp = userMessages.followUp.map(extractText).filter((t) => t.length > 0);
|
|
1326
|
+
|
|
1327
|
+
// Session-level string arrays track what was queued for display purposes.
|
|
1328
|
+
// Return the full set (session-tracked + any agent-only user messages).
|
|
1329
|
+
const steering = [...this._steeringMessages, ...preservedSteering];
|
|
1330
|
+
const followUp = [...this._followUpMessages, ...preservedFollowUp];
|
|
1309
1331
|
this._steeringMessages = [];
|
|
1310
1332
|
this._followUpMessages = [];
|
|
1333
|
+
|
|
1334
|
+
// Clear remaining system messages from agent queues
|
|
1311
1335
|
this.agent.clearAllQueues();
|
|
1312
1336
|
return { steering, followUp };
|
|
1313
1337
|
}
|
|
@@ -456,6 +456,7 @@ export interface LoopDeps {
|
|
|
456
456
|
prefs: GSDPreferences | undefined,
|
|
457
457
|
verbose: boolean,
|
|
458
458
|
startModel: { provider: string; id: string } | null,
|
|
459
|
+
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
459
460
|
) => Promise<{ routing: { tier: string; modelDowngraded: boolean } | null }>;
|
|
460
461
|
startUnitSupervision: (sctx: {
|
|
461
462
|
s: AutoSession;
|
|
@@ -1182,6 +1183,14 @@ export async function autoLoop(
|
|
|
1182
1183
|
unitId,
|
|
1183
1184
|
});
|
|
1184
1185
|
|
|
1186
|
+
// Detect retry and capture previous tier for escalation
|
|
1187
|
+
const isRetry = !!(
|
|
1188
|
+
s.currentUnit &&
|
|
1189
|
+
s.currentUnit.type === unitType &&
|
|
1190
|
+
s.currentUnit.id === unitId
|
|
1191
|
+
);
|
|
1192
|
+
const previousTier = s.currentUnitRouting?.tier;
|
|
1193
|
+
|
|
1185
1194
|
// Closeout previous unit
|
|
1186
1195
|
if (s.currentUnit) {
|
|
1187
1196
|
await deps.closeoutUnit(
|
|
@@ -1335,7 +1344,7 @@ export async function autoLoop(
|
|
|
1335
1344
|
);
|
|
1336
1345
|
}
|
|
1337
1346
|
|
|
1338
|
-
// Select and apply model
|
|
1347
|
+
// Select and apply model (with tier escalation on retry)
|
|
1339
1348
|
const modelResult = await deps.selectAndApplyModel(
|
|
1340
1349
|
ctx,
|
|
1341
1350
|
pi,
|
|
@@ -1345,6 +1354,7 @@ export async function autoLoop(
|
|
|
1345
1354
|
prefs,
|
|
1346
1355
|
s.verbose,
|
|
1347
1356
|
s.autoModeStartModel,
|
|
1357
|
+
{ isRetry, previousTier },
|
|
1348
1358
|
);
|
|
1349
1359
|
s.currentUnitRouting =
|
|
1350
1360
|
modelResult.routing as AutoSession["currentUnitRouting"];
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
|
|
8
8
|
import type { GSDPreferences } from "./preferences.js";
|
|
9
9
|
import { resolveModelWithFallbacksForUnit, resolveDynamicRoutingConfig } from "./preferences.js";
|
|
10
|
+
import type { ComplexityTier } from "./complexity-classifier.js";
|
|
10
11
|
import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js";
|
|
11
|
-
import { resolveModelForComplexity } from "./model-router.js";
|
|
12
|
+
import { resolveModelForComplexity, escalateTier } from "./model-router.js";
|
|
12
13
|
import { getLedger, getProjectTotals } from "./metrics.js";
|
|
13
14
|
import { unitPhaseLabel } from "./auto-dashboard.js";
|
|
14
15
|
|
|
@@ -33,6 +34,7 @@ export async function selectAndApplyModel(
|
|
|
33
34
|
prefs: GSDPreferences | undefined,
|
|
34
35
|
verbose: boolean,
|
|
35
36
|
autoModeStartModel: { provider: string; id: string } | null,
|
|
37
|
+
retryContext?: { isRetry: boolean; previousTier?: string },
|
|
36
38
|
): Promise<ModelSelectionResult> {
|
|
37
39
|
const modelConfig = resolveModelWithFallbacksForUnit(unitType);
|
|
38
40
|
let routing: { tier: string; modelDowngraded: boolean } | null = null;
|
|
@@ -60,8 +62,27 @@ export async function selectAndApplyModel(
|
|
|
60
62
|
const shouldClassify = !isHook || routingConfig.hooks !== false;
|
|
61
63
|
|
|
62
64
|
if (shouldClassify) {
|
|
63
|
-
|
|
65
|
+
let classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct);
|
|
64
66
|
const availableModelIds = availableModels.map(m => m.id);
|
|
67
|
+
|
|
68
|
+
// Escalate tier on retry when escalate_on_failure is enabled (default: true)
|
|
69
|
+
if (
|
|
70
|
+
retryContext?.isRetry &&
|
|
71
|
+
retryContext.previousTier &&
|
|
72
|
+
routingConfig.escalate_on_failure !== false
|
|
73
|
+
) {
|
|
74
|
+
const escalated = escalateTier(retryContext.previousTier as ComplexityTier);
|
|
75
|
+
if (escalated) {
|
|
76
|
+
classification = { ...classification, tier: escalated, reason: "escalated after failure" };
|
|
77
|
+
if (verbose) {
|
|
78
|
+
ctx.ui.notify(
|
|
79
|
+
`Tier escalation: ${retryContext.previousTier} → ${escalated} (retry after failure)`,
|
|
80
|
+
"info",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
65
86
|
const routingResult = resolveModelForComplexity(classification, modelConfig, routingConfig, availableModelIds);
|
|
66
87
|
|
|
67
88
|
if (routingResult.wasDowngraded) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /gsd rate — Submit feedback on the last unit's model tier assignment.
|
|
3
|
+
* Feeds into the adaptive routing history so future dispatches improve.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
7
|
+
import { loadLedgerFromDisk } from "./metrics.js";
|
|
8
|
+
import { recordFeedback, initRoutingHistory } from "./routing-history.js";
|
|
9
|
+
import type { ComplexityTier } from "./complexity-classifier.js";
|
|
10
|
+
|
|
11
|
+
const VALID_RATINGS = new Set(["over", "under", "ok"]);
|
|
12
|
+
|
|
13
|
+
export async function handleRate(
|
|
14
|
+
args: string,
|
|
15
|
+
ctx: ExtensionCommandContext,
|
|
16
|
+
basePath: string,
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
const rating = args.trim().toLowerCase();
|
|
19
|
+
|
|
20
|
+
if (!rating || !VALID_RATINGS.has(rating)) {
|
|
21
|
+
ctx.ui.notify(
|
|
22
|
+
"Usage: /gsd rate <over|ok|under>\n" +
|
|
23
|
+
" over — model was overpowered for that task (encourage cheaper)\n" +
|
|
24
|
+
" ok — model was appropriate\n" +
|
|
25
|
+
" under — model was too weak (encourage stronger)",
|
|
26
|
+
"info",
|
|
27
|
+
);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ledger = loadLedgerFromDisk(basePath);
|
|
32
|
+
if (!ledger || ledger.units.length === 0) {
|
|
33
|
+
ctx.ui.notify("No completed units found — nothing to rate.", "warning");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const lastUnit = ledger.units[ledger.units.length - 1];
|
|
38
|
+
const tier = lastUnit.tier as ComplexityTier | undefined;
|
|
39
|
+
|
|
40
|
+
if (!tier) {
|
|
41
|
+
ctx.ui.notify(
|
|
42
|
+
"Last unit has no tier data (dynamic routing was not active). Rating skipped.",
|
|
43
|
+
"warning",
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
initRoutingHistory(basePath);
|
|
49
|
+
recordFeedback(lastUnit.type, lastUnit.id, tier, rating as "over" | "under" | "ok");
|
|
50
|
+
|
|
51
|
+
ctx.ui.notify(
|
|
52
|
+
`Recorded "${rating}" for ${lastUnit.type}/${lastUnit.id} at tier ${tier}.`,
|
|
53
|
+
"info",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -48,6 +48,7 @@ import { computeProgressScore, formatProgressLine } from "./progress-score.js";
|
|
|
48
48
|
import { runEnvironmentChecks } from "./doctor-environment.js";
|
|
49
49
|
import { handleLogs } from "./commands-logs.js";
|
|
50
50
|
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
51
|
+
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
51
52
|
|
|
52
53
|
|
|
53
54
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
@@ -69,6 +70,39 @@ export function projectRoot(): string {
|
|
|
69
70
|
return root;
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Check if another process holds the auto-mode session lock.
|
|
75
|
+
* Returns the lock data if a remote session is alive, null otherwise.
|
|
76
|
+
*/
|
|
77
|
+
function getRemoteAutoSession(basePath: string): { pid: number } | null {
|
|
78
|
+
const lockData = readSessionLockData(basePath);
|
|
79
|
+
if (!lockData) return null;
|
|
80
|
+
if (lockData.pid === process.pid) return null;
|
|
81
|
+
if (!isSessionLockProcessAlive(lockData)) return null;
|
|
82
|
+
return { pid: lockData.pid };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Show a steering menu when auto-mode is running in another process.
|
|
87
|
+
* Returns true if a remote session was detected (caller should return early).
|
|
88
|
+
*/
|
|
89
|
+
function notifyRemoteAutoActive(ctx: ExtensionCommandContext, basePath: string): boolean {
|
|
90
|
+
const remote = getRemoteAutoSession(basePath);
|
|
91
|
+
if (!remote) return false;
|
|
92
|
+
ctx.ui.notify(
|
|
93
|
+
`Auto-mode is running in another process (PID ${remote.pid}).\n` +
|
|
94
|
+
`Use these commands to interact with it:\n` +
|
|
95
|
+
` /gsd status — check progress\n` +
|
|
96
|
+
` /gsd discuss — discuss architecture decisions\n` +
|
|
97
|
+
` /gsd queue — queue the next milestone\n` +
|
|
98
|
+
` /gsd steer — apply an override to active work\n` +
|
|
99
|
+
` /gsd capture — fire-and-forget thought\n` +
|
|
100
|
+
` /gsd stop — stop auto-mode`,
|
|
101
|
+
"warning",
|
|
102
|
+
);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
72
106
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
73
107
|
pi.registerCommand("gsd", {
|
|
74
108
|
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|update",
|
|
@@ -89,6 +123,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
89
123
|
{ cmd: "triage", desc: "Manually trigger triage of pending captures" },
|
|
90
124
|
{ cmd: "dispatch", desc: "Dispatch a specific phase directly" },
|
|
91
125
|
{ cmd: "history", desc: "View execution history" },
|
|
126
|
+
{ cmd: "rate", desc: "Rate last unit's model tier (over/ok/under) — improves adaptive routing" },
|
|
92
127
|
{ cmd: "undo", desc: "Revert last completed unit" },
|
|
93
128
|
{ cmd: "skip", desc: "Prevent a unit from auto-mode dispatch" },
|
|
94
129
|
{ cmd: "export", desc: "Export milestone/slice results" },
|
|
@@ -511,6 +546,7 @@ export async function handleGSDCommand(
|
|
|
511
546
|
await handleDryRun(ctx, projectRoot());
|
|
512
547
|
return;
|
|
513
548
|
}
|
|
549
|
+
if (notifyRemoteAutoActive(ctx, projectRoot())) return;
|
|
514
550
|
const verboseMode = trimmed.includes("--verbose");
|
|
515
551
|
const debugMode = trimmed.includes("--debug");
|
|
516
552
|
if (debugMode) enableDebug(projectRoot());
|
|
@@ -566,6 +602,12 @@ export async function handleGSDCommand(
|
|
|
566
602
|
return;
|
|
567
603
|
}
|
|
568
604
|
|
|
605
|
+
if (trimmed === "rate" || trimmed.startsWith("rate ")) {
|
|
606
|
+
const { handleRate } = await import("./commands-rate.js");
|
|
607
|
+
await handleRate(trimmed.replace(/^rate\s*/, "").trim(), ctx, projectRoot());
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
569
611
|
if (trimmed.startsWith("skip ")) {
|
|
570
612
|
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
|
|
571
613
|
return;
|
|
@@ -899,7 +941,7 @@ Examples:
|
|
|
899
941
|
}
|
|
900
942
|
|
|
901
943
|
if (trimmed === "") {
|
|
902
|
-
|
|
944
|
+
if (notifyRemoteAutoActive(ctx, projectRoot())) return;
|
|
903
945
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
904
946
|
return;
|
|
905
947
|
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
} from "./paths.js";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
26
|
+
import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
|
|
26
27
|
import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
|
|
27
28
|
import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
|
|
28
29
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
@@ -516,8 +517,13 @@ export async function showDiscuss(
|
|
|
516
517
|
// If all pending slices are discussed, notify and exit instead of looping
|
|
517
518
|
const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
|
|
518
519
|
if (allDiscussed) {
|
|
520
|
+
const lockData = readSessionLockData(basePath);
|
|
521
|
+
const remoteAutoRunning = lockData && lockData.pid !== process.pid && isSessionLockProcessAlive(lockData);
|
|
522
|
+
const nextStep = remoteAutoRunning
|
|
523
|
+
? "Auto-mode is already running — use /gsd status to check progress."
|
|
524
|
+
: "Run /gsd to start planning.";
|
|
519
525
|
ctx.ui.notify(
|
|
520
|
-
`All ${pendingSlices.length} slices discussed.
|
|
526
|
+
`All ${pendingSlices.length} slices discussed. ${nextStep}`,
|
|
521
527
|
"info",
|
|
522
528
|
);
|
|
523
529
|
return;
|
|
@@ -66,32 +66,24 @@ import { toPosixPath } from "../shared/mod.js";
|
|
|
66
66
|
import { isParallelActive, shutdownParallel } from "./parallel-orchestrator.js";
|
|
67
67
|
import { DEFAULT_BASH_TIMEOUT_SECS } from "./constants.js";
|
|
68
68
|
|
|
69
|
-
// ── Agent Instructions
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (existsSync(projectPath)) {
|
|
87
|
-
try {
|
|
88
|
-
const content = readFileSync(projectPath, "utf-8").trim();
|
|
89
|
-
if (content) parts.push(content);
|
|
90
|
-
} catch { /* non-fatal — skip unreadable file */ }
|
|
69
|
+
// ── Agent Instructions (DEPRECATED) ──────────────────────────────────────
|
|
70
|
+
// agent-instructions.md is deprecated. Use AGENTS.md or CLAUDE.md instead.
|
|
71
|
+
// Pi core natively supports AGENTS.md (with CLAUDE.md fallback) per directory.
|
|
72
|
+
|
|
73
|
+
function warnDeprecatedAgentInstructions(): void {
|
|
74
|
+
const paths = [
|
|
75
|
+
join(homedir(), ".gsd", "agent-instructions.md"),
|
|
76
|
+
join(process.cwd(), ".gsd", "agent-instructions.md"),
|
|
77
|
+
];
|
|
78
|
+
for (const p of paths) {
|
|
79
|
+
if (existsSync(p)) {
|
|
80
|
+
console.warn(
|
|
81
|
+
`[GSD] DEPRECATED: ${p} is no longer loaded. ` +
|
|
82
|
+
`Migrate your instructions to AGENTS.md (or CLAUDE.md) in the same directory. ` +
|
|
83
|
+
`See https://github.com/gsd-build/GSD-2/issues/1492`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
91
86
|
}
|
|
92
|
-
|
|
93
|
-
if (parts.length === 0) return null;
|
|
94
|
-
return parts.join("\n\n");
|
|
95
87
|
}
|
|
96
88
|
|
|
97
89
|
// ── Depth verification state ──────────────────────────────────────────────
|
|
@@ -682,12 +674,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
682
674
|
}
|
|
683
675
|
}
|
|
684
676
|
|
|
685
|
-
//
|
|
686
|
-
|
|
687
|
-
const agentInstructions = loadAgentInstructions();
|
|
688
|
-
if (agentInstructions) {
|
|
689
|
-
agentInstructionsBlock = `\n\n## Agent Instructions\n\nThe following instructions were provided by the user and must be followed in every session:\n\n${agentInstructions}`;
|
|
690
|
-
}
|
|
677
|
+
// Warn if deprecated agent-instructions.md files are still present
|
|
678
|
+
warnDeprecatedAgentInstructions();
|
|
691
679
|
|
|
692
680
|
const injection = await buildGuidedExecuteContextInjection(event.prompt, process.cwd());
|
|
693
681
|
|
|
@@ -732,7 +720,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
732
720
|
].join("\n");
|
|
733
721
|
}
|
|
734
722
|
|
|
735
|
-
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${
|
|
723
|
+
const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${knowledgeBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
|
|
736
724
|
stopContextTimer({
|
|
737
725
|
systemPromptSize: fullSystem.length,
|
|
738
726
|
injectionSize: injection?.length ?? 0,
|
|
@@ -15,9 +15,10 @@ import { homedir } from "node:os";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
17
|
import { parse as parseYaml } from "yaml";
|
|
18
|
-
import type { PostUnitHookConfig, PreDispatchHookConfig } from "./types.js";
|
|
18
|
+
import type { PostUnitHookConfig, PreDispatchHookConfig, TokenProfile } from "./types.js";
|
|
19
19
|
import type { DynamicRoutingConfig } from "./model-router.js";
|
|
20
20
|
import { normalizeStringArray } from "../shared/mod.js";
|
|
21
|
+
import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
|
|
21
22
|
|
|
22
23
|
import {
|
|
23
24
|
MODE_DEFAULTS,
|
|
@@ -141,6 +142,18 @@ export function loadEffectiveGSDPreferences(): LoadedGSDPreferences | null {
|
|
|
141
142
|
};
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
// Apply token-profile defaults as the lowest-priority layer so that
|
|
146
|
+
// `token_profile: budget` sets models and phase-skips automatically.
|
|
147
|
+
// Explicit user preferences always override profile defaults.
|
|
148
|
+
const profile = result.preferences.token_profile as TokenProfile | undefined;
|
|
149
|
+
if (profile) {
|
|
150
|
+
const profileDefaults = _resolveProfileDefaults(profile);
|
|
151
|
+
result = {
|
|
152
|
+
...result,
|
|
153
|
+
preferences: mergePreferences(profileDefaults as GSDPreferences, result.preferences),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
144
157
|
// Apply mode defaults as the lowest-priority layer
|
|
145
158
|
if (result.preferences.mode) {
|
|
146
159
|
result = {
|
|
@@ -260,6 +260,16 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
|
|
|
260
260
|
stale: 1_800_000, // 30 minutes — match primary lock settings
|
|
261
261
|
update: 10_000,
|
|
262
262
|
onCompromised: () => {
|
|
263
|
+
// Same false-positive suppression as the primary lock (#1512).
|
|
264
|
+
// Without this, the retry path fires _lockCompromised unconditionally
|
|
265
|
+
// on benign mtime drift (laptop sleep, heavy LLM event loop stalls).
|
|
266
|
+
const elapsed = Date.now() - _lockAcquiredAt;
|
|
267
|
+
if (elapsed < 1_800_000) {
|
|
268
|
+
process.stderr.write(
|
|
269
|
+
`[gsd] Lock heartbeat mismatch after ${Math.round(elapsed / 1000)}s — event loop stall, continuing.\n`,
|
|
270
|
+
);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
263
273
|
_lockCompromised = true;
|
|
264
274
|
_releaseFunction = null;
|
|
265
275
|
},
|
|
@@ -361,6 +371,26 @@ export function updateSessionLock(
|
|
|
361
371
|
export function validateSessionLock(basePath: string): boolean {
|
|
362
372
|
// Lock was compromised by proper-lockfile (mtime drift from sleep, stall, etc.)
|
|
363
373
|
if (_lockCompromised) {
|
|
374
|
+
// Recovery gate (#1512): Before declaring the lock lost, check if the lock
|
|
375
|
+
// file still contains our PID. If it does, no other process took over — the
|
|
376
|
+
// onCompromised fired from benign mtime drift (laptop sleep, event loop stall
|
|
377
|
+
// beyond the stale window). Attempt re-acquisition instead of giving up.
|
|
378
|
+
const lp = lockPath(basePath);
|
|
379
|
+
const existing = readExistingLockData(lp);
|
|
380
|
+
if (existing && existing.pid === process.pid) {
|
|
381
|
+
// Lock file still ours — try to re-acquire the OS lock
|
|
382
|
+
try {
|
|
383
|
+
const result = acquireSessionLock(basePath);
|
|
384
|
+
if (result.acquired) {
|
|
385
|
+
process.stderr.write(
|
|
386
|
+
`[gsd] Lock recovered after onCompromised — lock file PID matched, re-acquired.\n`,
|
|
387
|
+
);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
// Re-acquisition failed — fall through to return false
|
|
392
|
+
}
|
|
393
|
+
}
|
|
364
394
|
return false;
|
|
365
395
|
}
|
|
366
396
|
|
|
@@ -438,4 +438,4 @@ startTransition(() => setExpensiveState(newValue));
|
|
|
438
438
|
- [web.dev LCP](https://web.dev/articles/lcp)
|
|
439
439
|
- [web.dev INP](https://web.dev/articles/inp)
|
|
440
440
|
- [web.dev CLS](https://web.dev/articles/cls)
|
|
441
|
-
- [
|
|
441
|
+
- [Code Optimizer skill](../code-optimizer/SKILL.md)
|
|
@@ -42,7 +42,7 @@ The file must `export default function(pi: ExtensionAPI) { ... }`.
|
|
|
42
42
|
|
|
43
43
|
## Step 4: Check for Common Mistakes
|
|
44
44
|
|
|
45
|
-
Read
|
|
45
|
+
Read `../references/key-rules-gotchas.md` and verify each rule against the extension code.
|
|
46
46
|
|
|
47
47
|
## Step 5: Add Debugging
|
|
48
48
|
|
|
@@ -163,8 +163,6 @@ When performing an audit, structure findings as:
|
|
|
163
163
|
## References
|
|
164
164
|
|
|
165
165
|
For detailed guidelines on specific areas:
|
|
166
|
-
- [Performance Optimization](../performance/SKILL.md)
|
|
167
166
|
- [Core Web Vitals](../core-web-vitals/SKILL.md)
|
|
168
167
|
- [Accessibility](../accessibility/SKILL.md)
|
|
169
|
-
- [SEO](../seo/SKILL.md)
|
|
170
168
|
- [Best Practices](../best-practices/SKILL.md)
|