feat(FN-2602): merge fusion/fn-2602 (auto-resolved)
- feat(FN-2602): complete Step 7 — add release changeset - test(FN-2602): complete Step 6 — align migration and schema tests - test(FN-2602): complete Step 5 — update store status assertions - feat(FN-2602): complete Step 4 — rename triage prompt labels - feat(FN-2602): complete Step 3 — add status rename migration - feat(FN-2602): complete Step 2 — rename respecify status literals - feat(FN-2602): complete Step 1 — rename triage display labels
This commit is contained in:
5
.changeset/fn-2602-status-rename.md
Normal file
5
.changeset/fn-2602-status-rename.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Rename status values: specifying→planning, needs-respecify→needs-replan. Display label "Triage"→"Planning". Includes DB migration for existing records.
|
||||
@@ -131,7 +131,7 @@ describe("Database", () => {
|
||||
});
|
||||
|
||||
it("seeds schema version", () => {
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
});
|
||||
|
||||
it("seeds lastModified", () => {
|
||||
@@ -154,7 +154,7 @@ describe("Database", () => {
|
||||
|
||||
it("is idempotent - calling init() twice does not fail", () => {
|
||||
expect(() => db.init()).not.toThrow();
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
});
|
||||
|
||||
it("does not overwrite existing config on re-init", () => {
|
||||
@@ -761,7 +761,7 @@ describe("schema migrations", () => {
|
||||
db.init();
|
||||
|
||||
// Verify version bumped to 29 (includes v1→v2 through v26→v29)
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
// Verify new columns exist and existing data is intact
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
@@ -786,11 +786,11 @@ describe("schema migrations", () => {
|
||||
const db = new Database(fusionDir);
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
// Re-init should not fail
|
||||
db.init();
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
db.close();
|
||||
});
|
||||
@@ -825,7 +825,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
expect(cols.map((col) => col.name)).toContain("priority");
|
||||
@@ -866,7 +866,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
const colNames = cols.map((col) => col.name);
|
||||
@@ -935,7 +935,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
const colNames = cols.map((col) => col.name);
|
||||
@@ -976,7 +976,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = 'agentRatings'").all() as Array<{ name: string }>;
|
||||
expect(tables).toEqual([{ name: "agentRatings" }]);
|
||||
@@ -1000,7 +1000,7 @@ describe("schema migrations", () => {
|
||||
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name = 'mission_events'").all() as Array<{ name: string }>;
|
||||
expect(tables).toEqual([{ name: "mission_events" }]);
|
||||
@@ -1104,7 +1104,7 @@ describe("schema migrations", () => {
|
||||
db.init();
|
||||
|
||||
// Verify version bumped to 29
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
// Verify new columns exist and existing data is intact
|
||||
const cols = db.prepare("PRAGMA table_info(tasks)").all() as Array<{ name: string }>;
|
||||
@@ -1473,7 +1473,7 @@ describe("createDatabase factory", () => {
|
||||
const db = createDatabase(fusionDir);
|
||||
db.init();
|
||||
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
expect(db.getLastModified()).toBeGreaterThan(0);
|
||||
|
||||
db.close();
|
||||
|
||||
@@ -776,7 +776,7 @@ describe("Migration: pre-33 DB upgrade", () => {
|
||||
// Step 1: Create a fresh database at v33 (runs all migrations up to 33)
|
||||
const db1 = createDatabase(legacyDir);
|
||||
db1.init();
|
||||
expect(db1.getSchemaVersion()).toBe(46);
|
||||
expect(db1.getSchemaVersion()).toBe(47);
|
||||
db1.close();
|
||||
|
||||
// Step 2: Manually downgrade to version 32 and drop insight tables
|
||||
@@ -811,7 +811,7 @@ describe("Migration: pre-33 DB upgrade", () => {
|
||||
expect(tableNamesBefore).not.toContain("project_insight_runs");
|
||||
// Now run init — this triggers the v32→v33 migration
|
||||
db3.init();
|
||||
expect(db3.getSchemaVersion()).toBe(46);
|
||||
expect(db3.getSchemaVersion()).toBe(47);
|
||||
|
||||
// Step 4: Verify insight tables exist after migration
|
||||
const tablesAfter = db3.prepare(
|
||||
@@ -842,12 +842,12 @@ describe("Migration: pre-33 DB upgrade", () => {
|
||||
try {
|
||||
const db1 = createDatabase(testDir);
|
||||
db1.init();
|
||||
expect(db1.getSchemaVersion()).toBe(46);
|
||||
expect(db1.getSchemaVersion()).toBe(47);
|
||||
db1.close();
|
||||
|
||||
const db2 = createDatabase(testDir);
|
||||
expect(() => db2.init()).not.toThrow();
|
||||
expect(db2.getSchemaVersion()).toBe(46);
|
||||
expect(db2.getSchemaVersion()).toBe(47);
|
||||
db2.close();
|
||||
} finally {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
|
||||
@@ -2626,7 +2626,7 @@ describe("MissionStore", () => {
|
||||
|
||||
describe("Loop State & Validator Run Schema (v31)", () => {
|
||||
it("schema version is 40 after migration", () => {
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
});
|
||||
|
||||
it("mission_features table has loop state columns", () => {
|
||||
|
||||
@@ -739,7 +739,7 @@ describe("RoadmapStore", () => {
|
||||
|
||||
describe("schema version", () => {
|
||||
it("schema version is 40 after init", () => {
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -465,7 +465,7 @@ describe("Run Audit", () => {
|
||||
});
|
||||
|
||||
it("schema version is bumped to 40", () => {
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4895,7 +4895,7 @@ Task with acceptance criteria
|
||||
priorStatus: "awaiting-approval",
|
||||
phase: "addComment:awaiting-approval-invalidation",
|
||||
stage: "status-update",
|
||||
nextStatus: "needs-respecify",
|
||||
nextStatus: "needs-replan",
|
||||
runId: "run-invalidation-failure",
|
||||
agentId: "agent-invalidation",
|
||||
error: "status update failed",
|
||||
@@ -4923,7 +4923,7 @@ Task with acceptance criteria
|
||||
const persisted = await store.getTask(task.id);
|
||||
expect(persisted.comments).toHaveLength(1);
|
||||
expect(persisted.comments![0].text).toBe("New user feedback");
|
||||
expect(persisted.status).toBe("needs-respecify");
|
||||
expect(persisted.status).toBe("needs-replan");
|
||||
|
||||
expect(logEntrySpy).toHaveBeenCalled();
|
||||
|
||||
@@ -4941,7 +4941,7 @@ Task with acceptance criteria
|
||||
priorStatus: "awaiting-approval",
|
||||
phase: "addComment:awaiting-approval-invalidation",
|
||||
stage: "post-invalidation-log-entry",
|
||||
nextStatus: "needs-respecify",
|
||||
nextStatus: "needs-replan",
|
||||
runId: "run-post-invalidation-log-failure",
|
||||
agentId: "agent-invalidation",
|
||||
error: "log entry failed",
|
||||
@@ -5005,7 +5005,7 @@ Task with acceptance criteria
|
||||
expect(read1.steeringComments).toHaveLength(1);
|
||||
|
||||
// Simulate a write-back (updateTask writes via upsertTask)
|
||||
await store.updateTask(task.id, { status: "specifying" });
|
||||
await store.updateTask(task.id, { status: "planning" });
|
||||
|
||||
// Read again — should still have exactly 1 comment, not 2
|
||||
const read2 = await store.getTask(task.id);
|
||||
@@ -5025,7 +5025,7 @@ Task with acceptance criteria
|
||||
expect(fetched.comments).toHaveLength(2);
|
||||
expect(fetched.steeringComments).toHaveLength(2);
|
||||
// Write back via an innocuous update
|
||||
await store.updateTask(task.id, { status: "specifying" });
|
||||
await store.updateTask(task.id, { status: "planning" });
|
||||
}
|
||||
|
||||
// Final read — still exactly 2 comments
|
||||
@@ -5050,7 +5050,7 @@ Task with acceptance criteria
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const fetched = await store.getTask(task.id);
|
||||
expect(fetched.comments).toHaveLength(2);
|
||||
await store.updateTask(task.id, { status: "specifying" });
|
||||
await store.updateTask(task.id, { status: "planning" });
|
||||
}
|
||||
|
||||
const final = await store.getTask(task.id);
|
||||
@@ -5076,7 +5076,7 @@ Task with acceptance criteria
|
||||
expect(refinement).toBeDefined();
|
||||
});
|
||||
|
||||
it("transitions awaiting-approval to needs-respecify when user comments on triage task", async () => {
|
||||
it("transitions awaiting-approval to needs-replan when user comments on triage task", async () => {
|
||||
const task = await store.createTask({ description: "Task in triage" });
|
||||
// Keep in triage but set awaiting-approval status
|
||||
await store.updateTask(task.id, { status: "awaiting-approval" });
|
||||
@@ -5086,15 +5086,15 @@ Task with acceptance criteria
|
||||
// Re-read the task to get the Phase 3 status update
|
||||
const updated = await store.getTask(task.id);
|
||||
|
||||
// Task should remain in triage but status should change to needs-respecify
|
||||
// Task should remain in triage but status should change to needs-replan
|
||||
expect(updated.column).toBe("triage");
|
||||
expect(updated.status).toBe("needs-respecify");
|
||||
expect(updated.status).toBe("needs-replan");
|
||||
// Comment should still be added
|
||||
expect(updated.comments).toHaveLength(1);
|
||||
expect(updated.comments![0].text).toBe("I want to change the approach");
|
||||
});
|
||||
|
||||
it("does NOT transition to needs-respecify when agent comments on awaiting-approval task", async () => {
|
||||
it("does NOT transition to needs-replan when agent comments on awaiting-approval task", async () => {
|
||||
const task = await store.createTask({ description: "Task in triage" });
|
||||
await store.updateTask(task.id, { status: "awaiting-approval" });
|
||||
|
||||
@@ -5106,7 +5106,7 @@ Task with acceptance criteria
|
||||
expect(updated.comments).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("does NOT transition to needs-respecify when user comments on non-awaiting-approval triage task", async () => {
|
||||
it("does NOT transition to needs-replan when user comments on non-awaiting-approval triage task", async () => {
|
||||
const task = await store.createTask({ description: "Task in triage" });
|
||||
// Task is in triage with no status (not awaiting-approval)
|
||||
expect(task.status).toBeUndefined();
|
||||
|
||||
@@ -51,7 +51,7 @@ describe("TaskStore task documents", () => {
|
||||
|
||||
expect(tableNames.has("task_documents")).toBe(true);
|
||||
expect(tableNames.has("task_document_revisions")).toBe(true);
|
||||
expect(db.getSchemaVersion()).toBe(46);
|
||||
expect(db.getSchemaVersion()).toBe(47);
|
||||
|
||||
const index = db
|
||||
.prepare(
|
||||
|
||||
@@ -86,7 +86,7 @@ export function probeFts5(db: DatabaseSync): boolean {
|
||||
|
||||
// ── Schema Definition ────────────────────────────────────────────────
|
||||
|
||||
const SCHEMA_VERSION = 46;
|
||||
const SCHEMA_VERSION = 47;
|
||||
|
||||
function normalizeTaskComments(
|
||||
steeringComments: SteeringComment[] | undefined,
|
||||
@@ -1812,6 +1812,17 @@ export class Database {
|
||||
});
|
||||
}
|
||||
|
||||
// Status value rename (FN-2602)
|
||||
// Rename stored status strings: specifying→planning, needs-respecify→needs-replan
|
||||
if (version < 47) {
|
||||
this.applyMigration(47, () => {
|
||||
if (this.hasTable("tasks") && this.hasColumn("tasks", "status")) {
|
||||
this.db.exec("UPDATE tasks SET status = 'planning' WHERE status = 'specifying'");
|
||||
this.db.exec("UPDATE tasks SET status = 'needs-replan' WHERE status = 'needs-respecify'");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -132,18 +132,18 @@ Call \`task_done()\` to signal completion.
|
||||
},
|
||||
"triage-welcome": {
|
||||
key: "triage-welcome",
|
||||
name: "Triage Welcome",
|
||||
name: "Planning Welcome",
|
||||
roles: ["triage"],
|
||||
description: "Introductory section for the triage/specification agent",
|
||||
description: "Introductory section for the planning agent",
|
||||
defaultContent: `You are a task specification agent for "fn", an AI-orchestrated task board.
|
||||
|
||||
Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously in a fresh context with zero memory of this conversation.`,
|
||||
},
|
||||
"triage-context": {
|
||||
key: "triage-context",
|
||||
name: "Triage Context",
|
||||
name: "Planning Context",
|
||||
roles: ["triage"],
|
||||
description: "Context-gathering instructions for triage",
|
||||
description: "Context-gathering instructions for planning",
|
||||
defaultContent: `## What you receive
|
||||
- A raw task title and optional description (the user's rough idea)
|
||||
- Access to the project's files so you can understand context`,
|
||||
|
||||
@@ -4357,7 +4357,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
let invalidatedStatus = false;
|
||||
try {
|
||||
await this.updateTask(id, {
|
||||
status: "needs-respecify",
|
||||
status: "needs-replan",
|
||||
});
|
||||
invalidatedStatus = true;
|
||||
} catch (err) {
|
||||
@@ -4365,7 +4365,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
...commentContextBase,
|
||||
phase: "addComment:awaiting-approval-invalidation",
|
||||
stage: "status-update",
|
||||
nextStatus: "needs-respecify",
|
||||
nextStatus: "needs-replan",
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
@@ -4383,7 +4383,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
...commentContextBase,
|
||||
phase: "addComment:awaiting-approval-invalidation",
|
||||
stage: "post-invalidation-log-entry",
|
||||
nextStatus: "needs-respecify",
|
||||
nextStatus: "needs-replan",
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1697,7 +1697,7 @@ export interface MergeResult extends MergeDetails {
|
||||
}
|
||||
|
||||
export const COLUMN_LABELS: Record<Column, string> = {
|
||||
triage: "Triage",
|
||||
triage: "Planning",
|
||||
todo: "Todo",
|
||||
"in-progress": "In Progress",
|
||||
"in-review": "In Review",
|
||||
@@ -1706,7 +1706,7 @@ export const COLUMN_LABELS: Record<Column, string> = {
|
||||
};
|
||||
|
||||
export const COLUMN_DESCRIPTIONS: Record<Column, string> = {
|
||||
triage: "Raw ideas — AI will specify these",
|
||||
triage: "Raw ideas — AI will plan these",
|
||||
todo: "Specified and ready to start",
|
||||
"in-progress": "AI is working on this in a worktree",
|
||||
"in-review": "Complete — ready to merge",
|
||||
|
||||
Reference in New Issue
Block a user