Skip to content

feat(missions): per-mission taskPrefix override for triaged task ids#1930

Open
flexi767 wants to merge 1 commit into
Runfusion:mainfrom
flexi767:feat/per-mission-task-prefix
Open

feat(missions): per-mission taskPrefix override for triaged task ids#1930
flexi767 wants to merge 1 commit into
Runfusion:mainfrom
flexi767:feat/per-mission-task-prefix

Conversation

@flexi767

@flexi767 flexi767 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

taskPrefix is a project-wide setting (default FN). There's no way to make a single mission's tickets use a different prefix (e.g. ERR-) while everything else keeps FN-. Flipping the project setting changes the prefix for every new ticket board-wide — including whatever autopilot is minting on other missions concurrently.

Solution

Add an optional per-mission taskPrefix. The distributed id allocator is already multi-prefix (reserveDistributedTaskId({ prefix }), per-prefix sequences), so this change only threads a per-mission prefix through triage — no allocator changes.

  • Mission.taskPrefix (nullable, optional) — persisted via a new additive migration (schema v140, addColumnIfMissing, no backfill). Mirrors the existing branchStrategy persistence shape.
  • MissionStore.triageFeature passes the mission's prefix into TaskCreateInput.taskPrefix — a transient minting hint, not persisted on the task.
  • createTaskWithDistributedReservation prefers input.taskPrefix over the project settings.taskPrefix for the id reservation.
  • buildCommitMsgTrailerHook now derives the strip PREFIX from the task id itself (ERR-5ERR) rather than the project-wide options.taskPrefix, so per-mission prefixes strip correctly while the project prefix stays FN. This fixes all five engine call sites at once, backward-compatibly.
  • Mission create + edit UI (MissionManager) exposes a "Task prefix" input; mission-routes POST/PATCH validate it (letter-led alphanumeric, uppercased; empty ⇒ inherit project prefix).

Tests

  • mission-store.test.ts: create/update round-trip, legacy-NULL read, and triage mints with the mission's prefix (/^ERR-\d+$/).
  • worktree-hooks.test.ts: commit-msg hook derives the strip prefix from the task id, ignoring a mismatched options.taskPrefix.

All touched suites green; tsc --noEmit clean across core / engine / dashboard.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Missions can now have a custom task prefix, shown in the mission forms and saved with the mission.
    • Newly created triaged tasks can use the mission’s prefix when generating task IDs.
  • Bug Fixes

    • Existing missions without a saved prefix continue to work as before.
    • Task ID formatting now handles uppercase prefix input consistently.
  • Chores

    • Updated database schema support for the new mission prefix field.
    • Added coverage for mission prefix persistence and task ID generation.

Add an optional `taskPrefix` field on Mission so a single mission's tickets
can use a distinct id prefix (e.g. ERR-) while the rest of the board keeps the
project-wide prefix (default FN-). Previously taskPrefix was project-scoped
only; flipping it changed every new ticket board-wide including autopilot.

The distributed id allocator was already multi-prefix, so this only threads a
per-mission prefix through triage:
- Mission.taskPrefix (nullable) persisted via a new additive migration (v140).
- MissionStore.triageFeature passes it into TaskCreateInput.taskPrefix (a
  transient minting hint, not persisted on the task).
- createTaskWithDistributedReservation prefers input.taskPrefix over settings.
- buildCommitMsgTrailerHook derives the strip PREFIX from the task id itself
  rather than the project-wide option, so ERR-5 strips correctly while the
  project prefix is still FN (fixes all engine call sites at once).
- Mission create + edit UI (MissionManager) exposes a Task prefix input;
  mission-routes POST/PATCH validate it (letter-led alphanumeric, uppercased).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 6, 2026 09:43

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds per-mission task prefixes for triaged task IDs. The main changes are:

  • A nullable taskPrefix column on missions.
  • Mission store threading from mission triage into task ID reservation.
  • Dashboard create and edit inputs for mission task prefixes.
  • API validation for prefix format and inheritance.
  • Commit hook prefix stripping based on the actual task ID.

Confidence Score: 4/5

The mission edit flow needs a fix before merging.

  • Clearing a mission prefix from the dashboard leaves the old stored prefix active.
  • The hook fallback path should escape custom prefixes before using them in generated shell and sed patterns.
  • The database migration and triage threading look consistent for normal create and update flows.

packages/dashboard/app/components/MissionManager.tsx; packages/engine/src/worktree-hooks.ts

Important Files Changed

Filename Overview
packages/core/src/db.ts Adds schema version 140 and a nullable mission taskPrefix column through an additive migration.
packages/core/src/mission-store.ts Persists mission taskPrefix and passes it into triaged task creation.
packages/core/src/store.ts Uses transient TaskCreateInput.taskPrefix as the distributed reservation prefix before falling back to project settings.
packages/dashboard/src/mission-routes.ts Adds server validation and POST/PATCH handling for mission task prefixes.
packages/dashboard/app/components/MissionManager.tsx Adds task prefix inputs, but the edit payload omits the field when users try to clear it.
packages/engine/src/worktree-hooks.ts Derives hook stripping prefixes from task IDs, with a remaining hardening issue on fallback prefixes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  UI[Mission create/edit form] --> API[Mission routes validation]
  API --> Store[MissionStore persists nullable taskPrefix]
  Store --> Triage[triageFeature reads mission taskPrefix]
  Triage --> TaskCreate[TaskCreateInput transient taskPrefix]
  TaskCreate --> Allocator[Distributed task ID reservation]
  Allocator --> Hook[Commit hook derives strip prefix from task ID]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
  UI[Mission create/edit form] --> API[Mission routes validation]
  API --> Store[MissionStore persists nullable taskPrefix]
  Store --> Triage[triageFeature reads mission taskPrefix]
  Triage --> TaskCreate[TaskCreateInput transient taskPrefix]
  TaskCreate --> Allocator[Distributed task ID reservation]
  Allocator --> Hook[Commit hook derives strip prefix from task ID]
Loading

Reviews (1): Last reviewed commit: "feat(missions): per-mission taskPrefix o..." | Re-trigger Greptile

autopilotEnabled: missionForm.autopilotEnabled,
baseBranch: missionForm.baseBranch.trim() || "",
branchStrategy,
taskPrefix: missionForm.taskPrefix.trim() || undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Cleared Prefix Is Omitted

When a mission already has taskPrefix set and the edit form is cleared, this line converts the empty input to undefined. JSON.stringify drops that key, so the PATCH route skips taskPrefix and the old prefix remains stored; future triaged tasks keep using the stale mission prefix instead of inheriting the project prefix.

const derivedPrefix = /^[A-Za-z][A-Za-z0-9]*-\d+$/.test(trimmedTaskId)
? trimmedTaskId.replace(/-\d+$/, "").toUpperCase()
: "";
const taskPrefix = (derivedPrefix || options.taskPrefix || "FN").trim() || "FN";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Fallback Prefix Is Unescaped

When taskId does not match the new numeric-id regex, this falls back to options.taskPrefix and later interpolates it directly into the generated case pattern and sed regex. A configured prefix containing regex or shell metacharacters can make the hook strip the wrong value or expand shell variables, producing an incorrect Fusion-Task-Id trailer for valid commits.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a per-mission taskPrefix field spanning the SQLite schema (v140 migration), core mission store persistence, task triage/distributed reservation prefix selection, worktree commit hook prefix derivation from task id, dashboard API types/routes, and MissionManager UI forms with corresponding tests.

Changes

Per-mission task prefix

Layer / File(s) Summary
Schema and type contracts
packages/core/src/db.ts, packages/core/src/mission-types.ts, packages/core/src/types.ts
Adds missions.taskPrefix column via v140 migration, bumps SCHEMA_VERSION, and extends Mission, MissionCreateInput, TaskCreateInput with optional taskPrefix.
Mission store persistence
packages/core/src/mission-store.ts, packages/core/src/__tests__/mission-store.test.ts
Reads/writes taskPrefix in row mapping, createMission, and updateMission; tests cover create, update, and legacy NULL rows.
Task triage prefix propagation
packages/core/src/mission-store.ts, packages/core/src/store.ts, packages/core/src/__tests__/mission-store.test.ts
triageFeature forwards mission's taskPrefix to createTask, and createTaskWithDistributedReservation prefers input.taskPrefix over project settings; tests verify minted task ids.
Worktree commit hook prefix derivation
packages/engine/src/worktree-hooks.ts, packages/engine/src/__tests__/worktree-hooks.test.ts
buildCommitMsgTrailerHook derives its strip prefix from the task id pattern, falling back to options.taskPrefix/"FN"; new test covers mismatched prefixes.
Dashboard API and UI wiring
packages/dashboard/app/api/legacy.ts, packages/dashboard/app/components/mission-types.ts, packages/dashboard/src/mission-routes.ts, packages/dashboard/app/components/MissionManager.tsx
Adds taskPrefix to dashboard Mission types and createMission client input, adds validateTaskPrefix validation in create/patch routes, and adds a "Task prefix" input across mission create/edit forms.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MissionManager
  participant LegacyAPI
  participant MissionRoutes
  participant MissionStore
  participant TaskStore
  MissionManager->>LegacyAPI: createMission/updateMission({ taskPrefix })
  LegacyAPI->>MissionRoutes: POST/PATCH /api/missions
  MissionRoutes->>MissionRoutes: validateTaskPrefix(taskPrefix)
  MissionRoutes->>MissionStore: createMission/updateMission(input)
  MissionStore-->>MissionRoutes: mission
  Note over MissionStore,TaskStore: later, during triage
  MissionStore->>TaskStore: createTask({ taskPrefix: mission.taskPrefix })
  TaskStore->>TaskStore: reserve id using input.taskPrefix override
  TaskStore-->>MissionStore: minted taskId
Loading

Suggested reviewers: gsxdsm

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: a per-mission taskPrefix override for triaged task IDs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/dashboard/app/components/MissionManager.tsx (1)

1657-1668: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Editing a mission cannot clear an existing taskPrefix.

The update payload sends taskPrefix: missionForm.taskPrefix.trim() || undefined. JSON.stringify drops keys with undefined values, so clearing the field in the UI and saving omits taskPrefix from the PATCH body entirely. The server's if (taskPrefix !== undefined) guard in mission-routes.ts then never fires, so the previously-set prefix is never reverted to "inherit project prefix" — contradicting the intended empty-means-inherit behavior for edits.

baseBranch already avoids this by sending "" explicitly instead of undefined (line 1665); taskPrefix should do the same.

🐛 Proposed fix
         const updates: Record<string, unknown> = {
           title: missionForm.title.trim(),
           description: missionForm.description.trim() || undefined,
           status: missionForm.status,
           autopilotEnabled: missionForm.autopilotEnabled,
           baseBranch: missionForm.baseBranch.trim() || "",
           branchStrategy,
-          taskPrefix: missionForm.taskPrefix.trim() || undefined,
+          taskPrefix: missionForm.taskPrefix.trim() || "",
         };
🤖 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 `@packages/dashboard/app/components/MissionManager.tsx` around lines 1657 -
1668, The edit flow in MissionManager’s update payload is dropping an
intentionally cleared taskPrefix because it sends undefined, so the PATCH body
omits the field and the server never resets it to inherit the project prefix.
Update the MissionManager save logic where the edits are built so taskPrefix is
sent explicitly as an empty string when cleared, matching the existing
baseBranch handling, and keep the mission-routes.ts update guard compatible with
that payload shape.
🧹 Nitpick comments (2)
packages/engine/src/worktree-hooks.ts (1)

210-219: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LGTM — prefix derivation logic is sound.

