gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27
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 +5 -7
- package/dist/help-text.js +1 -1
- package/dist/resource-loader.js +6 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
- package/dist/resources/extensions/gsd/auto/loop.js +235 -36
- package/dist/resources/extensions/gsd/auto/phases.js +14 -7
- package/dist/resources/extensions/gsd/auto/session.js +36 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
- package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
- package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
- package/dist/resources/extensions/gsd/auto.js +139 -49
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
- package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
- package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
- package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
- package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
- package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
- package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
- package/dist/resources/extensions/gsd/db-writer.js +96 -16
- package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
- package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
- package/dist/resources/extensions/gsd/doctor.js +12 -2
- package/dist/resources/extensions/gsd/gsd-db.js +355 -3
- package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
- package/dist/resources/extensions/gsd/guided-flow.js +116 -26
- package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
- package/dist/resources/extensions/gsd/metrics.js +287 -1
- package/dist/resources/extensions/gsd/paths.js +79 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/dist/resources/extensions/gsd/state.js +21 -6
- package/dist/resources/extensions/gsd/templates/project.md +10 -0
- package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
- package/dist/resources/extensions/gsd/workspace.js +59 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
- package/dist/resources/extensions/gsd/write-intercept.js +3 -3
- 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 +14 -14
- 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 +14 -14
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- 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/mcp-server/README.md +2 -11
- package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +28 -0
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +28 -0
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +94 -4
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/mcp-server.test.ts +226 -0
- package/packages/mcp-server/src/remote-questions.test.ts +103 -0
- package/packages/mcp-server/src/remote-questions.ts +35 -0
- package/packages/mcp-server/src/server.ts +129 -6
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
- package/src/resources/extensions/gsd/auto/loop.ts +263 -41
- package/src/resources/extensions/gsd/auto/phases.ts +15 -7
- package/src/resources/extensions/gsd/auto/session.ts +40 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
- package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
- package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
- package/src/resources/extensions/gsd/auto.ts +166 -43
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
- package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
- package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
- package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
- package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
- package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
- package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
- package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
- package/src/resources/extensions/gsd/db-writer.ts +113 -17
- package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
- package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
- package/src/resources/extensions/gsd/doctor.ts +10 -2
- package/src/resources/extensions/gsd/gsd-db.ts +354 -3
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
- package/src/resources/extensions/gsd/guided-flow.ts +152 -26
- package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
- package/src/resources/extensions/gsd/metrics.ts +321 -1
- package/src/resources/extensions/gsd/paths.ts +67 -8
- package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
- package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
- package/src/resources/extensions/gsd/state.ts +44 -6
- package/src/resources/extensions/gsd/templates/project.md +10 -0
- package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
- package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
- package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
- package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
- package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
- package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
- package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
- package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
- package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
- package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
- package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
- package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
- package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
- package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
- package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
- package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
- package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
- package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
- package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
- package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
- package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
- package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
- package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
- package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
- package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
- package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
- package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
- package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
- package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
- package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
- package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
- package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
- package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
- package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
- package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
- package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
- package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
- package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
- package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
- package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
- package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
- package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
- package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
- package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
- package/src/resources/extensions/gsd/workspace.ts +95 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
- package/src/resources/extensions/gsd/write-intercept.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
- package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
- package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
|
@@ -55,19 +55,31 @@ const QUEUE_SAFE_TOOLS = new Set([
|
|
|
55
55
|
* true / false — shell no-ops / test exit codes
|
|
56
56
|
*/
|
|
57
57
|
const BASH_READ_ONLY_RE = /^\s*(cat|head|tail|less|more|wc|file|stat|du|df|which|type|echo|printf|ls|find|grep|rg|awk|sed\b(?!.*-i)|sort|uniq|diff|comm|tr|cut|tee\s+-a\s+\/dev\/null|git\s+(log|show|diff|status|branch|tag|remote|rev-parse|ls-files|blame|shortlog|describe|stash\s+list|config\s+--get|cat-file)|gh\s+(issue|pr|api|repo|release)\s+(view|list|diff|status|checks)|mkdir\s+-p\s+\.gsd|rtk\s|npm\s+run\s+(test|test:\w+|lint|lint:\w+|typecheck|type-check|type-check:\w+|check|verify|audit|outdated|format:check|ci|validate)\b|npm\s+(ls|list|info|view|show|outdated|audit|explain|doctor|ping|--version|-v)\b|npx\s|tsx\s|node\s+(--print|--version|-v\b)|python[23]?\s+(-c\s+'[^']*'|--version|-V\b|-m\s+(pip\s+show|pip\s+list|site))|pip[23]?\s+(show|list|freeze|check|index\s+versions)\b|jq\s|yq\s|curl\s+(-s\b|--silent\b)(?!\s+[^|>]*\s-[oO]\b)(?!\s+[^|>]*\s--output\b)[^|>]*$|openssl\s+(version|x509|s_client)|env\b|printenv\b|true\b|false\b)/;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
function createEmptyWriteGateState() {
|
|
59
|
+
return {
|
|
60
|
+
verifiedDepthMilestones: new Set(),
|
|
61
|
+
verifiedApprovalGates: new Set(),
|
|
62
|
+
activeQueuePhase: false,
|
|
63
|
+
pendingGateId: null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const writeGateStatesByBasePath = new Map();
|
|
67
|
+
function writeGateStateKey(basePath) {
|
|
68
|
+
return resolve(basePath);
|
|
69
|
+
}
|
|
70
|
+
function getWriteGateState(basePath = process.cwd()) {
|
|
71
|
+
const key = writeGateStateKey(basePath);
|
|
72
|
+
let state = writeGateStatesByBasePath.get(key);
|
|
73
|
+
if (!state) {
|
|
74
|
+
state = createEmptyWriteGateState();
|
|
75
|
+
writeGateStatesByBasePath.set(key, state);
|
|
76
|
+
}
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
61
79
|
/**
|
|
62
|
-
* Discussion gate enforcement state
|
|
63
|
-
*
|
|
64
|
-
* When ask_user_questions is called with a recognized gate question ID,
|
|
65
|
-
* we track the pending gate. Until the gate is confirmed (user selects the
|
|
66
|
-
* first/recommended option), all non-read-only tool calls are blocked.
|
|
67
|
-
* This mechanically prevents the model from rationalizing past failed or
|
|
68
|
-
* cancelled gate questions.
|
|
80
|
+
* Discussion gate enforcement state is scoped per basePath so multiple
|
|
81
|
+
* workspaces can coexist in the same process without sharing gate state.
|
|
69
82
|
*/
|
|
70
|
-
let pendingGateId = null;
|
|
71
83
|
/**
|
|
72
84
|
* Recognized gate question ID patterns.
|
|
73
85
|
* These appear in discuss.md (depth/requirements/roadmap).
|
|
@@ -99,24 +111,25 @@ function shouldPersistWriteGateSnapshot(env = process.env) {
|
|
|
99
111
|
const v = env.GSD_PERSIST_WRITE_GATE_STATE;
|
|
100
112
|
return v !== "0" && v !== "false";
|
|
101
113
|
}
|
|
102
|
-
function writeGateSnapshotPath(basePath
|
|
114
|
+
function writeGateSnapshotPath(basePath) {
|
|
103
115
|
return join(basePath, ".gsd", "runtime", "write-gate-state.json");
|
|
104
116
|
}
|
|
105
|
-
function currentWriteGateSnapshot() {
|
|
117
|
+
function currentWriteGateSnapshot(basePath = process.cwd()) {
|
|
118
|
+
const state = getWriteGateState(basePath);
|
|
106
119
|
return {
|
|
107
|
-
verifiedDepthMilestones: [...verifiedDepthMilestones].sort(),
|
|
108
|
-
verifiedApprovalGates: [...verifiedApprovalGates].sort(),
|
|
109
|
-
activeQueuePhase,
|
|
110
|
-
pendingGateId,
|
|
120
|
+
verifiedDepthMilestones: [...state.verifiedDepthMilestones].sort(),
|
|
121
|
+
verifiedApprovalGates: [...state.verifiedApprovalGates].sort(),
|
|
122
|
+
activeQueuePhase: state.activeQueuePhase,
|
|
123
|
+
pendingGateId: state.pendingGateId,
|
|
111
124
|
};
|
|
112
125
|
}
|
|
113
|
-
function persistWriteGateSnapshot(basePath
|
|
126
|
+
function persistWriteGateSnapshot(basePath) {
|
|
114
127
|
if (!shouldPersistWriteGateSnapshot())
|
|
115
128
|
return;
|
|
116
129
|
const path = writeGateSnapshotPath(basePath);
|
|
117
130
|
mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
|
|
118
131
|
const tempPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
119
|
-
writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(), null, 2), "utf-8");
|
|
132
|
+
writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(basePath), null, 2), "utf-8");
|
|
120
133
|
try {
|
|
121
134
|
renameSync(tempPath, path);
|
|
122
135
|
}
|
|
@@ -132,7 +145,7 @@ function persistWriteGateSnapshot(basePath = process.cwd()) {
|
|
|
132
145
|
}
|
|
133
146
|
}
|
|
134
147
|
}
|
|
135
|
-
function clearPersistedWriteGateSnapshot(basePath
|
|
148
|
+
function clearPersistedWriteGateSnapshot(basePath) {
|
|
136
149
|
if (!shouldPersistWriteGateSnapshot())
|
|
137
150
|
return;
|
|
138
151
|
const path = writeGateSnapshotPath(basePath);
|
|
@@ -164,7 +177,7 @@ const EMPTY_SNAPSHOT = {
|
|
|
164
177
|
activeQueuePhase: false,
|
|
165
178
|
pendingGateId: null,
|
|
166
179
|
};
|
|
167
|
-
export function loadWriteGateSnapshot(basePath
|
|
180
|
+
export function loadWriteGateSnapshot(basePath) {
|
|
168
181
|
const path = writeGateSnapshotPath(basePath);
|
|
169
182
|
if (!existsSync(path)) {
|
|
170
183
|
// When persist mode is active and the file has been deleted, treat it as a
|
|
@@ -172,61 +185,59 @@ export function loadWriteGateSnapshot(basePath = process.cwd()) {
|
|
|
172
185
|
// In non-persist mode the file is never written, so fall back to in-memory.
|
|
173
186
|
if (shouldPersistWriteGateSnapshot())
|
|
174
187
|
return EMPTY_SNAPSHOT;
|
|
175
|
-
return currentWriteGateSnapshot();
|
|
188
|
+
return currentWriteGateSnapshot(basePath);
|
|
176
189
|
}
|
|
177
190
|
try {
|
|
178
191
|
return normalizeWriteGateSnapshot(JSON.parse(readFileSync(path, "utf-8")));
|
|
179
192
|
}
|
|
180
193
|
catch {
|
|
181
|
-
return currentWriteGateSnapshot();
|
|
194
|
+
return currentWriteGateSnapshot(basePath);
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
|
-
export function isDepthVerified() {
|
|
185
|
-
return verifiedDepthMilestones.size > 0;
|
|
197
|
+
export function isDepthVerified(basePath = process.cwd()) {
|
|
198
|
+
return getWriteGateState(basePath).verifiedDepthMilestones.size > 0;
|
|
186
199
|
}
|
|
187
200
|
/**
|
|
188
201
|
* Check whether a specific milestone has passed depth verification.
|
|
189
202
|
*/
|
|
190
|
-
export function isMilestoneDepthVerified(milestoneId) {
|
|
203
|
+
export function isMilestoneDepthVerified(milestoneId, basePath = process.cwd()) {
|
|
191
204
|
if (!milestoneId)
|
|
192
205
|
return false;
|
|
193
|
-
return verifiedDepthMilestones.has(milestoneId);
|
|
206
|
+
return getWriteGateState(basePath).verifiedDepthMilestones.has(milestoneId);
|
|
194
207
|
}
|
|
195
208
|
export function isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId) {
|
|
196
209
|
if (!milestoneId)
|
|
197
210
|
return false;
|
|
198
211
|
return snapshot.verifiedDepthMilestones.includes(milestoneId);
|
|
199
212
|
}
|
|
200
|
-
export function isQueuePhaseActive() {
|
|
201
|
-
return activeQueuePhase;
|
|
213
|
+
export function isQueuePhaseActive(basePath = process.cwd()) {
|
|
214
|
+
return getWriteGateState(basePath).activeQueuePhase;
|
|
202
215
|
}
|
|
203
|
-
export function setQueuePhaseActive(active) {
|
|
204
|
-
activeQueuePhase = active;
|
|
205
|
-
persistWriteGateSnapshot();
|
|
216
|
+
export function setQueuePhaseActive(active, basePath) {
|
|
217
|
+
getWriteGateState(basePath).activeQueuePhase = active;
|
|
218
|
+
persistWriteGateSnapshot(basePath);
|
|
206
219
|
}
|
|
207
|
-
export function resetWriteGateState() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
220
|
+
export function resetWriteGateState(basePath) {
|
|
221
|
+
const state = getWriteGateState(basePath);
|
|
222
|
+
state.verifiedDepthMilestones.clear();
|
|
223
|
+
state.verifiedApprovalGates.clear();
|
|
224
|
+
state.pendingGateId = null;
|
|
225
|
+
persistWriteGateSnapshot(basePath);
|
|
212
226
|
}
|
|
213
|
-
export function clearDiscussionFlowState() {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
activeQueuePhase = false;
|
|
217
|
-
pendingGateId = null;
|
|
218
|
-
clearPersistedWriteGateSnapshot();
|
|
227
|
+
export function clearDiscussionFlowState(basePath) {
|
|
228
|
+
writeGateStatesByBasePath.delete(writeGateStateKey(basePath));
|
|
229
|
+
clearPersistedWriteGateSnapshot(basePath);
|
|
219
230
|
}
|
|
220
231
|
export function markDepthVerified(milestoneId, basePath = process.cwd()) {
|
|
221
232
|
if (!milestoneId)
|
|
222
233
|
return;
|
|
223
|
-
verifiedDepthMilestones.add(milestoneId);
|
|
234
|
+
getWriteGateState(basePath).verifiedDepthMilestones.add(milestoneId);
|
|
224
235
|
persistWriteGateSnapshot(basePath);
|
|
225
236
|
}
|
|
226
237
|
export function markApprovalGateVerified(gateId, basePath = process.cwd()) {
|
|
227
238
|
if (!gateId)
|
|
228
239
|
return;
|
|
229
|
-
verifiedApprovalGates.add(gateId);
|
|
240
|
+
getWriteGateState(basePath).verifiedApprovalGates.add(gateId);
|
|
230
241
|
persistWriteGateSnapshot(basePath);
|
|
231
242
|
}
|
|
232
243
|
export function isApprovalGateVerifiedInSnapshot(snapshot, gateId) {
|
|
@@ -258,26 +269,27 @@ function extractContextMilestoneId(inputPath) {
|
|
|
258
269
|
/**
|
|
259
270
|
* Mark a gate as pending (called when ask_user_questions is invoked with a gate ID).
|
|
260
271
|
*/
|
|
261
|
-
export function setPendingGate(gateId) {
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
export function setPendingGate(gateId, basePath) {
|
|
273
|
+
const state = getWriteGateState(basePath);
|
|
274
|
+
state.pendingGateId = gateId;
|
|
275
|
+
state.verifiedApprovalGates.delete(gateId);
|
|
264
276
|
const milestoneId = extractDepthVerificationMilestoneId(gateId);
|
|
265
277
|
if (milestoneId)
|
|
266
|
-
verifiedDepthMilestones.delete(milestoneId);
|
|
267
|
-
persistWriteGateSnapshot();
|
|
278
|
+
state.verifiedDepthMilestones.delete(milestoneId);
|
|
279
|
+
persistWriteGateSnapshot(basePath);
|
|
268
280
|
}
|
|
269
281
|
/**
|
|
270
282
|
* Clear the pending gate (called when the user confirms).
|
|
271
283
|
*/
|
|
272
|
-
export function clearPendingGate() {
|
|
273
|
-
pendingGateId = null;
|
|
274
|
-
persistWriteGateSnapshot();
|
|
284
|
+
export function clearPendingGate(basePath) {
|
|
285
|
+
getWriteGateState(basePath).pendingGateId = null;
|
|
286
|
+
persistWriteGateSnapshot(basePath);
|
|
275
287
|
}
|
|
276
288
|
/**
|
|
277
289
|
* Get the currently pending gate, if any.
|
|
278
290
|
*/
|
|
279
|
-
export function getPendingGate() {
|
|
280
|
-
return pendingGateId;
|
|
291
|
+
export function getPendingGate(basePath = process.cwd()) {
|
|
292
|
+
return getWriteGateState(basePath).pendingGateId;
|
|
281
293
|
}
|
|
282
294
|
/**
|
|
283
295
|
* Check whether a tool call should be blocked because a discussion gate
|
|
@@ -1,24 +1,106 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GSD Crash Recovery
|
|
2
|
+
* GSD Crash Recovery (Phase C pt 2 — DB-backed)
|
|
3
3
|
*
|
|
4
|
-
* Detects interrupted auto-mode sessions via
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Detects interrupted auto-mode sessions via the DB-backed workers +
|
|
5
|
+
* unit_dispatches + runtime_kv tables. The auto.lock file is gone; the
|
|
6
|
+
* `LockData` shape is preserved for backward compatibility with callers
|
|
7
|
+
* (auto.ts, doctor checks, interrupted-session.ts), but the contents are
|
|
8
|
+
* now synthesized from:
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* - workers.pid / .started_at / .last_heartbeat_at → liveness + age
|
|
11
|
+
* - unit_dispatches.unit_type / .unit_id / .started_at → what was running
|
|
12
|
+
* - runtime_kv("worker", workerId, "session_file") → pi session JSONL path
|
|
13
|
+
*
|
|
14
|
+
* "Crashed" is detected via workers.status='active' + heartbeat past TTL,
|
|
15
|
+
* cross-checked with the OS PID via isLockProcessAlive(). When the DB is
|
|
16
|
+
* unavailable (fresh project before init), all readers return null and
|
|
17
|
+
* writers no-op — preserving the historical "no lock means no prior
|
|
18
|
+
* crash" semantics.
|
|
19
|
+
*
|
|
20
|
+
* The journal-based emitCrashRecoveredUnitEnd is unchanged from the file
|
|
21
|
+
* era — it queries the journal independently of the lock mechanism.
|
|
11
22
|
*/
|
|
23
|
+
import { emitJournalEvent, queryJournal, } from "./journal.js";
|
|
12
24
|
import { readFileSync, unlinkSync, existsSync } from "node:fs";
|
|
13
25
|
import { join } from "node:path";
|
|
14
|
-
import {
|
|
26
|
+
import { findStaleWorkerForProject, getAllAutoWorkers, } from "./db/auto-workers.js";
|
|
27
|
+
import { getRuntimeKv, setRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
28
|
+
import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
29
|
+
import { gsdRoot, normalizeRealPath } from "./paths.js";
|
|
15
30
|
import { atomicWriteSync } from "./atomic-write.js";
|
|
16
31
|
import { effectiveLockFile } from "./session-lock.js";
|
|
17
|
-
|
|
32
|
+
const SESSION_FILE_KV_KEY = "session_file";
|
|
18
33
|
function lockPath(basePath) {
|
|
19
34
|
return join(gsdRoot(basePath), effectiveLockFile());
|
|
20
35
|
}
|
|
21
|
-
|
|
36
|
+
function readLegacyLock(basePath) {
|
|
37
|
+
try {
|
|
38
|
+
const p = lockPath(basePath);
|
|
39
|
+
if (!existsSync(p))
|
|
40
|
+
return null;
|
|
41
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function findActiveWorkerForCurrentProcess(projectRootRealpath) {
|
|
48
|
+
if (!isDbAvailable())
|
|
49
|
+
return null;
|
|
50
|
+
const workers = getAllAutoWorkers();
|
|
51
|
+
for (const worker of workers) {
|
|
52
|
+
if (worker.pid === process.pid
|
|
53
|
+
&& worker.project_root_realpath === projectRootRealpath) {
|
|
54
|
+
return worker;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Look up the most recent dispatch row for a worker, regardless of status.
|
|
61
|
+
* Returns null if the worker has no dispatch history yet (e.g. crashed
|
|
62
|
+
* during bootstrap before claiming the first unit).
|
|
63
|
+
*/
|
|
64
|
+
function getLatestDispatchForWorker(workerId) {
|
|
65
|
+
if (!isDbAvailable())
|
|
66
|
+
return null;
|
|
67
|
+
const db = _getAdapter();
|
|
68
|
+
const row = db.prepare(`SELECT unit_type, unit_id, started_at, status
|
|
69
|
+
FROM unit_dispatches
|
|
70
|
+
WHERE worker_id = :worker_id
|
|
71
|
+
ORDER BY id DESC
|
|
72
|
+
LIMIT 1`).get({ ":worker_id": workerId });
|
|
73
|
+
return row ?? null;
|
|
74
|
+
}
|
|
75
|
+
function workerToLockData(worker) {
|
|
76
|
+
const dispatch = getLatestDispatchForWorker(worker.worker_id);
|
|
77
|
+
const sessionFile = getRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY) ?? undefined;
|
|
78
|
+
return {
|
|
79
|
+
pid: worker.pid,
|
|
80
|
+
startedAt: worker.started_at,
|
|
81
|
+
// Pre-Phase-C-pt-2 default: when no dispatch row exists yet (bootstrap
|
|
82
|
+
// crash), report unitType="starting", unitId="bootstrap" — same shape
|
|
83
|
+
// the file-based writer used to produce.
|
|
84
|
+
unitType: dispatch?.unit_type ?? "starting",
|
|
85
|
+
unitId: dispatch?.unit_id ?? "bootstrap",
|
|
86
|
+
unitStartedAt: dispatch?.started_at ?? worker.started_at,
|
|
87
|
+
sessionFile,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Write or update the lock state for the current auto-mode session.
|
|
92
|
+
*
|
|
93
|
+
* Phase C pt 2: the only persistent state this function adds beyond what
|
|
94
|
+
* the workers + unit_dispatches tables already track is the pi session
|
|
95
|
+
* JSONL path, which lands in runtime_kv (worker scope, key
|
|
96
|
+
* "session_file"). The pid/startedAt/unitType/unitId/unitStartedAt are
|
|
97
|
+
* recorded by registerAutoWorker / heartbeatAutoWorker / recordDispatchClaim
|
|
98
|
+
* already.
|
|
99
|
+
*
|
|
100
|
+
* basePath is unused by the new implementation (kept as a parameter for
|
|
101
|
+
* back-compat with the 15+ call sites) — the worker is identified by
|
|
102
|
+
* pid + project_root_realpath in the workers table.
|
|
103
|
+
*/
|
|
22
104
|
export function writeLock(basePath, unitType, unitId, sessionFile) {
|
|
23
105
|
try {
|
|
24
106
|
const data = {
|
|
@@ -29,51 +111,86 @@ export function writeLock(basePath, unitType, unitId, sessionFile) {
|
|
|
29
111
|
unitStartedAt: new Date().toISOString(),
|
|
30
112
|
sessionFile,
|
|
31
113
|
};
|
|
32
|
-
|
|
33
|
-
|
|
114
|
+
atomicWriteSync(lockPath(basePath), JSON.stringify(data, null, 2));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Best-effort — never throw from the lock writer.
|
|
118
|
+
}
|
|
119
|
+
if (!isDbAvailable() || !sessionFile)
|
|
120
|
+
return;
|
|
121
|
+
try {
|
|
122
|
+
const projectRoot = normalizeRealPath(basePath);
|
|
123
|
+
const worker = findActiveWorkerForCurrentProcess(projectRoot);
|
|
124
|
+
if (!worker)
|
|
125
|
+
return;
|
|
126
|
+
setRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY, sessionFile);
|
|
34
127
|
}
|
|
35
|
-
catch
|
|
36
|
-
|
|
128
|
+
catch {
|
|
129
|
+
// Best-effort — never throw from the lock writer.
|
|
37
130
|
}
|
|
38
131
|
}
|
|
39
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Phase C pt 2: clearLock no longer deletes a file. The cleanup path
|
|
134
|
+
* (markWorkerStopping in stopAuto) flips the workers row to 'stopping'.
|
|
135
|
+
* This function additionally drops the session_file runtime_kv row for
|
|
136
|
+
* the current worker so a follow-up crash detection doesn't pick up a
|
|
137
|
+
* stale session-file pointer.
|
|
138
|
+
*/
|
|
40
139
|
export function clearLock(basePath) {
|
|
41
140
|
try {
|
|
42
141
|
const p = lockPath(basePath);
|
|
43
142
|
if (existsSync(p))
|
|
44
143
|
unlinkSync(p);
|
|
45
144
|
}
|
|
46
|
-
catch
|
|
47
|
-
|
|
145
|
+
catch {
|
|
146
|
+
// Best-effort.
|
|
48
147
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
export function readCrashLock(basePath) {
|
|
148
|
+
if (!isDbAvailable())
|
|
149
|
+
return;
|
|
52
150
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
151
|
+
const projectRoot = normalizeRealPath(basePath);
|
|
152
|
+
const worker = findActiveWorkerForCurrentProcess(projectRoot);
|
|
153
|
+
if (!worker)
|
|
154
|
+
return;
|
|
155
|
+
deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
|
|
58
156
|
}
|
|
59
|
-
catch
|
|
60
|
-
|
|
61
|
-
|
|
157
|
+
catch {
|
|
158
|
+
// Best-effort.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Detect a previous crashed auto-mode session.
|
|
163
|
+
*
|
|
164
|
+
* Phase C pt 2: synthesized from workers (status='active' + lapsed
|
|
165
|
+
* heartbeat) + unit_dispatches (most recent for that worker) +
|
|
166
|
+
* runtime_kv (session_file). Returns null when no stale worker exists
|
|
167
|
+
* or the DB is unavailable.
|
|
168
|
+
*/
|
|
169
|
+
export function readCrashLock(basePath) {
|
|
170
|
+
if (isDbAvailable()) {
|
|
171
|
+
try {
|
|
172
|
+
const projectRoot = normalizeRealPath(basePath);
|
|
173
|
+
const stale = findStaleWorkerForProject(projectRoot);
|
|
174
|
+
if (stale)
|
|
175
|
+
return workerToLockData(stale);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Fall through to the legacy lock-file compatibility path.
|
|
179
|
+
}
|
|
62
180
|
}
|
|
181
|
+
return readLegacyLock(basePath);
|
|
63
182
|
}
|
|
64
183
|
/**
|
|
65
184
|
* Check whether the process that wrote the lock is still running.
|
|
66
185
|
* Uses `process.kill(pid, 0)` which sends no signal but checks liveness.
|
|
67
186
|
* Returns true if the PID matches our own — we are the lock holder (#2470).
|
|
187
|
+
*
|
|
188
|
+
* Unchanged from the file-based era — pure stateless OS check.
|
|
68
189
|
*/
|
|
69
190
|
export function isLockProcessAlive(lock) {
|
|
70
191
|
const pid = lock.pid;
|
|
71
192
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
72
193
|
return false;
|
|
73
|
-
// Our own PID means WE hold this lock — we are alive. (#2470)
|
|
74
|
-
// Callers that need to distinguish "our lock" from "someone else's lock"
|
|
75
|
-
// (e.g. startAuto checking for a prior crashed session with a recycled PID)
|
|
76
|
-
// already guard with `crashLock.pid !== process.pid` before calling us.
|
|
77
194
|
if (pid === process.pid)
|
|
78
195
|
return true;
|
|
79
196
|
try {
|
|
@@ -81,8 +198,6 @@ export function isLockProcessAlive(lock) {
|
|
|
81
198
|
return true;
|
|
82
199
|
}
|
|
83
200
|
catch (err) {
|
|
84
|
-
// EPERM means the process exists but we lack permission — treat as alive.
|
|
85
|
-
// ESRCH means the process does not exist — treat as dead (stale lock).
|
|
86
201
|
if (err.code === "EPERM")
|
|
87
202
|
return true;
|
|
88
203
|
return false;
|
|
@@ -96,7 +211,6 @@ export function formatCrashInfo(lock) {
|
|
|
96
211
|
` Started at: ${lock.unitStartedAt}`,
|
|
97
212
|
` PID: ${lock.pid}`,
|
|
98
213
|
];
|
|
99
|
-
// Add recovery guidance based on what was happening when it crashed
|
|
100
214
|
if (lock.unitType === "starting" && lock.unitId === "bootstrap") {
|
|
101
215
|
lines.push(`No work was lost. Run /gsd auto to restart.`);
|
|
102
216
|
}
|
|
@@ -113,33 +227,23 @@ export function formatCrashInfo(lock) {
|
|
|
113
227
|
}
|
|
114
228
|
/**
|
|
115
229
|
* Emit a synthetic unit-end event for a unit that crashed without emitting its own.
|
|
116
|
-
*
|
|
117
|
-
* Queries the journal to find the most recent unit-start for the crashed unit.
|
|
118
|
-
* If a matching unit-end already exists (e.g. the hard timeout fired), this is a
|
|
119
|
-
* no-op. Called during crash recovery, before clearing the stale lock.
|
|
120
|
-
*
|
|
121
|
-
* Addresses the gap reported in #3348 where `unit-start` was emitted but no
|
|
122
|
-
* `unit-end` followed — side effects landed but the worker died before closeout.
|
|
230
|
+
* Unchanged from the file era — operates on the journal, not the lock.
|
|
123
231
|
*/
|
|
124
232
|
export function emitCrashRecoveredUnitEnd(basePath, lock) {
|
|
125
|
-
// Skip bootstrap / starting pseudo-units — they have no meaningful unit-start event.
|
|
126
233
|
if (!lock.unitType || !lock.unitId || lock.unitType === "starting")
|
|
127
234
|
return;
|
|
128
235
|
try {
|
|
129
236
|
const all = queryJournal(basePath);
|
|
130
|
-
// Find the most recent unit-start for this unitId
|
|
131
237
|
const starts = all.filter((e) => e.eventType === "unit-start" && e.data?.unitId === lock.unitId);
|
|
132
238
|
if (starts.length === 0)
|
|
133
239
|
return;
|
|
134
240
|
const lastStart = starts[starts.length - 1];
|
|
135
|
-
// Check if a unit-end was already emitted (e.g. hard timeout fired after the crash)
|
|
136
241
|
const alreadyClosed = all.some((e) => e.eventType === "unit-end" &&
|
|
137
242
|
e.data?.unitId === lock.unitId &&
|
|
138
243
|
e.causedBy?.flowId === lastStart.flowId &&
|
|
139
244
|
e.causedBy?.seq === lastStart.seq);
|
|
140
245
|
if (alreadyClosed)
|
|
141
246
|
return;
|
|
142
|
-
// Find the highest seq in this flow for monotonic ordering
|
|
143
247
|
const maxSeq = all
|
|
144
248
|
.filter((e) => e.flowId === lastStart.flowId)
|
|
145
249
|
.reduce((max, e) => Math.max(max, e.seq), lastStart.seq);
|
|
@@ -158,6 +262,15 @@ export function emitCrashRecoveredUnitEnd(basePath, lock) {
|
|
|
158
262
|
});
|
|
159
263
|
}
|
|
160
264
|
catch {
|
|
161
|
-
// Never throw from crash recovery path
|
|
265
|
+
// Never throw from crash recovery path.
|
|
162
266
|
}
|
|
163
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Used by the doctor checks (doctor-runtime-checks.ts, doctor-proactive.ts)
|
|
270
|
+
* to enumerate stale workers across all projects this DB knows about.
|
|
271
|
+
* Phase C pt 2 export — surface for the same diagnostics that previously
|
|
272
|
+
* iterated `auto.lock` files.
|
|
273
|
+
*/
|
|
274
|
+
export function findStaleAutoWorker(basePath) {
|
|
275
|
+
return readCrashLock(basePath);
|
|
276
|
+
}
|