gsd-pi 2.28.0-dev.4009980 → 2.28.0-dev.e19bf89

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.
@@ -35,7 +35,6 @@ import {
35
35
  import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.js";
36
36
  import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences } from "./preferences.js";
37
37
  import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
38
- import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
39
38
  import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
40
39
  import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
41
40
  import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
@@ -155,17 +154,13 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
155
154
  ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
156
155
  }
157
156
 
158
- // Proactive health tracking — exclude completion-transition codes at task level
159
- // since they are expected after the last task and resolved by complete-slice
160
- const issuesForHealth = effectiveFixLevel === "task"
161
- ? report.issues.filter(i => !COMPLETION_TRANSITION_CODES.has(i.code))
162
- : report.issues;
163
- const summary = summarizeDoctorIssues(issuesForHealth);
157
+ // Proactive health tracking
158
+ const summary = summarizeDoctorIssues(report.issues);
164
159
  recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
165
160
 
166
161
  // Check if we should escalate to LLM-assisted heal
167
162
  if (summary.errors > 0) {
168
- const unresolvedErrors = issuesForHealth
163
+ const unresolvedErrors = report.issues
169
164
  .filter(i => i.severity === "error" && !i.fixable)
170
165
  .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
171
166
  const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
@@ -26,13 +26,6 @@ import {
26
26
  import { invalidateAllCaches } from "./cache.js";
27
27
  import { synthesizeCrashRecovery } from "./session-forensics.js";
28
28
  import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
29
- import {
30
- acquireSessionLock,
31
- updateSessionLock,
32
- releaseSessionLock,
33
- readSessionLockData,
34
- isSessionLockProcessAlive,
35
- } from "./session-lock.js";
36
29
  import { selfHealRuntimeRecords } from "./auto-recovery.js";
37
30
  import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
38
31
  import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
@@ -88,18 +81,6 @@ export async function bootstrapAutoSession(
88
81
  ): Promise<boolean> {
89
82
  const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase } = deps;
90
83
 
91
- // ── Session lock: acquire FIRST, before any state mutation ──────────────
92
- // This is the primary guard against concurrent sessions on the same project.
93
- // Uses OS-level file locking (proper-lockfile) to prevent TOCTOU races.
94
- const lockResult = acquireSessionLock(base);
95
- if (!lockResult.acquired) {
96
- ctx.ui.notify(
97
- `${lockResult.reason}\nStop it with \`kill ${lockResult.existingPid ?? "the other process"}\` before starting a new session.`,
98
- "error",
99
- );
100
- return false;
101
- }
102
-
103
84
  // Ensure git repo exists
104
85
  if (!nativeIsRepo(base)) {
105
86
  const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
@@ -128,11 +109,16 @@ export async function bootstrapAutoSession(
128
109
  // Initialize GitServiceImpl
129
110
  s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
130
111
 
131
- // Check for crash from previous session (use both old and new lock data)
112
+ // Check for crash from previous session
132
113
  const crashLock = readCrashLock(base);
133
114
  if (crashLock) {
134
- // We already hold the session lock, so no concurrent session is running.
135
- // The crash lock is from a dead process — recover context from it.
115
+ if (isLockProcessAlive(crashLock)) {
116
+ ctx.ui.notify(
117
+ `Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
118
+ "error",
119
+ );
120
+ return false;
121
+ }
136
122
  const recoveredMid = crashLock.unitId.split("/")[0];
137
123
  const milestoneAlreadyComplete = recoveredMid
138
124
  ? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
@@ -421,8 +407,7 @@ export async function bootstrapAutoSession(
421
407
  : "Will loop until milestone complete.";
422
408
  ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
423
409
 
424
- // Update lock file with milestone info (OS lock already acquired at bootstrap start)
425
- updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
410
+ // Write initial lock file
426
411
  writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
427
412
 
428
413
  // Secrets collection gate — pause instead of blocking (#1146)
@@ -33,12 +33,6 @@ import { invalidateAllCaches } from "./cache.js";
33
33
  import { saveActivityLog, clearActivityLogState } from "./activity-log.js";
34
34
  import { synthesizeCrashRecovery, getDeepDiagnostic } from "./session-forensics.js";
35
35
  import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
36
- import {
37
- acquireSessionLock,
38
- validateSessionLock,
39
- releaseSessionLock,
40
- updateSessionLock,
41
- } from "./session-lock.js";
42
36
  import {
43
37
  clearUnitRuntimeRecord,
44
38
  inspectExecuteTaskDurability,
@@ -457,10 +451,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
457
451
  if (!s.active && !s.paused) return;
458
452
  const reasonSuffix = reason ? ` — ${reason}` : "";
459
453
  clearUnitTimeout();
460
- if (lockBase()) {
461
- releaseSessionLock(lockBase());
462
- clearLock(lockBase());
463
- }
454
+ if (lockBase()) clearLock(lockBase());
464
455
  clearSkillSnapshot();
465
456
  resetSkillTelemetry();
466
457
  s.dispatching = false;
@@ -574,10 +565,7 @@ export async function pauseAuto(ctx?: ExtensionContext, _pi?: ExtensionAPI): Pro
574
565
 
575
566
  s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
576
567
 
577
- if (lockBase()) {
578
- releaseSessionLock(lockBase());
579
- clearLock(lockBase());
580
- }
568
+ if (lockBase()) clearLock(lockBase());
581
569
 
582
570
  deregisterSigtermHandler();
583
571
 
@@ -610,16 +598,6 @@ export async function startAuto(
610
598
 
611
599
  // If resuming from paused state, just re-activate and dispatch next unit.
612
600
  if (s.paused) {
613
- // Re-acquire session lock before resuming
614
- const resumeLock = acquireSessionLock(base);
615
- if (!resumeLock.acquired) {
616
- ctx.ui.notify(
617
- `Cannot resume: ${resumeLock.reason}`,
618
- "error",
619
- );
620
- return;
621
- }
622
-
623
601
  s.paused = false;
624
602
  s.active = true;
625
603
  s.verbose = verboseMode;
@@ -721,7 +699,6 @@ export async function startAuto(
721
699
  s.pausedForSecrets = false;
722
700
  }
723
701
 
724
- updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
725
702
  writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
726
703
 
727
704
  await dispatchNextUnit(ctx, pi);
@@ -973,24 +950,6 @@ async function dispatchNextUnit(
973
950
  return;
974
951
  }
975
952
 
976
- // ── Session lock validation: detect if another process has taken over ──
977
- if (lockBase() && !validateSessionLock(lockBase())) {
978
- debugLog("dispatchNextUnit session-lock-lost — another process may have taken over");
979
- ctx.ui.notify(
980
- "Session lock lost — another GSD process appears to have taken over. Stopping gracefully.",
981
- "error",
982
- );
983
- // Don't call stopAuto here to avoid releasing the lock we don't own
984
- s.active = false;
985
- s.paused = false;
986
- clearUnitTimeout();
987
- deregisterSigtermHandler();
988
- ctx.ui.setStatus("gsd-auto", undefined);
989
- ctx.ui.setWidget("gsd-progress", undefined);
990
- ctx.ui.setFooter(undefined);
991
- return;
992
- }
993
-
994
953
  // Reentrancy guard
995
954
  if (s.dispatching && s.skipDepth === 0) {
996
955
  debugLog("dispatchNextUnit reentrancy guard — another dispatch in progress, bailing");
@@ -1624,7 +1583,6 @@ async function dispatchNextUnit(
1624
1583
  }
1625
1584
 
1626
1585
  const sessionFile = ctx.sessionManager.getSessionFile();
1627
- updateSessionLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
1628
1586
  writeLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
1629
1587
 
1630
1588
  // Prompt injection
@@ -1851,7 +1809,6 @@ export async function dispatchHookUnit(
1851
1809
  }
1852
1810
 
1853
1811
  const sessionFile = ctx.sessionManager.getSessionFile();
1854
- updateSessionLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
1855
1812
  writeLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
1856
1813
 
1857
1814
  clearUnitTimeout();
@@ -43,7 +43,6 @@ import { handleConfig } from "./commands-config.js";
43
43
  import { handleInspect } from "./commands-inspect.js";
44
44
  import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleDryRun } from "./commands-maintenance.js";
45
45
  import { handleDoctor, handleSteer, handleCapture, handleTriage, handleKnowledge, handleRunHook, handleUpdate, handleSkillHealth } from "./commands-handlers.js";
46
- import { handleLogs } from "./commands-logs.js";
47
46
 
48
47
  // ─── Re-exports (preserve public API surface) ───────────────────────────────
49
48
  export { handlePrefs, handlePrefsMode, handlePrefsWizard, ensurePreferencesFile, handleImportClaude, buildCategorySummaries, serializePreferencesToFrontmatter, yamlSafeString, configureMode } from "./commands-prefs-wizard.js";
@@ -108,7 +107,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
108
107
  { cmd: "run-hook", desc: "Manually trigger a specific hook" },
109
108
  { cmd: "skill-health", desc: "Skill lifecycle dashboard" },
110
109
  { cmd: "doctor", desc: "Runtime health checks with auto-fix" },
111
- { cmd: "logs", desc: "Browse activity logs, debug logs, and metrics" },
112
110
  { cmd: "forensics", desc: "Examine execution logs" },
113
111
  { cmd: "init", desc: "Project init wizard — detect, configure, bootstrap .gsd/" },
114
112
  { cmd: "setup", desc: "Global setup status and configuration" },
@@ -186,18 +184,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
186
184
  .map((s) => ({ value: `setup ${s.cmd}`, label: s.cmd, description: s.desc }));
187
185
  }
188
186
 
189
- if (parts[0] === "logs" && parts.length <= 2) {
190
- const subPrefix = parts[1] ?? "";
191
- const subs = [
192
- { cmd: "debug", desc: "List or view debug log files" },
193
- { cmd: "tail", desc: "Show last N activity log summaries" },
194
- { cmd: "clear", desc: "Remove old activity and debug logs" },
195
- ];
196
- return subs
197
- .filter((s) => s.cmd.startsWith(subPrefix))
198
- .map((s) => ({ value: `logs ${s.cmd}`, label: s.cmd, description: s.desc }));
199
- }
200
-
201
187
  if (parts[0] === "keys" && parts.length <= 2) {
202
188
  const subPrefix = parts[1] ?? "";
203
189
  const subs = [
@@ -406,11 +392,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
406
392
  return;
407
393
  }
408
394
 
409
- if (trimmed === "logs" || trimmed.startsWith("logs ")) {
410
- await handleLogs(trimmed.replace(/^logs\s*/, "").trim(), ctx);
411
- return;
412
- }
413
-
414
395
  if (trimmed === "forensics" || trimmed.startsWith("forensics ")) {
415
396
  const { handleForensics } = await import("./forensics.js");
416
397
  await handleForensics(trimmed.replace(/^forensics\s*/, "").trim(), ctx, pi);
@@ -32,19 +32,6 @@ export type DoctorIssueCode =
32
32
  | "gitignore_missing_patterns"
33
33
  | "unresolvable_dependency";
34
34
 
35
- /**
36
- * Issue codes that represent expected completion-transition states.
37
- * These are detected by the doctor but should NOT be auto-fixed at task level —
38
- * they are resolved by the complete-slice/complete-milestone dispatch units.
39
- * Consumers (e.g. auto-post-unit health tracking) should exclude these from
40
- * error counts when running at task fixLevel to avoid false escalation.
41
- */
42
- export const COMPLETION_TRANSITION_CODES = new Set<DoctorIssueCode>([
43
- "all_tasks_done_missing_slice_summary",
44
- "all_tasks_done_missing_slice_uat",
45
- "all_tasks_done_roadmap_not_checked",
46
- ]);
47
-
48
35
  export interface DoctorIssue {
49
36
  severity: DoctorSeverity;
50
37
  code: DoctorIssueCode;
@@ -8,7 +8,6 @@ import { invalidateAllCaches } from "./cache.js";
8
8
  import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
9
9
 
10
10
  import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
11
- import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
12
11
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
13
12
 
14
13
  // ── Re-exports ─────────────────────────────────────────────────────────────
@@ -357,11 +356,16 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
357
356
  // dispatch lifecycle (complete-slice, complete-milestone units), not to
358
357
  // mechanical post-hook bookkeeping. When fixLevel is "task", these are
359
358
  // detected and reported but never auto-fixed.
359
+ const completionTransitionCodes = new Set<DoctorIssueCode>([
360
+ "all_tasks_done_missing_slice_summary",
361
+ "all_tasks_done_missing_slice_uat",
362
+ "all_tasks_done_roadmap_not_checked",
363
+ ]);
360
364
 
361
365
  /** Whether a given issue code should be auto-fixed at the current fixLevel. */
362
366
  const shouldFix = (code: DoctorIssueCode): boolean => {
363
367
  if (!fix) return false;
364
- if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code)) return false;
368
+ if (fixLevel === "task" && completionTransitionCodes.has(code)) return false;
365
369
  return true;
366
370
  };
367
371
 
@@ -348,8 +348,6 @@ function migrateSchema(db: DbAdapter): void {
348
348
 
349
349
  let currentDb: DbAdapter | null = null;
350
350
  let currentPath: string | null = null;
351
- /** PID that opened the current connection — used for diagnostic logging. */
352
- let currentPid: number = 0;
353
351
 
354
352
  // ─── Public API ────────────────────────────────────────────────────────────
355
353
 
@@ -397,7 +395,6 @@ export function openDatabase(path: string): boolean {
397
395
 
398
396
  currentDb = adapter;
399
397
  currentPath = path;
400
- currentPid = process.pid;
401
398
  return true;
402
399
  }
403
400
 
@@ -413,7 +410,6 @@ export function closeDatabase(): void {
413
410
  }
414
411
  currentDb = null;
415
412
  currentPath = null;
416
- currentPid = 0;
417
413
  }
418
414
  }
419
415
 
@@ -728,21 +724,6 @@ export function reconcileWorktreeDb(
728
724
  }
729
725
  }
730
726
 
731
- /**
732
- * Returns the PID of the process that opened the current DB connection.
733
- * Returns 0 if no connection is open.
734
- */
735
- export function getDbOwnerPid(): number {
736
- return currentPid;
737
- }
738
-
739
- /**
740
- * Returns the path of the currently open database, or null if none.
741
- */
742
- export function getDbPath(): string | null {
743
- return currentPath;
744
- }
745
-
746
727
  // ─── Internal Access (for testing) ─────────────────────────────────────────
747
728
 
748
729
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-pi",
3
- "version": "2.28.0-dev.4009980",
3
+ "version": "2.28.0-dev.e19bf89",
4
4
  "description": "GSD — Get Shit Done coding agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,6 @@ import {
35
35
  import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.js";
36
36
  import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences } from "./preferences.js";
37
37
  import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
38
- import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
39
38
  import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
40
39
  import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
41
40
  import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
@@ -155,17 +154,13 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
155
154
  ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
156
155
  }
157
156
 
158
- // Proactive health tracking — exclude completion-transition codes at task level
159
- // since they are expected after the last task and resolved by complete-slice
160
- const issuesForHealth = effectiveFixLevel === "task"
161
- ? report.issues.filter(i => !COMPLETION_TRANSITION_CODES.has(i.code))
162
- : report.issues;
163
- const summary = summarizeDoctorIssues(issuesForHealth);
157
+ // Proactive health tracking
158
+ const summary = summarizeDoctorIssues(report.issues);
164
159
  recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
165
160
 
166
161
  // Check if we should escalate to LLM-assisted heal
167
162
  if (summary.errors > 0) {
168
- const unresolvedErrors = issuesForHealth
163
+ const unresolvedErrors = report.issues
169
164
  .filter(i => i.severity === "error" && !i.fixable)
170
165
  .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
171
166
  const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
@@ -26,13 +26,6 @@ import {
26
26
  import { invalidateAllCaches } from "./cache.js";
27
27
  import { synthesizeCrashRecovery } from "./session-forensics.js";
28
28
  import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
29
- import {
30
- acquireSessionLock,
31
- updateSessionLock,
32
- releaseSessionLock,
33
- readSessionLockData,
34
- isSessionLockProcessAlive,
35
- } from "./session-lock.js";
36
29
  import { selfHealRuntimeRecords } from "./auto-recovery.js";
37
30
  import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
38
31
  import { nativeIsRepo, nativeInit, nativeAddAll, nativeCommit } from "./native-git-bridge.js";
@@ -88,18 +81,6 @@ export async function bootstrapAutoSession(
88
81
  ): Promise<boolean> {
89
82
  const { shouldUseWorktreeIsolation, registerSigtermHandler, lockBase } = deps;
90
83
 
91
- // ── Session lock: acquire FIRST, before any state mutation ──────────────
92
- // This is the primary guard against concurrent sessions on the same project.
93
- // Uses OS-level file locking (proper-lockfile) to prevent TOCTOU races.
94
- const lockResult = acquireSessionLock(base);
95
- if (!lockResult.acquired) {
96
- ctx.ui.notify(
97
- `${lockResult.reason}\nStop it with \`kill ${lockResult.existingPid ?? "the other process"}\` before starting a new session.`,
98
- "error",
99
- );
100
- return false;
101
- }
102
-
103
84
  // Ensure git repo exists
104
85
  if (!nativeIsRepo(base)) {
105
86
  const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
@@ -128,11 +109,16 @@ export async function bootstrapAutoSession(
128
109
  // Initialize GitServiceImpl
129
110
  s.gitService = new GitServiceImpl(s.basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
130
111
 
131
- // Check for crash from previous session (use both old and new lock data)
112
+ // Check for crash from previous session
132
113
  const crashLock = readCrashLock(base);
133
114
  if (crashLock) {
134
- // We already hold the session lock, so no concurrent session is running.
135
- // The crash lock is from a dead process — recover context from it.
115
+ if (isLockProcessAlive(crashLock)) {
116
+ ctx.ui.notify(
117
+ `Another auto-mode session (PID ${crashLock.pid}) appears to be running.\nStop it with \`kill ${crashLock.pid}\` before starting a new session.`,
118
+ "error",
119
+ );
120
+ return false;
121
+ }
136
122
  const recoveredMid = crashLock.unitId.split("/")[0];
137
123
  const milestoneAlreadyComplete = recoveredMid
138
124
  ? !!resolveMilestoneFile(base, recoveredMid, "SUMMARY")
@@ -421,8 +407,7 @@ export async function bootstrapAutoSession(
421
407
  : "Will loop until milestone complete.";
422
408
  ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
423
409
 
424
- // Update lock file with milestone info (OS lock already acquired at bootstrap start)
425
- updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
410
+ // Write initial lock file
426
411
  writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
427
412
 
428
413
  // Secrets collection gate — pause instead of blocking (#1146)
@@ -33,12 +33,6 @@ import { invalidateAllCaches } from "./cache.js";
33
33
  import { saveActivityLog, clearActivityLogState } from "./activity-log.js";
34
34
  import { synthesizeCrashRecovery, getDeepDiagnostic } from "./session-forensics.js";
35
35
  import { writeLock, clearLock, readCrashLock, formatCrashInfo, isLockProcessAlive } from "./crash-recovery.js";
36
- import {
37
- acquireSessionLock,
38
- validateSessionLock,
39
- releaseSessionLock,
40
- updateSessionLock,
41
- } from "./session-lock.js";
42
36
  import {
43
37
  clearUnitRuntimeRecord,
44
38
  inspectExecuteTaskDurability,
@@ -457,10 +451,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
457
451
  if (!s.active && !s.paused) return;
458
452
  const reasonSuffix = reason ? ` — ${reason}` : "";
459
453
  clearUnitTimeout();
460
- if (lockBase()) {
461
- releaseSessionLock(lockBase());
462
- clearLock(lockBase());
463
- }
454
+ if (lockBase()) clearLock(lockBase());
464
455
  clearSkillSnapshot();
465
456
  resetSkillTelemetry();
466
457
  s.dispatching = false;
@@ -574,10 +565,7 @@ export async function pauseAuto(ctx?: ExtensionContext, _pi?: ExtensionAPI): Pro
574
565
 
575
566
  s.pausedSessionFile = ctx?.sessionManager?.getSessionFile() ?? null;
576
567
 
577
- if (lockBase()) {
578
- releaseSessionLock(lockBase());
579
- clearLock(lockBase());
580
- }
568
+ if (lockBase()) clearLock(lockBase());
581
569
 
582
570
  deregisterSigtermHandler();
583
571
 
@@ -610,16 +598,6 @@ export async function startAuto(
610
598
 
611
599
  // If resuming from paused state, just re-activate and dispatch next unit.
612
600
  if (s.paused) {
613
- // Re-acquire session lock before resuming
614
- const resumeLock = acquireSessionLock(base);
615
- if (!resumeLock.acquired) {
616
- ctx.ui.notify(
617
- `Cannot resume: ${resumeLock.reason}`,
618
- "error",
619
- );
620
- return;
621
- }
622
-
623
601
  s.paused = false;
624
602
  s.active = true;
625
603
  s.verbose = verboseMode;
@@ -721,7 +699,6 @@ export async function startAuto(
721
699
  s.pausedForSecrets = false;
722
700
  }
723
701
 
724
- updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
725
702
  writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
726
703
 
727
704
  await dispatchNextUnit(ctx, pi);
@@ -973,24 +950,6 @@ async function dispatchNextUnit(
973
950
  return;
974
951
  }
975
952
 
976
- // ── Session lock validation: detect if another process has taken over ──
977
- if (lockBase() && !validateSessionLock(lockBase())) {
978
- debugLog("dispatchNextUnit session-lock-lost — another process may have taken over");
979
- ctx.ui.notify(
980
- "Session lock lost — another GSD process appears to have taken over. Stopping gracefully.",
981
- "error",
982
- );
983
- // Don't call stopAuto here to avoid releasing the lock we don't own
984
- s.active = false;
985
- s.paused = false;
986
- clearUnitTimeout();
987
- deregisterSigtermHandler();
988
- ctx.ui.setStatus("gsd-auto", undefined);
989
- ctx.ui.setWidget("gsd-progress", undefined);
990
- ctx.ui.setFooter(undefined);
991
- return;
992
- }
993
-
994
953
  // Reentrancy guard
995
954
  if (s.dispatching && s.skipDepth === 0) {
996
955
  debugLog("dispatchNextUnit reentrancy guard — another dispatch in progress, bailing");
@@ -1624,7 +1583,6 @@ async function dispatchNextUnit(
1624
1583
  }
1625
1584
 
1626
1585
  const sessionFile = ctx.sessionManager.getSessionFile();
1627
- updateSessionLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
1628
1586
  writeLock(lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
1629
1587
 
1630
1588
  // Prompt injection
@@ -1851,7 +1809,6 @@ export async function dispatchHookUnit(
1851
1809
  }
1852
1810
 
1853
1811
  const sessionFile = ctx.sessionManager.getSessionFile();
1854
- updateSessionLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
1855
1812
  writeLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
1856
1813
 
1857
1814
  clearUnitTimeout();
@@ -43,7 +43,6 @@ import { handleConfig } from "./commands-config.js";
43
43
  import { handleInspect } from "./commands-inspect.js";
44
44
  import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleDryRun } from "./commands-maintenance.js";
45
45
  import { handleDoctor, handleSteer, handleCapture, handleTriage, handleKnowledge, handleRunHook, handleUpdate, handleSkillHealth } from "./commands-handlers.js";
46
- import { handleLogs } from "./commands-logs.js";
47
46
 
48
47
  // ─── Re-exports (preserve public API surface) ───────────────────────────────
49
48
  export { handlePrefs, handlePrefsMode, handlePrefsWizard, ensurePreferencesFile, handleImportClaude, buildCategorySummaries, serializePreferencesToFrontmatter, yamlSafeString, configureMode } from "./commands-prefs-wizard.js";
@@ -108,7 +107,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
108
107
  { cmd: "run-hook", desc: "Manually trigger a specific hook" },
109
108
  { cmd: "skill-health", desc: "Skill lifecycle dashboard" },
110
109
  { cmd: "doctor", desc: "Runtime health checks with auto-fix" },
111
- { cmd: "logs", desc: "Browse activity logs, debug logs, and metrics" },
112
110
  { cmd: "forensics", desc: "Examine execution logs" },
113
111
  { cmd: "init", desc: "Project init wizard — detect, configure, bootstrap .gsd/" },
114
112
  { cmd: "setup", desc: "Global setup status and configuration" },
@@ -186,18 +184,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
186
184
  .map((s) => ({ value: `setup ${s.cmd}`, label: s.cmd, description: s.desc }));
187
185
  }
188
186
 
189
- if (parts[0] === "logs" && parts.length <= 2) {
190
- const subPrefix = parts[1] ?? "";
191
- const subs = [
192
- { cmd: "debug", desc: "List or view debug log files" },
193
- { cmd: "tail", desc: "Show last N activity log summaries" },
194
- { cmd: "clear", desc: "Remove old activity and debug logs" },
195
- ];
196
- return subs
197
- .filter((s) => s.cmd.startsWith(subPrefix))
198
- .map((s) => ({ value: `logs ${s.cmd}`, label: s.cmd, description: s.desc }));
199
- }
200
-
201
187
  if (parts[0] === "keys" && parts.length <= 2) {
202
188
  const subPrefix = parts[1] ?? "";
203
189
  const subs = [
@@ -406,11 +392,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
406
392
  return;
407
393
  }
408
394
 
409
- if (trimmed === "logs" || trimmed.startsWith("logs ")) {
410
- await handleLogs(trimmed.replace(/^logs\s*/, "").trim(), ctx);
411
- return;
412
- }
413
-
414
395
  if (trimmed === "forensics" || trimmed.startsWith("forensics ")) {
415
396
  const { handleForensics } = await import("./forensics.js");
416
397
  await handleForensics(trimmed.replace(/^forensics\s*/, "").trim(), ctx, pi);
@@ -32,19 +32,6 @@ export type DoctorIssueCode =
32
32
  | "gitignore_missing_patterns"
33
33
  | "unresolvable_dependency";
34
34
 
35
- /**
36
- * Issue codes that represent expected completion-transition states.
37
- * These are detected by the doctor but should NOT be auto-fixed at task level —
38
- * they are resolved by the complete-slice/complete-milestone dispatch units.
39
- * Consumers (e.g. auto-post-unit health tracking) should exclude these from
40
- * error counts when running at task fixLevel to avoid false escalation.
41
- */
42
- export const COMPLETION_TRANSITION_CODES = new Set<DoctorIssueCode>([
43
- "all_tasks_done_missing_slice_summary",
44
- "all_tasks_done_missing_slice_uat",
45
- "all_tasks_done_roadmap_not_checked",
46
- ]);
47
-
48
35
  export interface DoctorIssue {
49
36
  severity: DoctorSeverity;
50
37
  code: DoctorIssueCode;
@@ -8,7 +8,6 @@ import { invalidateAllCaches } from "./cache.js";
8
8
  import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
9
9
 
10
10
  import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
11
- import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
12
11
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
13
12
 
14
13
  // ── Re-exports ─────────────────────────────────────────────────────────────
@@ -357,11 +356,16 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
357
356
  // dispatch lifecycle (complete-slice, complete-milestone units), not to
358
357
  // mechanical post-hook bookkeeping. When fixLevel is "task", these are
359
358
  // detected and reported but never auto-fixed.
359
+ const completionTransitionCodes = new Set<DoctorIssueCode>([
360
+ "all_tasks_done_missing_slice_summary",
361
+ "all_tasks_done_missing_slice_uat",
362
+ "all_tasks_done_roadmap_not_checked",
363
+ ]);
360
364
 
361
365
  /** Whether a given issue code should be auto-fixed at the current fixLevel. */
362
366
  const shouldFix = (code: DoctorIssueCode): boolean => {
363
367
  if (!fix) return false;
364
- if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code)) return false;
368
+ if (fixLevel === "task" && completionTransitionCodes.has(code)) return false;
365
369
  return true;
366
370
  };
367
371