feat(sf): DB migration v22 adds is_sketch + sketch_scope columns (ADR-011)

Mirrors gsd-2's slices schema for progressive planning. Three changes
to sf-db.ts:

1. Fresh-install CREATE TABLE for slices (line 312) gains:
   - is_sketch INTEGER NOT NULL DEFAULT 0  -- 1 = awaiting refine
   - sketch_scope TEXT NOT NULL DEFAULT '' -- 2-3 sentence scope hint

2. Schema version 22 migration: ensureColumn for both fields so
   existing installs upgrade without data loss. Wrapped in the same
   currentVersion < N guard pattern as v6, v7, v8 ... v21.

3. rowToSlice() returns sketch_scope and is_sketch on the SliceRow
   so the dispatch rule from 0c78b0038 can read them via getSlice().

End-to-end verified: fresh DB has both columns at defaults; getSlice()
returns is_sketch=0, sketch_scope='' on a freshly-inserted slice.

Closes the DDL-migration gap from the progressive-planning rollout
plan in fef2e4b6f. Remaining: plan-milestone tool needs to write
is_sketch=1 + sketch_scope when emitting sketches; refine-slice tool
needs to clear is_sketch=0 when persisting the full plan. Until those
land, the dispatch rule still falls through (sketches never created).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-02 19:18:50 +02:00
parent 0c78b00381
commit c11595cf22

View file

@ -332,6 +332,8 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
planning_meeting_json TEXT NOT NULL DEFAULT '',
sequence INTEGER DEFAULT 0, -- Ordering hint: tools may set this to control execution order
replan_triggered_at TEXT DEFAULT NULL,
is_sketch INTEGER NOT NULL DEFAULT 0, -- ADR-011: 1 = slice is a sketch awaiting refine-slice
sketch_scope TEXT NOT NULL DEFAULT '', -- ADR-011: 2-3 sentence scope hint from plan-milestone
PRIMARY KEY (milestone_id, id),
FOREIGN KEY (milestone_id) REFERENCES milestones(id)
)
@ -1396,6 +1398,31 @@ function migrateSchema(db: DbAdapter): void {
});
}
if (currentVersion < 22) {
// ADR-011: progressive planning. is_sketch=1 means the slice is a 2-3
// sentence sketch awaiting refine-slice expansion; refine fills in the
// real plan and clears the flag. sketch_scope holds the milestone
// planner's stored scope hint that refine treats as a hard boundary.
ensureColumn(
db,
"slices",
"is_sketch",
`ALTER TABLE slices ADD COLUMN is_sketch INTEGER NOT NULL DEFAULT 0`,
);
ensureColumn(
db,
"slices",
"sketch_scope",
`ALTER TABLE slices ADD COLUMN sketch_scope TEXT NOT NULL DEFAULT ''`,
);
db.prepare(
"INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)",
).run({
":version": 22,
":applied_at": new Date().toISOString(),
});
}
db.exec("COMMIT");
} catch (err) {
db.exec("ROLLBACK");
@ -2355,6 +2382,8 @@ function rowToSlice(row: Record<string, unknown>): SliceRow {
planning_meeting: parsePlanningMeeting(row["planning_meeting_json"]),
sequence: (row["sequence"] as number) ?? 0,
replan_triggered_at: (row["replan_triggered_at"] as string) ?? null,
sketch_scope: (row["sketch_scope"] as string) ?? "",
is_sketch: (row["is_sketch"] as number) ?? 0,
};
}