fix: reopen DB on cold resume, recognize heavy check mark (#3319)
* fix: reopen DB on cold resume and recognize U+2714 check mark The paused-session resume path in auto.ts called rebuildState/deriveState without first opening the project database, causing state derivation to fall back to markdown parsing. This misparsed roadmap table rows with glyph done markers and could redispatch wrong slices. Export openProjectDbIfPresent from auto-start.ts and call it in the resume path before rebuildState, matching the fresh bootstrap ordering. Also add U+2714 (heavy check mark) to the table parser done-detection regex alongside the existing U+2705/U+2611/U+2713 glyphs. Closes #2940 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove duplicate openProjectDbIfPresent from rebase conflict The rebase onto main introduced a duplicate `openProjectDbIfPresent` function declaration (one from the PR, one from main), causing TS2393. Keep the exported version that uses the static import. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: use logWarning instead of process.stderr in openProjectDbIfPresent --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: trek-e <trek-e@users.noreply.github.com>
This commit is contained in:
parent
b1ae782876
commit
0908adcb4e
5 changed files with 82 additions and 24 deletions
|
|
@ -58,7 +58,7 @@ import { initRoutingHistory } from "./routing-history.js";
|
|||
import { restoreHookState, resetHookState } from "./post-unit-hooks.js";
|
||||
import { resetProactiveHealing, setLevelChangeCallback } from "./doctor-proactive.js";
|
||||
import { snapshotSkills } from "./skill-discovery.js";
|
||||
import { isDbAvailable, getMilestone } from "./gsd-db.js";
|
||||
import { isDbAvailable, getMilestone, openDatabase } from "./gsd-db.js";
|
||||
import { hideFooter } from "./auto-dashboard.js";
|
||||
import {
|
||||
debugLog,
|
||||
|
|
@ -99,33 +99,24 @@ export interface BootstrapDeps {
|
|||
* concurrent session detected). Returns true when ready to dispatch.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Open the project-root DB before the first deriveState call (#2841).
|
||||
* When auto-mode starts cold (no prior DB handle), state derivation that
|
||||
* touches DB-backed helpers (queue-order, task status) silently falls back
|
||||
* to markdown-only data, producing stale or incomplete state. Opening the
|
||||
* DB first ensures deriveState sees the full picture on its very first run.
|
||||
*/
|
||||
async function openProjectDbIfPresent(basePath: string): Promise<void> {
|
||||
const gsdDbPath = resolveProjectRootDbPath(basePath);
|
||||
if (!existsSync(gsdDbPath)) return;
|
||||
if (isDbAvailable()) return;
|
||||
|
||||
try {
|
||||
const { openDatabase } = await import("./gsd-db.js");
|
||||
openDatabase(gsdDbPath);
|
||||
} catch (err) {
|
||||
/* non-fatal — DB lifecycle block below will retry */
|
||||
logWarning("engine", `DB open failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Guard: tracks consecutive bootstrap attempts that found phase === "complete".
|
||||
* Prevents the recursive dialog loop described in #1348 where
|
||||
* bootstrapAutoSession → showSmartEntry → checkAutoStartAfterDiscuss → startAuto
|
||||
* cycles indefinitely when the discuss workflow doesn't produce a milestone. */
|
||||
let _consecutiveCompleteBootstraps = 0;
|
||||
const MAX_CONSECUTIVE_COMPLETE_BOOTSTRAPS = 2;
|
||||
|
||||
export async function openProjectDbIfPresent(basePath: string): Promise<void> {
|
||||
const gsdDbPath = resolveProjectRootDbPath(basePath);
|
||||
if (!existsSync(gsdDbPath) || isDbAvailable()) return;
|
||||
|
||||
try {
|
||||
openDatabase(gsdDbPath);
|
||||
} catch (err) {
|
||||
logWarning("engine", `gsd-db: failed to open existing database: ${err instanceof Error ? err.message : String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function bootstrapAutoSession(
|
||||
s: AutoSession,
|
||||
ctx: ExtensionCommandContext,
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ import {
|
|||
postUnitPreVerification,
|
||||
postUnitPostVerification,
|
||||
} from "./auto-post-unit.js";
|
||||
import { bootstrapAutoSession, type BootstrapDeps } from "./auto-start.js";
|
||||
import { bootstrapAutoSession, openProjectDbIfPresent, type BootstrapDeps } from "./auto-start.js";
|
||||
import { autoLoop, resolveAgentEnd, resolveAgentEndCancelled, _resetPendingResolve, isSessionSwitchInFlight, type LoopDeps, type ErrorContext } from "./auto-loop.js";
|
||||
import {
|
||||
WorktreeResolver,
|
||||
|
|
@ -1205,6 +1205,9 @@ export async function startAuto(
|
|||
"info",
|
||||
);
|
||||
restoreHookState(s.basePath);
|
||||
// Open the project DB before rebuild/derive so resume uses DB-backed
|
||||
// state instead of falling back to stale markdown parsing (#2940).
|
||||
await openProjectDbIfPresent(s.basePath);
|
||||
try {
|
||||
await rebuildState(s.basePath);
|
||||
syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ function parseTableSlices(section: string): RoadmapSliceEntry[] {
|
|||
const fullRow = line.toLowerCase();
|
||||
const done =
|
||||
/\[x\]/i.test(line) ||
|
||||
/[✅☑✓]/.test(line) ||
|
||||
/[✅☑✓✔]/.test(line) ||
|
||||
/\bdone\b/.test(fullRow) ||
|
||||
/\bcomplete(?:d)?\b/.test(fullRow);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* cold-resume-db-reopen.test.ts — Regression test for #2940.
|
||||
*
|
||||
* Validates that the paused-session resume path in auto.ts opens the project
|
||||
* database before calling rebuildState() / deriveState(), matching the fresh
|
||||
* bootstrap path in auto-start.ts.
|
||||
*
|
||||
* Without this, cold resume falls back to markdown parsing which misreads
|
||||
* done cells and redispatches wrong slices.
|
||||
*/
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { createTestContext } from "./test-helpers.ts";
|
||||
|
||||
const { assertTrue, report } = createTestContext();
|
||||
|
||||
const autoSrc = readFileSync(join(import.meta.dirname, "..", "auto.ts"), "utf-8");
|
||||
|
||||
console.log("\n=== #2940: resume path opens DB before rebuildState/deriveState ===");
|
||||
|
||||
// The resume block is the `if (s.paused) { ... }` section that calls rebuildState/deriveState.
|
||||
// Locate the resume section by finding `s.paused = false;` followed by `rebuildState`.
|
||||
const resumeSectionStart = autoSrc.indexOf("if (s.paused) {", autoSrc.indexOf("// If resuming from paused state"));
|
||||
assertTrue(resumeSectionStart > 0, "auto.ts has the paused-session resume block");
|
||||
|
||||
const resumeSection = autoSrc.slice(resumeSectionStart, resumeSectionStart + 3000);
|
||||
|
||||
// The resume path must open the DB before rebuildState/deriveState
|
||||
const rebuildIdx = resumeSection.indexOf("rebuildState(");
|
||||
assertTrue(rebuildIdx > 0, "resume block calls rebuildState");
|
||||
|
||||
const deriveIdx = resumeSection.indexOf("deriveState(");
|
||||
assertTrue(deriveIdx > 0, "resume block calls deriveState");
|
||||
|
||||
// There must be a DB open call before the first rebuildState call
|
||||
const dbOpenPatterns = [
|
||||
"openProjectDbIfPresent(",
|
||||
"openDatabase(",
|
||||
"ensureDbOpen(",
|
||||
];
|
||||
|
||||
const preDeriveSection = resumeSection.slice(0, rebuildIdx);
|
||||
const hasDbOpen = dbOpenPatterns.some(pat => preDeriveSection.includes(pat));
|
||||
assertTrue(
|
||||
hasDbOpen,
|
||||
"resume path must open DB before rebuildState/deriveState (#2940)",
|
||||
);
|
||||
|
||||
report();
|
||||
|
|
@ -133,6 +133,19 @@ test("parseRoadmapSlices: table with glyph completion markers (#2841)", () => {
|
|||
assert.equal(slices[3]?.done, true);
|
||||
});
|
||||
|
||||
test("parseRoadmapSlices: table with heavy check mark U+2714 (#2940)", () => {
|
||||
const tableContent = [
|
||||
"# M003: Heavy Check", "", "## Slices", "",
|
||||
"| Slice | Title | Risk | Status |", "|---|---|---|---|",
|
||||
"| S01 | First | Low | \u2714 |",
|
||||
"| S02 | Second | High | Pending |", "",
|
||||
].join("\n");
|
||||
const slices = parseRoadmapSlices(tableContent);
|
||||
assert.equal(slices.length, 2);
|
||||
assert.equal(slices[0]?.done, true, "U+2714 heavy check mark should mark slice as done");
|
||||
assert.equal(slices[1]?.done, false);
|
||||
});
|
||||
|
||||
test("parseRoadmapSlices: table with dependencies column (#1736)", () => {
|
||||
const tableContent = [
|
||||
"# M004: Deps", "", "## Slices", "",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue