Skip to content

Roadmap lanes follow wish status - #2751

Merged
namastex888 merged 5 commits into
devfrom
wish/roadmap-truth
Aug 6, 2026
Merged

namastex888 merged 5 commits into
devfrom
wish/roadmap-truth

Conversation

@namastex888

@namastex888 namastex888 commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • reconcile sync-owned roadmap cards from WISH status on CLI JSON reads while keeping MCP reads non-mutating
  • add task link for associating an existing card with a wish, including true repeated-call idempotency
  • preserve manual/sync audit provenance and harden WISH file reads against filesystem escapes
  • advance all release manifests to 5.260805.1

Validation

  • bun install --frozen-lockfile with Bun 1.3.11: no changes
  • bun run check with Bun 1.3.11: 3017 passed, 11 intended environment-gated skips, 0 failed, 11222 expectations
  • independent aggregate review: SHIP after one idempotency fix round

Summary by CodeRabbit

  • New Features

    • Added task link to associate existing tasks with wishes and optional groups.
    • Added automatic wish-status reconciliation when reading boards as JSON, moving matching tasks to appropriate lifecycle lanes.
    • Re-linking tasks is idempotent, and omitting a group clears the previous association.
  • Bug Fixes

    • JSON board reads preserve task data when wish files are missing, invalid, or unavailable.
    • Read-only board operations no longer modify task state or events.
  • Chores

    • Updated Genie plugin packages and manifests to version 5.260805.1.

Review disclosures (2026-08-06 post-relocation execution re-review)

An independent execution re-review (FIX-FIRST, resolved — full ledger in .genie/wishes/roadmap-truth/WISH.md on this branch) surfaced items this body must declare:

  • .well-known channel manifests are edited by 40ad3d050 (dev.json, latest.json). Net-nil: both are pinned to origin/main's exact values so this branch's tree matches main's release surface. They were outside the wish's file list, hence declared here.
  • Branch base deviation: cut from dev @ 6d252e65c, not origin/main as the wish's Group 0 guard wrote. Benign — the tree differs from origin/main only in the .well-known files above, which the tip commit pins to main's values.
  • Version stamps: the seven manifest/package files land at 5.260805.1 (dev is behind at 5.260803.6); the merge moves forward and [auto-version] CI owns the final stamp.
  • Promote status: the dogfood promote of this build to the local machine was reverted by the release channel on 2026-08-06; the machine-live observations in the 2026-08-05 execution record are historical. Groups 1–3 rest on the committed tests (177/0 in the four touched files); the live divergence oracle and probe walk run post-release.
  • Wish docs: 21350f7f6 commits the wish plan, design, and full review ledger onto this branch.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1d337ef7-858b-4e7e-bab6-f4d31a995dc3

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds task-to-wish linking, JSON board wish-status reconciliation, read-only MCP integrity coverage, and coordinated package and plugin version updates.

Changes

Task-to-wish linking

Layer / File(s) Summary
Task link state operation
src/lib/v5/task-state.ts, src/lib/v5/task-state.test.ts
Adds linkTaskToWish with conditional updates, optional group handling, timestamp behavior, and unknown-task validation.
Task link CLI command
src/term-commands/v5-task.ts, src/term-commands/v5-task.test.ts
Adds task link <id> with required wish validation, optional group support, idempotent relinking, and failure-path coverage.

Board wish-status reconciliation

Layer / File(s) Summary
Wish status reading and mapping
src/term-commands/v5-board.ts, src/term-commands/v5-board.test.ts
Reads bounded, direct WISH.md files and maps recognized statuses to lifecycle lanes.
JSON board lane reconciliation
src/term-commands/v5-board.ts, src/term-commands/v5-board.test.ts
Reconciles selected sync-owned task lanes during JSON reads and preserves lanes for invalid or unavailable wishes.
Read-only MCP integrity validation
src/term-commands/mcp.test.ts
Verifies that genie_board does not modify an immutable database or task state.

Manifest version alignment

Layer / File(s) Summary
Manifest version updates
.claude-plugin/marketplace.json, package.json, plugins/genie/..., plugins/hermes-genie/plugin.yaml, plugins/pi-genie/package.json
Updates repository and plugin versions from 5.260803.6 to 5.260805.1.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Board as genie_board
  participant Wishes as WISH.md
  participant State as moveTask
  participant SQLite as SQLite database
  Board->>Wishes: Read selected task status
  Wishes-->>Board: Return status or read miss
  Board->>State: Move task for recognized status
  State->>SQLite: Persist lane and sync event
  SQLite-->>Board: Return updated board state
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main roadmap change: lanes follow wish status during CLI JSON reads.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wish/roadmap-truth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/v5/task-state.ts`:
- Around line 524-532: Update the transaction flow surrounding link.immediate()
so getTask(db, taskId) executes and its result is captured inside the immediate
transaction before the write lock is released. Return that captured task after
link.immediate(), ensuring handleLink receives the association written by this
call rather than a concurrent update.

In `@src/term-commands/v5-board.ts`:
- Around line 145-172: Update laneForWishStatus to recognize the FIX-FIRST
status prefix and map it to the Review lane, consistent with
returned-from-review semantics. Add or update the corresponding FIX-FIRST
mapping case in the laneForWishStatus tests.
- Around line 224-245: Update reconcileWishLanes to create a per-invocation Map
keyed by wish slug before iterating boards, and reuse each cached result when
resolving readWishStatus. Populate the cache only on the first encounter for
each slug, including null or missing statuses, so repeated tasks and boards
avoid duplicate reads while preserving one consistent status throughout the
reconciliation pass.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e69b8bd2-c7e8-4398-ba43-6935da8fa5d6

📥 Commits

Reviewing files that changed from the base of the PR and between 708c5c0 and 4bf066e.

📒 Files selected for processing (14)
  • .claude-plugin/marketplace.json
  • package.json
  • plugins/genie/.claude-plugin/plugin.json
  • plugins/genie/.codex-plugin/plugin.json
  • plugins/genie/package.json
  • plugins/hermes-genie/plugin.yaml
  • plugins/pi-genie/package.json
  • src/lib/v5/task-state.test.ts
  • src/lib/v5/task-state.ts
  • src/term-commands/mcp.test.ts
  • src/term-commands/v5-board.test.ts
  • src/term-commands/v5-board.ts
  • src/term-commands/v5-task.test.ts
  • src/term-commands/v5-task.ts

Comment thread src/lib/v5/task-state.ts
Comment on lines +524 to +532
const link = db.transaction(() => {
requireTask(db, taskId);
db.query(
`UPDATE tasks SET wish = ?, group_name = ?, updated_at = ?
WHERE id = ? AND (wish IS NOT ? OR group_name IS NOT ?)`,
).run(wish, normalizedGroup, now, taskId, wish, normalizedGroup);
});
link.immediate();
return getTask(db, taskId) as TaskRow;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Return the linked task inside the transaction.

Line 532 runs after link.immediate() releases the write lock. A concurrent linkTaskToWish call can update the same task in that gap. This call can then return the other call's wish and group, and handleLink can report an association that this command did not write.

Read and return the task inside the immediate transaction.

Proposed fix
   const normalizedGroup = group ?? null;
   const link = db.transaction(() => {
     requireTask(db, taskId);
     db.query(
       `UPDATE tasks SET wish = ?, group_name = ?, updated_at = ?
        WHERE id = ? AND (wish IS NOT ? OR group_name IS NOT ?)`,
     ).run(wish, normalizedGroup, now, taskId, wish, normalizedGroup);
+    return getTask(db, taskId) as TaskRow;
   });
-  link.immediate();
-  return getTask(db, taskId) as TaskRow;
+  return link.immediate();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const link = db.transaction(() => {
requireTask(db, taskId);
db.query(
`UPDATE tasks SET wish = ?, group_name = ?, updated_at = ?
WHERE id = ? AND (wish IS NOT ? OR group_name IS NOT ?)`,
).run(wish, normalizedGroup, now, taskId, wish, normalizedGroup);
});
link.immediate();
return getTask(db, taskId) as TaskRow;
const link = db.transaction(() => {
requireTask(db, taskId);
db.query(
`UPDATE tasks SET wish = ?, group_name = ?, updated_at = ?
WHERE id = ? AND (wish IS NOT ? OR group_name IS NOT ?)`,
).run(wish, normalizedGroup, now, taskId, wish, normalizedGroup);
return getTask(db, taskId) as TaskRow;
});
return link.immediate();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/v5/task-state.ts` around lines 524 - 532, Update the transaction flow
surrounding link.immediate() so getTask(db, taskId) executes and its result is
captured inside the immediate transaction before the write lock is released.
Return that captured task after link.immediate(), ensuring handleLink receives
the association written by this call rather than a concurrent update.

Comment on lines +145 to +172
function laneForWishStatus(status: string): WishLane | null {
const key = status.toUpperCase();
if (key.startsWith('DRAFT') || key.startsWith('ROADMAP')) return 'Idea';
if (key.startsWith('BLOCK') || key.startsWith('ON-HOLD')) return 'Work';
if (key.startsWith('EXECUTED') || key.startsWith('REVIEWED') || key.startsWith('PLAN-REVIEWED')) return 'Review';
if (key.startsWith('IN') || key.startsWith('EXECUT') || key.startsWith('WAVE')) return 'Work';
if (
key.startsWith('READY') ||
key.startsWith('APPROVED') ||
key.startsWith('PLAN-') ||
key.startsWith('SHIP-') ||
key.startsWith('STAGED')
) {
return 'Wish';
}
if (
key.startsWith('DONE') ||
key.startsWith('SHIP') ||
key.startsWith('MERGED') ||
key.startsWith('COMPLET') ||
key.startsWith('DELIVER') ||
key.startsWith('PUBLISH') ||
key.startsWith('CONCLU')
) {
return 'Done';
}
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

FIX-FIRST is a durable wish status but maps to no lane.

The coding guidelines list FIX-FIRST as a durable wish status. laneForWishStatus matches no prefix for it, so it returns null and the card never reconciles. FIX-FIRST means a wish returned from review, so Review is the consistent destination. Add the prefix, or state in the docstring that FIX-FIRST stays hand-owned on purpose.

As per coding guidelines: "durable statuses are DRAFT, FIX-FIRST, APPROVED, IN_PROGRESS, BLOCKED, and SHIPPED."

🐛 Proposed mapping for `FIX-FIRST`
-  if (key.startsWith('EXECUTED') || key.startsWith('REVIEWED') || key.startsWith('PLAN-REVIEWED')) return 'Review';
+  if (
+    key.startsWith('EXECUTED') ||
+    key.startsWith('REVIEWED') ||
+    key.startsWith('PLAN-REVIEWED') ||
+    key.startsWith('FIX-FIRST')
+  ) {
+    return 'Review';
+  }

Add the matching case to the prefix table in src/term-commands/v5-board.test.ts as well.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function laneForWishStatus(status: string): WishLane | null {
const key = status.toUpperCase();
if (key.startsWith('DRAFT') || key.startsWith('ROADMAP')) return 'Idea';
if (key.startsWith('BLOCK') || key.startsWith('ON-HOLD')) return 'Work';
if (key.startsWith('EXECUTED') || key.startsWith('REVIEWED') || key.startsWith('PLAN-REVIEWED')) return 'Review';
if (key.startsWith('IN') || key.startsWith('EXECUT') || key.startsWith('WAVE')) return 'Work';
if (
key.startsWith('READY') ||
key.startsWith('APPROVED') ||
key.startsWith('PLAN-') ||
key.startsWith('SHIP-') ||
key.startsWith('STAGED')
) {
return 'Wish';
}
if (
key.startsWith('DONE') ||
key.startsWith('SHIP') ||
key.startsWith('MERGED') ||
key.startsWith('COMPLET') ||
key.startsWith('DELIVER') ||
key.startsWith('PUBLISH') ||
key.startsWith('CONCLU')
) {
return 'Done';
}
return null;
}
function laneForWishStatus(status: string): WishLane | null {
const key = status.toUpperCase();
if (key.startsWith('DRAFT') || key.startsWith('ROADMAP')) return 'Idea';
if (key.startsWith('BLOCK') || key.startsWith('ON-HOLD')) return 'Work';
if (
key.startsWith('EXECUTED') ||
key.startsWith('REVIEWED') ||
key.startsWith('PLAN-REVIEWED') ||
key.startsWith('FIX-FIRST')
) {
return 'Review';
}
if (key.startsWith('IN') || key.startsWith('EXECUT') || key.startsWith('WAVE')) return 'Work';
if (
key.startsWith('READY') ||
key.startsWith('APPROVED') ||
key.startsWith('PLAN-') ||
key.startsWith('SHIP-') ||
key.startsWith('STAGED')
) {
return 'Wish';
}
if (
key.startsWith('DONE') ||
key.startsWith('SHIP') ||
key.startsWith('MERGED') ||
key.startsWith('COMPLET') ||
key.startsWith('DELIVER') ||
key.startsWith('PUBLISH') ||
key.startsWith('CONCLU')
) {
return 'Done';
}
return null;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/term-commands/v5-board.ts` around lines 145 - 172, Update
laneForWishStatus to recognize the FIX-FIRST status prefix and map it to the
Review lane, consistent with returned-from-review semantics. Add or update the
corresponding FIX-FIRST mapping case in the laneForWishStatus tests.

Source: Coding guidelines

Comment on lines +224 to +245
function reconcileWishLanes(db: Database, filter: TaskFilter, selectedBoard: BoardRow | null): void {
const repoRoot = resolveRepoRoot();
const boards = selectedBoard ? [selectedBoard] : listBoards(db);
for (const board of boards) {
const lanes = board.lanes;
if (!lanes || lanes.length === 0) continue;
const laneNames = new Set(lanes.map((lane) => lane.name));
const enclosingLane = lanes[0].name;
const boardFilter: TaskFilter = { ...filter, boardId: board.id };
for (const task of listTasksWithLane(db, boardFilter)) {
if (!task.wish) continue;
const status = readWishStatus(repoRoot, task.wish);
const destination = status ? laneForWishStatus(status) : null;
if (!destination || !laneNames.has(destination)) continue;
const currentLane = task.lane ?? enclosingLane;
if (currentLane === destination) continue;
try {
moveTask(db, task.id, destination, { author: 'wish-status-sync', authorKind: 'genie' });
} catch {
// Best-effort reconciliation: render the durable lane that remains.
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cache wish status per slug for the duration of one reconciliation pass.

readWishStatus runs once per task. Tasks that belong to the same wish repeat the same three lstatSync calls plus an open and a full read of the same WISH.md. When no board is selected, the loop also repeats across boards. A local Map keyed by slug removes the duplicate I/O and keeps one consistent status for the whole pass.

♻️ Proposed per-pass cache
 function reconcileWishLanes(db: Database, filter: TaskFilter, selectedBoard: BoardRow | null): void {
   const repoRoot = resolveRepoRoot();
   const boards = selectedBoard ? [selectedBoard] : listBoards(db);
+  // One filesystem read per slug: a wish commonly owns many cards.
+  const statusBySlug = new Map<string, string | null>();
+  const statusFor = (wish: string): string | null => {
+    if (!statusBySlug.has(wish)) statusBySlug.set(wish, readWishStatus(repoRoot, wish));
+    return statusBySlug.get(wish) ?? null;
+  };
   for (const board of boards) {
     const lanes = board.lanes;
     if (!lanes || lanes.length === 0) continue;
     const laneNames = new Set(lanes.map((lane) => lane.name));
     const enclosingLane = lanes[0].name;
     const boardFilter: TaskFilter = { ...filter, boardId: board.id };
     for (const task of listTasksWithLane(db, boardFilter)) {
       if (!task.wish) continue;
-      const status = readWishStatus(repoRoot, task.wish);
+      const status = statusFor(task.wish);
       const destination = status ? laneForWishStatus(status) : null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function reconcileWishLanes(db: Database, filter: TaskFilter, selectedBoard: BoardRow | null): void {
const repoRoot = resolveRepoRoot();
const boards = selectedBoard ? [selectedBoard] : listBoards(db);
for (const board of boards) {
const lanes = board.lanes;
if (!lanes || lanes.length === 0) continue;
const laneNames = new Set(lanes.map((lane) => lane.name));
const enclosingLane = lanes[0].name;
const boardFilter: TaskFilter = { ...filter, boardId: board.id };
for (const task of listTasksWithLane(db, boardFilter)) {
if (!task.wish) continue;
const status = readWishStatus(repoRoot, task.wish);
const destination = status ? laneForWishStatus(status) : null;
if (!destination || !laneNames.has(destination)) continue;
const currentLane = task.lane ?? enclosingLane;
if (currentLane === destination) continue;
try {
moveTask(db, task.id, destination, { author: 'wish-status-sync', authorKind: 'genie' });
} catch {
// Best-effort reconciliation: render the durable lane that remains.
}
}
function reconcileWishLanes(db: Database, filter: TaskFilter, selectedBoard: BoardRow | null): void {
const repoRoot = resolveRepoRoot();
const boards = selectedBoard ? [selectedBoard] : listBoards(db);
// One filesystem read per slug: a wish commonly owns many cards.
const statusBySlug = new Map<string, string | null>();
const statusFor = (wish: string): string | null => {
if (!statusBySlug.has(wish)) statusBySlug.set(wish, readWishStatus(repoRoot, wish));
return statusBySlug.get(wish) ?? null;
};
for (const board of boards) {
const lanes = board.lanes;
if (!lanes || lanes.length === 0) continue;
const laneNames = new Set(lanes.map((lane) => lane.name));
const enclosingLane = lanes[0].name;
const boardFilter: TaskFilter = { ...filter, boardId: board.id };
for (const task of listTasksWithLane(db, boardFilter)) {
if (!task.wish) continue;
const status = statusFor(task.wish);
const destination = status ? laneForWishStatus(status) : null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/term-commands/v5-board.ts` around lines 224 - 245, Update
reconcileWishLanes to create a per-invocation Map keyed by wish slug before
iterating boards, and reuse each cached result when resolving readWishStatus.
Populate the cache only on the first encounter for each slug, including null or
missing statuses, so repeated tasks and boards avoid duplicate reads while
preserving one consistent status throughout the reconciliation pass.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bf066eff1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +265 to +267
// Deliberately CLI-only. MCP queries call their shared read projection and
// never enter this verb handler, so they remain read-only.
if (opts.json) reconcileWishLanes(db, filter, board);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep JSON board reads non-mutating for plugin callers

When Pi's genie_board tool, Hermes's legacy genie_board/genie_wish_status tools, or the Hermes session-context hook invokes genie board --json, this unconditional reconciliation calls moveTask and writes lanes plus timeline events. Those callers explicitly advertise themselves as read-only (and the context hook can run automatically), so merely inspecting status can silently undo manual lane placement; reconciliation needs an explicit mutating surface or flag while ordinary --json remains read-only.

Useful? React with 👍 / 👎.

Comment on lines +145 to +150
function laneForWishStatus(status: string): WishLane | null {
const key = status.toUpperCase();
if (key.startsWith('DRAFT') || key.startsWith('ROADMAP')) return 'Idea';
if (key.startsWith('BLOCK') || key.startsWith('ON-HOLD')) return 'Work';
if (key.startsWith('EXECUTED') || key.startsWith('REVIEWED') || key.startsWith('PLAN-REVIEWED')) return 'Review';
if (key.startsWith('IN') || key.startsWith('EXECUT') || key.startsWith('WAVE')) return 'Work';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Map the canonical FIX-FIRST status to its lifecycle lane

FIX-FIRST is a canonical persisted WISH status (scripts/wishes-lint.ts) for a plan that must be fixed and re-reviewed, but none of these prefixes recognizes it. Consequently, after a linked wish transitions to FIX-FIRST, every JSON reconciliation leaves its card in the previous lane, defeating the new status-following behavior for a normal lifecycle transition; handle FIX-FIRST explicitly in the plan-stage lane and cover it in the mapping test.

Useful? React with 👍 / 👎.

@namastex888
namastex888 changed the base branch from main to dev August 6, 2026 18:53
… its branch

The wish documents were authored in the remotty repo by the originating
session and recovered on 2026-08-06. The ledger carries the 2026-08-05
execution record with a dated amendment (promote reverted), the FIX-FIRST
post-relocation execution review, and the fix record resolving it.

Wish: roadmap-truth
Fix round 1 closed all eight FIX-FIRST gaps; deferred live oracles are
owned by follow-up task t_msi2nv2gcf9c97af.

Wish: roadmap-truth
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant