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
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { showNextAction } from "../shared/tui.js";
|
|
9
9
|
import { loadFile, saveFile } from "./files.js";
|
|
10
|
-
import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js";
|
|
10
|
+
import { isDbAvailable, getMilestone, getMilestoneSlices } from "./gsd-db.js";
|
|
11
11
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
12
12
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
13
13
|
import { buildCompleteSlicePrompt, buildDiscussMilestonePrompt, buildExecuteTaskPrompt, buildPlanMilestonePrompt, buildPlanSlicePrompt, buildSkillActivationBlock, } from "./auto-prompts.js";
|
|
14
|
-
import { deriveState } from "./state.js";
|
|
14
|
+
import { deriveState, isGhostMilestone } from "./state.js";
|
|
15
15
|
import { invalidateAllCaches } from "./cache.js";
|
|
16
16
|
import { startAutoDetached } from "./auto.js";
|
|
17
17
|
import { clearLock } from "./crash-recovery.js";
|
|
@@ -43,10 +43,41 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
|
|
|
43
43
|
import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForGuidedUnit, supportsStructuredQuestions, } from "./workflow-mcp.js";
|
|
44
44
|
import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, } from "./preparation.js";
|
|
45
45
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
46
|
+
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
46
47
|
// ─── Re-exports (preserve public API for existing importers) ────────────────
|
|
47
48
|
export { MILESTONE_ID_RE, generateMilestoneSuffix, nextMilestoneId, extractMilestoneSeq, parseMilestoneId, milestoneIdSort, maxMilestoneNum, findMilestoneIds, reserveMilestoneId, claimReservedId, getReservedMilestoneIds, clearReservedMilestoneIds, } from "./milestone-ids.js";
|
|
48
49
|
export { showQueue, handleQueueReorder, showQueueAdd, buildExistingMilestonesContext, } from "./guided-flow-queue.js";
|
|
49
50
|
import { logWarning } from "./workflow-logger.js";
|
|
51
|
+
import { deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
52
|
+
import { PAUSED_SESSION_KV_KEY } from "./interrupted-session.js";
|
|
53
|
+
// ─── Scope-based validator wrappers ──────────────────────────────────────────
|
|
54
|
+
// These thin wrappers accept a MilestoneScope so callers that already hold a
|
|
55
|
+
// pinned scope never have to re-derive (basePath, milestoneId) separately.
|
|
56
|
+
// The underlying implementations in auto-recovery.ts / auto-artifact-paths.ts /
|
|
57
|
+
// state.ts are unchanged — only the call surface in guided-flow.ts is migrated.
|
|
58
|
+
/**
|
|
59
|
+
* Scope-based overload of verifyExpectedArtifact.
|
|
60
|
+
* Uses scope.workspace.projectRoot as the authoritative base path, making
|
|
61
|
+
* the check immune to cwd-drift and worktree-path divergence.
|
|
62
|
+
*/
|
|
63
|
+
export function verifyExpectedArtifactForScope(scope, unitType, unitId) {
|
|
64
|
+
return verifyExpectedArtifact(unitType, unitId, scope.workspace.projectRoot);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Scope-based overload of resolveExpectedArtifactPath.
|
|
68
|
+
* Returns the canonical absolute path (or null) using the scope's projectRoot.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveExpectedArtifactPathForScope(scope, unitType, unitId) {
|
|
71
|
+
return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Scope-based overload of isGhostMilestone.
|
|
75
|
+
* Binds basePath and milestoneId from the scope, ensuring path resolution
|
|
76
|
+
* uses the canonical project root regardless of the cwd at call time.
|
|
77
|
+
*/
|
|
78
|
+
export function isGhostMilestoneByScope(scope) {
|
|
79
|
+
return isGhostMilestone(scope.workspace.projectRoot, scope.milestoneId);
|
|
80
|
+
}
|
|
50
81
|
function needsPlanV2Gate(state) {
|
|
51
82
|
return state.phase === "executing"
|
|
52
83
|
|| state.phase === "summarizing"
|
|
@@ -77,6 +108,10 @@ function buildDocsCommitInstruction(_message) {
|
|
|
77
108
|
// #4573: cap for how many times we nudge the LLM after a premature ready
|
|
78
109
|
// phrase before giving up and asking the user to re-run /gsd.
|
|
79
110
|
const MAX_READY_REJECTS = 2;
|
|
111
|
+
// H1 (#5012): cap for Gate 1b plan-blocked recovery hints. After this many
|
|
112
|
+
// consecutive recovery attempts the loop is stopped and the user is directed
|
|
113
|
+
// to investigate manually.
|
|
114
|
+
const MAX_PLAN_BLOCKED_RECOVERIES = 3;
|
|
80
115
|
// #4573: matches the canonical ready phrase the discuss prompt asks the LLM
|
|
81
116
|
// to emit. Accepts any M-prefixed milestone ID (three digits + optional
|
|
82
117
|
// suffix) with optional trailing punctuation.
|
|
@@ -110,9 +145,9 @@ This stage is running inside the foreground \`/gsd new-project --deep\` intervie
|
|
|
110
145
|
/**
|
|
111
146
|
* Backward-compat bridge: returns a mutable reference to the entry matching
|
|
112
147
|
* basePath, or the sole entry when only one session exists.
|
|
113
|
-
*
|
|
148
|
+
* Exported for testing — internal use only in production code.
|
|
114
149
|
*/
|
|
115
|
-
function _getPendingAutoStart(basePath) {
|
|
150
|
+
export function _getPendingAutoStart(basePath) {
|
|
116
151
|
if (basePath)
|
|
117
152
|
return pendingAutoStartMap.get(basePath) ?? null;
|
|
118
153
|
if (pendingAutoStartMap.size === 1)
|
|
@@ -157,7 +192,9 @@ function clearEmptyLegacyDeepSetupPseudoMilestones(basePath, entries) {
|
|
|
157
192
|
* Exported for testing (#2985).
|
|
158
193
|
*/
|
|
159
194
|
export function setPendingAutoStart(basePath, entry) {
|
|
160
|
-
|
|
195
|
+
const ws = createWorkspace(entry.basePath);
|
|
196
|
+
const scope = scopeMilestone(ws, entry.milestoneId);
|
|
197
|
+
pendingAutoStartMap.set(basePath, { createdAt: Date.now(), planBlockedRecoveryCount: 0, ...entry, scope });
|
|
161
198
|
}
|
|
162
199
|
/**
|
|
163
200
|
* Clear pending auto-start state.
|
|
@@ -249,6 +286,10 @@ export async function checkDeepProjectSetupAfterTurn(_event, ctx, basePath) {
|
|
|
249
286
|
if (!entry)
|
|
250
287
|
return false;
|
|
251
288
|
if (entry.currentUnitType && entry.currentUnitId) {
|
|
289
|
+
// TODO(C-future): PendingDeepProjectSetupEntry does not carry a MilestoneScope
|
|
290
|
+
// because deep-project-setup units span non-milestone unit types (discuss-project,
|
|
291
|
+
// discuss-requirements, etc.). Migrate to verifyExpectedArtifactForScope once
|
|
292
|
+
// PendingDeepProjectSetupEntry is extended with a scope field.
|
|
252
293
|
const artifactReady = verifyExpectedArtifact(entry.currentUnitType, entry.currentUnitId, entry.basePath);
|
|
253
294
|
if (!artifactReady) {
|
|
254
295
|
return false;
|
|
@@ -320,17 +361,61 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
320
361
|
const { ctx, pi, basePath, milestoneId, step } = entry;
|
|
321
362
|
// Gate 1: Primary milestone must have CONTEXT.md or ROADMAP.md
|
|
322
363
|
// The "discuss" path creates CONTEXT.md; the "plan" path creates ROADMAP.md.
|
|
323
|
-
|
|
324
|
-
const
|
|
364
|
+
// Use pinned scope (immune to cwd-drift) for existence checks.
|
|
365
|
+
const contextFilePath = entry.scope.contextFile();
|
|
366
|
+
const roadmapFilePath = entry.scope.roadmapFile();
|
|
367
|
+
const contextFile = existsSync(contextFilePath) ? contextFilePath : null;
|
|
368
|
+
const roadmapFile = existsSync(roadmapFilePath) ? roadmapFilePath : null;
|
|
325
369
|
if (!contextFile && !roadmapFile)
|
|
326
370
|
return false; // neither artifact yet — keep waiting
|
|
371
|
+
// Gate 1b: Discriminate plan-blocked from discuss-incomplete when the DB row is queued.
|
|
372
|
+
// If the DB is available and the row is still "queued" but CONTEXT.md already exists on
|
|
373
|
+
// disk, the discuss phase completed but gsd_plan_milestone was hard-blocked by the
|
|
374
|
+
// depth-verification gate. Emit a recovery hint so the next agent turn can retry
|
|
375
|
+
// gsd_plan_milestone, then return false (keep blocking auto-start).
|
|
376
|
+
// If CONTEXT.md does not exist (discuss-incomplete), Gate 1 already blocked above.
|
|
377
|
+
if (isDbAvailable()) {
|
|
378
|
+
const dbRow = getMilestone(milestoneId);
|
|
379
|
+
if (dbRow?.status === "queued" && contextFile) {
|
|
380
|
+
if (entry.planBlockedRecoveryCount >= MAX_PLAN_BLOCKED_RECOVERIES) {
|
|
381
|
+
// H1: recovery loop cap reached — stop triggering new turns, escalate to user.
|
|
382
|
+
logWarning("guided", `Gate 1b: milestone ${milestoneId} plan-blocked recovery limit reached ` +
|
|
383
|
+
`(${entry.planBlockedRecoveryCount}/${MAX_PLAN_BLOCKED_RECOVERIES}); escalating to user`);
|
|
384
|
+
ctx.ui.notify(`Milestone ${milestoneId} plan_milestone has been blocked ${entry.planBlockedRecoveryCount} times. ` +
|
|
385
|
+
`Re-run /gsd to reset the recovery counter, or run /gsd-debug to diagnose without resetting.`, "error");
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
logWarning("guided", `Gate 1b: milestone ${milestoneId} queued with CONTEXT.md present — ` +
|
|
389
|
+
`plan_milestone was blocked; emitting recovery hint ` +
|
|
390
|
+
`(attempt ${entry.planBlockedRecoveryCount + 1}/${MAX_PLAN_BLOCKED_RECOVERIES})`);
|
|
391
|
+
ctx.ui.notify(`Milestone ${milestoneId}: context file exists but milestone is still queued. ` +
|
|
392
|
+
`Retrying gsd_plan_milestone to complete the blocked planning step.`, "warning");
|
|
393
|
+
try {
|
|
394
|
+
pi.sendMessage({
|
|
395
|
+
customType: "gsd-plan-milestone-blocked-recovery",
|
|
396
|
+
content: `Milestone ${milestoneId} has ${contextFile} on disk but its DB row is still ` +
|
|
397
|
+
`"queued". The gsd_plan_milestone tool was previously blocked by the ` +
|
|
398
|
+
`depth-verification gate. Call gsd_plan_milestone now to complete the ` +
|
|
399
|
+
`planning phase.`,
|
|
400
|
+
display: false,
|
|
401
|
+
}, { triggerTurn: true });
|
|
402
|
+
// Increment only after a successful dispatch so transient sendMessage
|
|
403
|
+
// failures do not consume recovery budget.
|
|
404
|
+
entry.planBlockedRecoveryCount += 1;
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
logWarning("guided", `Gate 1b recovery sendMessage failed: ${e.message}`);
|
|
408
|
+
}
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
327
412
|
// Gate 2: STATE.md must exist — written as the last step in the discuss
|
|
328
413
|
// output phase. This prevents auto-start from firing during Phase 3
|
|
329
414
|
// (sequential readiness gates for remaining milestones) in multi-milestone
|
|
330
415
|
// discussions, where M001-CONTEXT.md exists but M002/M003 haven't been
|
|
331
416
|
// processed yet.
|
|
332
|
-
const
|
|
333
|
-
if (!
|
|
417
|
+
const stateFilePath = entry.scope.stateFile();
|
|
418
|
+
if (!existsSync(stateFilePath))
|
|
334
419
|
return false; // discussion not finalized yet
|
|
335
420
|
// Gate 3: Multi-milestone completeness warning
|
|
336
421
|
// Parse PROJECT.md for milestone sequence, warn if any are missing context.
|
|
@@ -362,7 +447,7 @@ export function checkAutoStartAfterDiscuss() {
|
|
|
362
447
|
// The LLM writes DISCUSSION-MANIFEST.json after each Phase 3 gate decision.
|
|
363
448
|
// When it exists, validate it before auto-starting. Project history alone is
|
|
364
449
|
// not a reliable signal for the current discussion mode.
|
|
365
|
-
const manifestPath = join(
|
|
450
|
+
const manifestPath = join(entry.scope.workspace.contract.projectGsd, "DISCUSSION-MANIFEST.json");
|
|
366
451
|
if (existsSync(manifestPath)) {
|
|
367
452
|
try {
|
|
368
453
|
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
@@ -896,7 +981,7 @@ export async function showHeadlessMilestoneCreation(ctx, pi, basePath, seedConte
|
|
|
896
981
|
// Build and dispatch the headless discuss prompt
|
|
897
982
|
const prompt = buildHeadlessDiscussPrompt(nextId, seedContext, basePath);
|
|
898
983
|
// Set pending auto start (auto-mode triggers on "Milestone X ready." via checkAutoStartAfterDiscuss)
|
|
899
|
-
|
|
984
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId });
|
|
900
985
|
// Dispatch as discuss-milestone. The LLM writes PROJECT.md, REQUIREMENTS.md,
|
|
901
986
|
// and CONTEXT.md, then calls gsd_plan_milestone — this is semantically the
|
|
902
987
|
// discuss path, just non-interactive. Using "plan-milestone" here caused
|
|
@@ -1062,13 +1147,13 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
1062
1147
|
const seed = draftContent
|
|
1063
1148
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
1064
1149
|
: basePrompt;
|
|
1065
|
-
|
|
1150
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
1066
1151
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1067
1152
|
}
|
|
1068
1153
|
else if (choice === "discuss_fresh") {
|
|
1069
1154
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1070
1155
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1071
|
-
|
|
1156
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: mid, step: false });
|
|
1072
1157
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1073
1158
|
milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1074
1159
|
commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
|
|
@@ -1081,7 +1166,7 @@ export async function showDiscuss(ctx, pi, basePath) {
|
|
|
1081
1166
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1082
1167
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1083
1168
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1084
|
-
|
|
1169
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: false });
|
|
1085
1170
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1086
1171
|
}
|
|
1087
1172
|
return;
|
|
@@ -1335,6 +1420,9 @@ function selfHealRuntimeRecords(basePath, ctx) {
|
|
|
1335
1420
|
for (const record of records) {
|
|
1336
1421
|
const { unitType, unitId, phase } = record;
|
|
1337
1422
|
// Clear records whose expected artifact already exists (completed but not cleaned up)
|
|
1423
|
+
// TODO(C-future): selfHealRuntimeRecords iterates across all unit types (not just milestone
|
|
1424
|
+
// units), so it cannot be converted to resolveExpectedArtifactPathForScope without
|
|
1425
|
+
// first establishing a per-record scope. Migrate once unit runtime records carry scope info.
|
|
1338
1426
|
const artifactPath = resolveExpectedArtifactPath(unitType, unitId, basePath);
|
|
1339
1427
|
if (artifactPath && existsSync(artifactPath)) {
|
|
1340
1428
|
clearUnitRuntimeRecord(basePath, unitType, unitId);
|
|
@@ -1437,7 +1525,7 @@ async function handleMilestoneActions(ctx, pi, basePath, milestoneId, milestoneT
|
|
|
1437
1525
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1438
1526
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1439
1527
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1440
|
-
|
|
1528
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1441
1529
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1442
1530
|
return true;
|
|
1443
1531
|
}
|
|
@@ -1535,11 +1623,13 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1535
1623
|
if (interrupted.classification === "stale") {
|
|
1536
1624
|
clearLock(basePath);
|
|
1537
1625
|
if (interrupted.pausedSession) {
|
|
1626
|
+
// Phase C pt 2: paused-session.json migrated to runtime_kv
|
|
1627
|
+
// (global scope, key PAUSED_SESSION_KV_KEY).
|
|
1538
1628
|
try {
|
|
1539
|
-
|
|
1629
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
1540
1630
|
}
|
|
1541
1631
|
catch (e) {
|
|
1542
|
-
logWarning("guided", `stale
|
|
1632
|
+
logWarning("guided", `stale paused-session DB cleanup failed: ${e.message}`, { file: "guided-flow.ts" });
|
|
1543
1633
|
}
|
|
1544
1634
|
}
|
|
1545
1635
|
}
|
|
@@ -1642,7 +1732,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1642
1732
|
const isFirst = milestoneIds.length === 0;
|
|
1643
1733
|
if (isFirst) {
|
|
1644
1734
|
// First ever — skip wizard, just ask directly
|
|
1645
|
-
|
|
1735
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1646
1736
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1647
1737
|
}
|
|
1648
1738
|
else {
|
|
@@ -1660,7 +1750,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1660
1750
|
notYetMessage: "Run /gsd when ready.",
|
|
1661
1751
|
});
|
|
1662
1752
|
if (choice === "new_milestone") {
|
|
1663
|
-
|
|
1753
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1664
1754
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1665
1755
|
}
|
|
1666
1756
|
}
|
|
@@ -1669,7 +1759,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1669
1759
|
const milestoneId = state.activeMilestone.id;
|
|
1670
1760
|
const milestoneTitle = state.activeMilestone.title;
|
|
1671
1761
|
if (planV2GateDecision === "recover-missing-context") {
|
|
1672
|
-
|
|
1762
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1673
1763
|
await dispatchWorkflow(pi, await buildDiscussMilestonePrompt(milestoneId, milestoneTitle, basePath, getStructuredQuestionsAvailability(pi, ctx)), "gsd-discuss", ctx, "discuss-milestone");
|
|
1674
1764
|
return;
|
|
1675
1765
|
}
|
|
@@ -1697,7 +1787,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1697
1787
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1698
1788
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1699
1789
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1700
|
-
|
|
1790
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1701
1791
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1702
1792
|
}
|
|
1703
1793
|
else if (choice === "status") {
|
|
@@ -1744,13 +1834,13 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1744
1834
|
const seed = draftContent
|
|
1745
1835
|
? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
|
|
1746
1836
|
: basePrompt;
|
|
1747
|
-
|
|
1837
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1748
1838
|
await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
|
|
1749
1839
|
}
|
|
1750
1840
|
else if (choice === "discuss_fresh") {
|
|
1751
1841
|
const discussMilestoneTemplates = inlineTemplate("context", "Context");
|
|
1752
1842
|
const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
|
|
1753
|
-
|
|
1843
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1754
1844
|
await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
1755
1845
|
milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
|
|
1756
1846
|
commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
|
|
@@ -1761,7 +1851,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1761
1851
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1762
1852
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1763
1853
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1764
|
-
|
|
1854
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1765
1855
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1766
1856
|
}
|
|
1767
1857
|
return;
|
|
@@ -1817,7 +1907,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1817
1907
|
notYetMessage: "Run /gsd when ready.",
|
|
1818
1908
|
});
|
|
1819
1909
|
if (choice === "plan") {
|
|
1820
|
-
|
|
1910
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
|
|
1821
1911
|
await dispatchWorkflow(pi, await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath), "gsd-run", ctx, "plan-milestone");
|
|
1822
1912
|
}
|
|
1823
1913
|
else if (choice === "discuss") {
|
|
@@ -1833,7 +1923,7 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
|
|
|
1833
1923
|
const milestoneIds = findMilestoneIds(basePath);
|
|
1834
1924
|
const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
1835
1925
|
const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
|
|
1836
|
-
|
|
1926
|
+
setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
|
|
1837
1927
|
await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
|
|
1838
1928
|
}
|
|
1839
1929
|
else if (choice === "discard_milestone") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { verifyExpectedArtifact } from "./auto-recovery.js";
|
|
4
4
|
import { formatCrashInfo, isLockProcessAlive, readCrashLock, } from "./crash-recovery.js";
|
|
@@ -6,6 +6,7 @@ import { gsdRoot } from "./paths.js";
|
|
|
6
6
|
import { MILESTONE_ID_RE } from "./milestone-ids.js";
|
|
7
7
|
import { synthesizeCrashRecovery, } from "./session-forensics.js";
|
|
8
8
|
import { deriveState } from "./state.js";
|
|
9
|
+
import { getRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
9
10
|
const LEGACY_DEEP_SETUP_UNITS = new Set([
|
|
10
11
|
"workflow-preferences:WORKFLOW-PREFS",
|
|
11
12
|
"discuss-project:PROJECT",
|
|
@@ -32,24 +33,26 @@ function isStalePseudoMilestonePause(meta) {
|
|
|
32
33
|
&& typeof meta.unitId === "string"
|
|
33
34
|
&& LEGACY_DEEP_SETUP_UNITS.has(`${meta.unitType}:${meta.unitId}`);
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* runtime_kv key (global scope) that stores the most recent paused-session
|
|
38
|
+
* metadata. Phase C pt 2: replaces runtime/paused-session.json. The key is
|
|
39
|
+
* project-wide (not worker-scoped) because the paused state represents the
|
|
40
|
+
* last time auto-mode paused on this project — there is at most one paused
|
|
41
|
+
* session per project at a time.
|
|
42
|
+
*/
|
|
43
|
+
export const PAUSED_SESSION_KV_KEY = "paused_session";
|
|
35
44
|
export function readPausedSessionMetadata(basePath) {
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
// basePath is unused now (the DB is workspace-scoped via the connection
|
|
46
|
+
// openDatabase opened on it) but kept in the signature for callers.
|
|
47
|
+
void basePath;
|
|
48
|
+
const meta = getRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
49
|
+
if (!meta)
|
|
38
50
|
return null;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (isStalePseudoMilestonePause(meta)) {
|
|
42
|
-
try {
|
|
43
|
-
unlinkSync(pausedPath);
|
|
44
|
-
}
|
|
45
|
-
catch { /* non-fatal */ }
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
return meta;
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
+
if (isStalePseudoMilestonePause(meta)) {
|
|
52
|
+
deleteRuntimeKv("global", "", PAUSED_SESSION_KV_KEY);
|
|
51
53
|
return null;
|
|
52
54
|
}
|
|
55
|
+
return meta;
|
|
53
56
|
}
|
|
54
57
|
export function isBootstrapCrashLock(lock) {
|
|
55
58
|
return !!(lock &&
|