Regex-based derivation correctly handles per-mission prefixes, and the fallback to options.taskPrefix/"FN" for malformed task ids is a sensible safety net. Consider adding a unit test for the fallback path (e.g., a taskId that doesn't match <PREFIX>-<digits>) to lock in that behavior, but it's not blocking.

🤖 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 `@packages/engine/src/worktree-hooks.ts` around lines 210 - 219, Add a unit
test for the task prefix fallback behavior in worktree-hooks: verify that the
logic in the taskId-derived prefix path falls back to options.taskPrefix, and
then to "FN", when taskId does not match the "<PREFIX>-<digits>" pattern. Use
the prefix derivation block around trimmedTaskId, derivedPrefix, and taskPrefix
to locate the code, and cover at least one malformed taskId case so the fallback
behavior stays locked in.
packages/core/src/mission-store.ts (1)

654-698: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

No format validation of taskPrefix at the store layer.

createMission/updateMission persist input.taskPrefix/updates.taskPrefix verbatim with no shape check (letter-led alphanumeric, uppercase). Per the PR description, validation lives in the dashboard mission-routes handlers, so any direct caller of MissionStore (scripts, plugins, future internal callers, or applyMissionHierarchySnapshot) can persist a malformed prefix that later flows into task-id minting. This matches the existing convention for branchStrategy/baseBranch (also unvalidated here), so it's not a regression, but worth a defense-in-depth note.

Also applies to: 1261-1311

🤖 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 `@packages/core/src/mission-store.ts` around lines 654 - 698,
`MissionStore.createMission` (and the matching update path) currently persists
`taskPrefix` without validating its shape, so direct callers can store malformed
prefixes that later break task-id generation. Add defense-in-depth validation in
`MissionStore` itself, reusing the same letter-led uppercase alphanumeric rules
enforced by the dashboard mission-routes handlers before writing
`input.taskPrefix`/`updates.taskPrefix` to the database. Make sure the same
check also covers internal entry points like `applyMissionHierarchySnapshot` so
all store-level writes stay consistent.
🤖 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.

Outside diff comments:
In `@packages/dashboard/app/components/MissionManager.tsx`:
- Around line 1657-1668: The edit flow in MissionManager’s update payload is
dropping an intentionally cleared taskPrefix because it sends undefined, so the
PATCH body omits the field and the server never resets it to inherit the project
prefix. Update the MissionManager save logic where the edits are built so
taskPrefix is sent explicitly as an empty string when cleared, matching the
existing baseBranch handling, and keep the mission-routes.ts update guard
compatible with that payload shape.

---

Nitpick comments:
In `@packages/core/src/mission-store.ts`:
- Around line 654-698: `MissionStore.createMission` (and the matching update
path) currently persists `taskPrefix` without validating its shape, so direct
callers can store malformed prefixes that later break task-id generation. Add
defense-in-depth validation in `MissionStore` itself, reusing the same
letter-led uppercase alphanumeric rules enforced by the dashboard mission-routes
handlers before writing `input.taskPrefix`/`updates.taskPrefix` to the database.
Make sure the same check also covers internal entry points like
`applyMissionHierarchySnapshot` so all store-level writes stay consistent.

In `@packages/engine/src/worktree-hooks.ts`:
- Around line 210-219: Add a unit test for the task prefix fallback behavior in
worktree-hooks: verify that the logic in the taskId-derived prefix path falls
back to options.taskPrefix, and then to "FN", when taskId does not match the
"<PREFIX>-<digits>" pattern. Use the prefix derivation block around
trimmedTaskId, derivedPrefix, and taskPrefix to locate the code, and cover at
least one malformed taskId case so the fallback behavior stays locked in.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ee38844e-e6c9-4514-8ed5-c763bf6404b2

📥 Commits

Reviewing files that changed from the base of the PR and between 6c9989c and 795f5d4.

📒 Files selected for processing (12)
  • packages/core/src/__tests__/mission-store.test.ts
  • packages/core/src/db.ts
  • packages/core/src/mission-store.ts
  • packages/core/src/mission-types.ts
  • packages/core/src/store.ts
  • packages/core/src/types.ts
  • packages/dashboard/app/api/legacy.ts
  • packages/dashboard/app/components/MissionManager.tsx
  • packages/dashboard/app/components/mission-types.ts
  • packages/dashboard/src/mission-routes.ts
  • packages/engine/src/__tests__/worktree-hooks.test.ts
  • packages/engine/src/worktree-hooks.ts

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.

2 participants