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.
Files changed (71) hide show
  1. package/README.md +3 -1
  2. package/dist/resources/extensions/gsd/auto-loop.js +7 -2
  3. package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
  4. package/dist/resources/extensions/gsd/commands-rate.js +31 -0
  5. package/dist/resources/extensions/gsd/commands.js +43 -1
  6. package/dist/resources/extensions/gsd/guided-flow.js +7 -1
  7. package/dist/resources/extensions/gsd/index.js +16 -32
  8. package/dist/resources/extensions/gsd/preferences.js +12 -0
  9. package/dist/resources/extensions/gsd/session-lock.js +27 -0
  10. package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
  11. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
  12. package/dist/resources/skills/github-workflows/SKILL.md +0 -2
  13. package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
  14. package/package.json +1 -1
  15. package/packages/pi-agent-core/dist/agent.d.ts +10 -2
  16. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  17. package/packages/pi-agent-core/dist/agent.js +19 -8
  18. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  19. package/packages/pi-agent-core/src/agent.ts +31 -10
  20. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  21. package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
  22. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  23. package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
  24. package/src/resources/extensions/gsd/auto-loop.ts +11 -1
  25. package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
  26. package/src/resources/extensions/gsd/commands-rate.ts +55 -0
  27. package/src/resources/extensions/gsd/commands.ts +43 -1
  28. package/src/resources/extensions/gsd/guided-flow.ts +7 -1
  29. package/src/resources/extensions/gsd/index.ts +20 -32
  30. package/src/resources/extensions/gsd/preferences.ts +14 -1
  31. package/src/resources/extensions/gsd/session-lock.ts +30 -0
  32. package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
  33. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
  34. package/src/resources/skills/github-workflows/SKILL.md +0 -2
  35. package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
  36. package/dist/resources/skills/swiftui/SKILL.md +0 -208
  37. package/dist/resources/skills/swiftui/references/animations.md +0 -921
  38. package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
  39. package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
  40. package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
  41. package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
  42. package/dist/resources/skills/swiftui/references/performance.md +0 -1706
  43. package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
  44. package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
  45. package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
  46. package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
  47. package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
  48. package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
  49. package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
  50. package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
  51. package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
  52. package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
  53. package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
  54. package/src/resources/skills/swiftui/SKILL.md +0 -208
  55. package/src/resources/skills/swiftui/references/animations.md +0 -921
  56. package/src/resources/skills/swiftui/references/architecture.md +0 -1561
  57. package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
  58. package/src/resources/skills/swiftui/references/navigation.md +0 -1492
  59. package/src/resources/skills/swiftui/references/networking-async.md +0 -214
  60. package/src/resources/skills/swiftui/references/performance.md +0 -1706
  61. package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
  62. package/src/resources/skills/swiftui/references/state-management.md +0 -1443
  63. package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
  64. package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
  65. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
  66. package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
  67. package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
  68. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
  69. package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
  70. package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
  71. 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
- role: "user",
1176
- content,
1177
- timestamp: Date.now(),
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
- role: "user",
1192
- content,
1193
- timestamp: Date.now(),
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
- const steering = [...this._steeringMessages];
1308
- const followUp = [...this._followUpMessages];
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
- const classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct);
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
- // Bare /gsd defaults to step mode
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. Run /gsd to start planning.`,
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
- // Lightweight "always follow" files injected into every GSD agent session.
71
- // Global: ~/.gsd/agent-instructions.md Project: .gsd/agent-instructions.md
72
- // Both are loaded and concatenated (global first, project appends).
73
-
74
- function loadAgentInstructions(): string | null {
75
- const parts: string[] = [];
76
-
77
- const globalPath = join(homedir(), ".gsd", "agent-instructions.md");
78
- if (existsSync(globalPath)) {
79
- try {
80
- const content = readFileSync(globalPath, "utf-8").trim();
81
- if (content) parts.push(content);
82
- } catch { /* non-fatal skip unreadable file */ }
83
- }
84
-
85
- const projectPath = join(process.cwd(), ".gsd", "agent-instructions.md");
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
- // Load agent instructions (global + project)
686
- let agentInstructionsBlock = "";
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}${agentInstructionsBlock}${knowledgeBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
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
- - [Performance skill](../performance/SKILL.md)
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 `references/key-rules-gotchas.md` and verify each rule against the extension code.
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
 
@@ -88,5 +88,3 @@ EVIDENCE: [output from ci_monitor.cjs]
88
88
  ## References
89
89
 
90
90
  - `references/gh/SKILL.md` — gh CLI reference
91
- - `scripts/ci_monitor.cjs` — CI monitoring tool
92
- - `scripts/ci_monitor.md` — Tool usage documentation
@@ -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)