feat(FN-2605): merge fusion/fn-2605 (auto-resolved)
- test(FN-2605): complete Step 4 — align tests with planning labels - docs(FN-2605): complete Step 3 — update demo and script terminology - docs(FN-2605): complete Step 2 — update docs terminology - docs(FN-2605): complete Step 1 — update README terminology
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.
|
||||
20
README.md
20
README.md
@@ -23,7 +23,7 @@
|
||||
<br />
|
||||
|
||||
<a href="https://runfusion.ai">
|
||||
<img src="https://runfusion.ai/fusion-dashboard.png" alt="Fusion dashboard: Triage, Todo, In Progress, In Review, Done kanban columns with active task cards" width="900" />
|
||||
<img src="https://runfusion.ai/fusion-dashboard.png" alt="Fusion dashboard: Planning, Todo, In Progress, In Review, Done kanban columns with active task cards" width="900" />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
## Your entire dev environment. On a single pane of glass.
|
||||
|
||||
Describe a task in plain language. A triage agent reads your project, understands context, and writes a full `PROMPT.md` spec — steps, file scope, acceptance criteria. Then Fusion plans, reviews, executes, and reviews again, in an isolated git worktree, with a human approval gate wherever you want one.
|
||||
Describe a task in plain language. A planning agent reads your project, understands context, and writes a full `PROMPT.md` plan — steps, file scope, acceptance criteria. Then Fusion plans, reviews, executes, and reviews again, in an isolated git worktree, with a human approval gate wherever you want one.
|
||||
|
||||
One board. Controlled from anywhere. Laptop, Mac mini, Linux server, cloud VM, phone — all connected.
|
||||
|
||||
@@ -43,7 +43,7 @@ One board. Controlled from anywhere. Laptop, Mac mini, Linux server, cloud VM, p
|
||||
## The flow
|
||||
|
||||
```
|
||||
① Describe ② Triage ③ The board ④ Isolated worktree
|
||||
① Describe ② Planning ③ The board ④ Isolated worktree
|
||||
───────────── ───────────── ───────────── ─────────────────────
|
||||
"Add dark mode → Agent writes → Plan → Review → → fusion/FN-123 branch
|
||||
toggle to PROMPT.md Execute → Review concurrent, zero
|
||||
@@ -65,7 +65,7 @@ Every task shows its plan, its reviews, its diffs, and its file changes in real
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| 🧠 **AI specification** | Describe a task in plain language. Triage agents turn it into a `PROMPT.md` spec with steps, file scope, and acceptance criteria. |
|
||||
| 🧠 **AI planning** | Describe a task in plain language. Planning agents turn it into a `PROMPT.md` plan with steps, file scope, and acceptance criteria. |
|
||||
| 🔁 **Workflow gates** | Plan → Review → Execute → Review on every step. Pre-merge gates block bad code; post-merge gates run informational checks. |
|
||||
| 🌳 **Worktree isolation** | Each task runs in its own branch and worktree (`fusion/{task-id}`). Parallel tasks. Zero conflicts. |
|
||||
| ⚡ **Smart merge** | Passing every gate? Fusion squash-merges and moves on. Opt into manual approval anywhere. |
|
||||
@@ -83,7 +83,7 @@ Every task shows its plan, its reviews, its diffs, and its file changes in real
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
H((You)) -->|rough idea| T["Triage<br/><i>auto-specification</i>"]
|
||||
H((You)) -->|rough idea| T["Planning<br/><i>auto-planning</i>"]
|
||||
T --> TD["Todo<br/><i>scheduled for execution</i>"]
|
||||
TD --> IP["In Progress<br/><i>for each step:<br/>plan, review, execute, review</i>"]
|
||||
|
||||
@@ -115,7 +115,7 @@ graph TD
|
||||
style D fill:#1a1a1a,stroke:#8b949e,color:#8b949e
|
||||
```
|
||||
|
||||
Tasks with dependencies are processed sequentially. Independent tasks run in parallel. Optionally require manual approval before tasks move from Triage to Todo (`requirePlanApproval` setting).
|
||||
Tasks with dependencies are processed sequentially. Independent tasks run in parallel. Optionally require manual approval before tasks move from Planning to Todo (`requirePlanApproval` setting).
|
||||
|
||||
---
|
||||
|
||||
@@ -306,7 +306,7 @@ For Capacitor + PWA workflow, see [MOBILE.md](./MOBILE.md).
|
||||
|
||||
## Core features
|
||||
|
||||
- **AI Specification** — Triage agent generates detailed `PROMPT.md` with steps, file scope, and acceptance criteria
|
||||
- **AI Planning** — Planning agent generates detailed `PROMPT.md` with steps, file scope, and acceptance criteria
|
||||
- **Step-by-step Execution** — Plan → Review → Execute → Review cycle for each task step
|
||||
- **Git Worktree Isolation** — Each task runs in its own worktree (`fusion/{task-id}` branch)
|
||||
- **Workflow Steps** — Configurable quality gates (pre-merge: blocks merge; post-merge: informational)
|
||||
@@ -331,7 +331,7 @@ Fusion uses a dual-scope model hierarchy with five independent lanes. Global set
|
||||
| Lane | Purpose | Global Baseline Keys | Project Override Keys |
|
||||
|------|---------|---------------------|----------------------|
|
||||
| Executor | Task execution agent | `executionGlobalProvider` + `executionGlobalModelId` | `executionProvider` + `executionModelId` |
|
||||
| Planning/Triage | Task specification agent | `planningGlobalProvider` + `planningGlobalModelId` | `planningProvider` + `planningModelId` |
|
||||
| Planning | Task planning agent | `planningGlobalProvider` + `planningGlobalModelId` | `planningProvider` + `planningModelId` |
|
||||
| Validator | Plan/code reviewer | `validatorGlobalProvider` + `validatorGlobalModelId` | `validatorProvider` + `validatorModelId` |
|
||||
| Title Summarization | Auto-title generation | `titleSummarizerGlobalProvider` + `titleSummarizerGlobalModelId` | `titleSummarizerProvider` + `titleSummarizerModelId` |
|
||||
| Workflow Step Refinement | AI prompt refinement | (uses `defaultProvider`/`defaultModelId`) | (uses `modelProvider`/`modelId` on WorkflowStep) |
|
||||
@@ -403,7 +403,7 @@ Routines are AI agent tasks triggered by cron schedules, webhooks, or manual exe
|
||||
## CLI quick examples
|
||||
|
||||
```bash
|
||||
fn task create "Fix the login bug" # Quick entry → triage
|
||||
fn task create "Fix the login bug" # Quick entry → planning
|
||||
fn task plan "Build auth system" # AI-guided planning
|
||||
fn task import owner/repo --labels bug # Import GitHub issues
|
||||
fn task show FN-001 # View task details
|
||||
@@ -431,7 +431,7 @@ fn skills install firebase/agent-skills # Install agent skills
|
||||
|---------|-------------|
|
||||
| `@fusion/core` | Domain model — tasks, board columns, SQLite store |
|
||||
| `@fusion/dashboard` | Web UI — Express server + kanban board with SSE |
|
||||
| `@fusion/engine` | AI engine — triage, execution, scheduling, workflow steps |
|
||||
| `@fusion/engine` | AI engine — planning, execution, scheduling, workflow steps |
|
||||
| `@runfusion/fusion` | CLI + pi extension — published to npm |
|
||||
|
||||
---
|
||||
|
||||
20
demo/seed.ts
20
demo/seed.ts
@@ -6,8 +6,8 @@
|
||||
* - Done: shipped features
|
||||
* - In Review: finished work waiting for merge
|
||||
* - In Progress: agents actively executing (with steps partially done)
|
||||
* - Todo: specified and queued
|
||||
* - Triage: raw ideas just landing
|
||||
* - Todo: planned and queued
|
||||
* - Planning: raw ideas just landing
|
||||
*/
|
||||
import { TaskStore } from "../packages/core/src/index.js";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
@@ -66,7 +66,7 @@ async function main() {
|
||||
doneIds.push(task.id);
|
||||
await store.updateTask(task.id, { size: t.size, reviewLevel: t.reviewLevel });
|
||||
|
||||
// Move through the pipeline: triage → todo → in-progress → in-review → done
|
||||
// Move through the pipeline: planning → todo → in-progress → in-review → done
|
||||
await store.moveTask(task.id, "todo");
|
||||
await store.moveTask(task.id, "in-progress");
|
||||
|
||||
@@ -221,9 +221,9 @@ async function main() {
|
||||
await writePrompt(store, task.id, t.title, t.desc, steps, t.deps);
|
||||
}
|
||||
|
||||
// ── Triage ────────────────────────────────────────────────────────
|
||||
// ── Planning ──────────────────────────────────────────────────────
|
||||
|
||||
const triage = [
|
||||
const planning = [
|
||||
{
|
||||
desc: "Users are reporting slow page loads on the dashboard when they have more than 200 tasks. Probably need virtual scrolling or pagination in the UI.",
|
||||
},
|
||||
@@ -241,7 +241,7 @@ async function main() {
|
||||
},
|
||||
];
|
||||
|
||||
for (const t of triage) {
|
||||
for (const t of planning) {
|
||||
await store.createTask({ description: t.desc });
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ async function main() {
|
||||
}
|
||||
|
||||
console.log(`\nSeeded ${tasks.length} tasks:`);
|
||||
console.log(` Triage: ${byColumn["triage"] || 0}`);
|
||||
console.log(` Planning: ${byColumn["triage"] || 0}`);
|
||||
console.log(` Todo: ${byColumn["todo"] || 0}`);
|
||||
console.log(` In Progress: ${byColumn["in-progress"] || 0}`);
|
||||
console.log(` In Review: ${byColumn["in-review"] || 0}`);
|
||||
@@ -341,7 +341,7 @@ ${stepsSection}
|
||||
async function addLogs(store: TaskStore, id: string, targetColumn: string) {
|
||||
const actions: Record<string, string[][]> = {
|
||||
done: [
|
||||
["Triage complete — spec written", "approved"],
|
||||
["Planning complete — plan written", "approved"],
|
||||
["Scheduled for execution", "worktree created"],
|
||||
["Step 0 started", "in-progress"],
|
||||
["Review: step 0", "approved"],
|
||||
@@ -355,7 +355,7 @@ async function addLogs(store: TaskStore, id: string, targetColumn: string) {
|
||||
["Auto-merged into main"],
|
||||
],
|
||||
"in-review": [
|
||||
["Triage complete — spec written", "approved"],
|
||||
["Planning complete — plan written", "approved"],
|
||||
["Scheduled for execution", "worktree created"],
|
||||
["Step 0 started", "in-progress"],
|
||||
["Review: step 0", "approved"],
|
||||
@@ -368,7 +368,7 @@ async function addLogs(store: TaskStore, id: string, targetColumn: string) {
|
||||
["All steps complete — moved to review"],
|
||||
],
|
||||
"in-progress": [
|
||||
["Triage complete — spec written", "approved"],
|
||||
["Planning complete — plan written", "approved"],
|
||||
["Scheduled for execution", "worktree created"],
|
||||
["Step 0 started", "in-progress"],
|
||||
["Review: step 0", "approved"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Simulates live board activity — tasks progressing, new ideas landing,
|
||||
* triage completing, reviews finishing. Run alongside `kb dashboard`.
|
||||
* planning completing, reviews finishing. Run alongside `kb dashboard`.
|
||||
*
|
||||
* Usage: `npx tsx demo/simulate.ts [dir]`
|
||||
*
|
||||
@@ -44,7 +44,7 @@ async function main() {
|
||||
|
||||
while (true) {
|
||||
const tasks = await store.listTasks();
|
||||
const triage = tasks.filter((t) => t.column === "triage" && !t.paused);
|
||||
const planning = tasks.filter((t) => t.column === "triage" && !t.paused);
|
||||
const inProgress = tasks.filter((t) => t.column === "in-progress" && !t.paused);
|
||||
const inReview = tasks.filter((t) => t.column === "in-review" && !t.paused);
|
||||
const todo = tasks.filter((t) => t.column === "todo" && !t.paused);
|
||||
@@ -53,23 +53,23 @@ async function main() {
|
||||
const roll = Math.random();
|
||||
|
||||
if (roll < 0.2 && ideaIndex < NEW_TASK_IDEAS.length) {
|
||||
// New task lands in triage
|
||||
// New task lands in planning
|
||||
const desc = NEW_TASK_IDEAS[ideaIndex++];
|
||||
const task = await store.createTask({ description: desc });
|
||||
console.log(` + New task: ${task.id} — "${desc.slice(0, 50)}..."`);
|
||||
await sleep(2000 + Math.random() * 3000);
|
||||
} else if (roll < 0.4 && triage.length > 0) {
|
||||
// Triage completes — task gets spec'd and moves to todo
|
||||
const task = pick(triage);
|
||||
} else if (roll < 0.4 && planning.length > 0) {
|
||||
// Planning completes — task gets planned and moves to todo
|
||||
const task = pick(planning);
|
||||
const title = task.description.slice(0, 60).replace(/\.$/, "");
|
||||
await store.updateTask(task.id, {
|
||||
title,
|
||||
size: pick(["S", "M", "L"] as const),
|
||||
reviewLevel: pick([0, 1, 1, 2, 2, 3]),
|
||||
});
|
||||
await store.logEntry(task.id, "Triage complete — spec written", "approved");
|
||||
await store.logEntry(task.id, "Planning complete — plan written", "approved");
|
||||
await store.moveTask(task.id, "todo");
|
||||
console.log(` ✓ Triaged: ${task.id} → todo`);
|
||||
console.log(` ✓ Planned: ${task.id} → todo`);
|
||||
await sleep(3000 + Math.random() * 4000);
|
||||
} else if (roll < 0.6 && todo.length > 0 && inProgress.length < 3) {
|
||||
// Scheduler picks up a todo task
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[← Back to repository root](../README.md)
|
||||
|
||||
Fusion is an AI-orchestrated task board that turns ideas into reviewed, merged code using a structured workflow: **triage → todo → in-progress → in-review → done**.
|
||||
Fusion is an AI-orchestrated task board that turns ideas into reviewed, merged code using a structured workflow: **planning → todo → in-progress → in-review → done**.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -151,10 +151,10 @@ Recommended priority: first add ownership/inbox primitives (checkout + inbox-lit
|
||||
### 12) Approval workflows
|
||||
**Status:** PARTIAL
|
||||
**Description:** Approval entities linked to issues, reviewed first on wake.
|
||||
**Current State:** Triaged specs can require manual approval (`awaiting-approval` + approve/reject routes).
|
||||
**Current State:** Planned tasks can require manual approval (`awaiting-approval` + approve/reject routes).
|
||||
**Gap:** No standalone approval objects with linked issue resolution metadata.
|
||||
**Recommendation:** Add optional approval domain only if needed beyond existing triage-plan approval.
|
||||
**Files Affected:** `packages/engine/src/triage.ts`, `packages/dashboard/src/routes.ts`, `packages/core/src/types.ts`
|
||||
**Recommendation:** Add optional approval domain only if needed beyond existing planning approval.
|
||||
**Files Affected:** `packages/engine/src/planning.ts`, `packages/dashboard/src/routes.ts`, `packages/core/src/types.ts`
|
||||
|
||||
### 13) Self-assignment constraints
|
||||
**Status:** MISSING
|
||||
@@ -167,7 +167,7 @@ Recommended priority: first add ownership/inbox primitives (checkout + inbox-lit
|
||||
### 14) Status value parity
|
||||
**Status:** PARTIAL
|
||||
**Description:** Paperclip issue statuses differ from Fusion’s board columns/status fields.
|
||||
**Current State:** Fusion has columns (`triage`, `todo`, `in-progress`, `in-review`, `done`, `archived`) and ad-hoc task status strings.
|
||||
**Current State:** Fusion has columns (`planning`, `todo`, `in-progress`, `in-review`, `done`, `archived`) and ad-hoc task status strings.
|
||||
**Gap:** No direct `blocked/cancelled/backlog` canonical lifecycle.
|
||||
**Recommendation:** Decide whether to add a normalized execution status enum alongside board columns.
|
||||
**Files Affected:** `packages/core/src/types.ts`, `packages/core/src/store.ts`, `packages/dashboard/src/routes.ts`, `packages/engine/src/scheduler.ts`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[← Docs index](./README.md)
|
||||
|
||||
Fusion uses multiple agent roles for triage, execution, review, and merge workflows.
|
||||
Fusion uses multiple agent roles for planning, execution, review, and merge workflows.
|
||||
|
||||
## Agent Field Parity Matrix
|
||||
|
||||
@@ -64,7 +64,7 @@ The `taskId` field is suppressed in API responses when the linked task is in a t
|
||||
- `GET /api/agents/stats` — `assignedTaskCount` excludes agents with terminal linked tasks
|
||||
|
||||
**Non-terminal task statuses (taskId is preserved):**
|
||||
- `triage`
|
||||
- `planning`
|
||||
- `todo`
|
||||
- `in-progress`
|
||||
- `in-review`
|
||||
@@ -126,12 +126,12 @@ Agent deletion is available from both the detail header lifecycle controls and t
|
||||
Fusion includes built-in templates for role prompts:
|
||||
|
||||
- `default-executor`
|
||||
- `default-triage`
|
||||
- `default-planning`
|
||||
- `default-reviewer`
|
||||
- `default-merger`
|
||||
- `senior-engineer`
|
||||
- `strict-reviewer`
|
||||
- `concise-triage`
|
||||
- `concise-planning`
|
||||
|
||||
These can be assigned per role using `agentPrompts.roleAssignments`.
|
||||
|
||||
@@ -235,7 +235,7 @@ agent-presets/
|
||||
├── designer/soul.md
|
||||
├── marketing-manager/soul.md
|
||||
├── technical-writer/soul.md
|
||||
├── triage/soul.md
|
||||
├── planning/soul.md
|
||||
└── reviewer/soul.md
|
||||
```
|
||||
|
||||
@@ -302,11 +302,11 @@ The **Prompts** section in the Settings modal provides a user-friendly interface
|
||||
| `executor-guardrails` | executor | Behavioral guardrails and constraints |
|
||||
| `executor-spawning` | executor | Instructions for spawning child agents |
|
||||
| `executor-completion` | executor | Completion criteria and signaling |
|
||||
| `triage-welcome` | triage | Introductory section for the triage/specification agent |
|
||||
| `triage-context` | triage | Context-gathering instructions |
|
||||
| `triage-welcome` | planning | Introductory section for the planning agent |
|
||||
| `triage-context` | planning | Context-gathering instructions |
|
||||
| `reviewer-verdict` | reviewer | Verdict criteria and format |
|
||||
| `merger-conflicts` | merger | Merge conflict resolution instructions |
|
||||
| `agent-generation-system` | — | System prompt for AI-assisted agent specification generation |
|
||||
| `agent-generation-system` | — | System prompt for AI-assisted agent plan generation |
|
||||
| `workflow-step-refine` | — | System prompt for refining workflow step descriptions |
|
||||
|
||||
### How It Works
|
||||
@@ -433,7 +433,7 @@ Heartbeat runs from the Agents panel run on a **separate control-plane lane** th
|
||||
|
||||
| Component | Path | Concurrency |
|
||||
|-----------|------|------------|
|
||||
| TriageProcessor | Task lane | Semaphore-gated |
|
||||
| PlanningProcessor | Task lane | Semaphore-gated |
|
||||
| TaskExecutor | Task lane | Semaphore-gated |
|
||||
| Scheduler | Task lane | Semaphore-gated |
|
||||
| onMerge | Task lane | Semaphore-gated |
|
||||
|
||||
@@ -8,7 +8,7 @@ This document describes the actual architecture of Fusion as implemented in this
|
||||
|
||||
## 1) Overview
|
||||
|
||||
Fusion is an AI-orchestrated task board. It takes tasks through a structured lifecycle (`triage → todo → in-progress → in-review → done → archived`) and automates triage, execution, review, merge, and operational recovery.
|
||||
Fusion is an AI-orchestrated task board. It takes tasks through a structured lifecycle (`planning → todo → in-progress → in-review → done → archived`) and automates planning, execution, review, merge, and operational recovery.
|
||||
|
||||
At a high level, Fusion is split into:
|
||||
- **Core domain + persistence** (`@fusion/core`)
|
||||
@@ -37,7 +37,7 @@ At a high level, Fusion is split into:
|
||||
│ │
|
||||
┌────────▼───────────────────────▼───────┐
|
||||
│ Engine Runtime │
|
||||
│ Scheduler / Triage / Executor / Merger │
|
||||
│ Scheduler / Planning / Executor / Merger │
|
||||
│ Heartbeat / Self-healing / Autopilot │
|
||||
└────────┬───────────────────────┬────────┘
|
||||
│ │
|
||||
@@ -61,7 +61,7 @@ At a high level, Fusion is split into:
|
||||
| Package | Published | Role | Key files |
|
||||
|---|---|---|---|
|
||||
| `@fusion/core` | Private | Domain model, stores, SQLite adapters, settings, shared types | `packages/core/src/types.ts`, `store.ts`, `db.ts`, `central-core.ts`, `agent-store.ts` |
|
||||
| `@fusion/engine` | Private | AI orchestration runtime (triage, scheduler, executor, merger, recovery) | `packages/engine/src/triage.ts`, `scheduler.ts`, `executor.ts`, `merger.ts`, `project-runtime.ts` |
|
||||
| `@fusion/engine` | Private | AI orchestration runtime (planning, scheduler, executor, merger, recovery) | planning processor, `scheduler.ts`, `executor.ts`, `merger.ts`, `project-runtime.ts` |
|
||||
| `@fusion/dashboard` | Private | Express API server + React app | `packages/dashboard/src/server.ts`, `routes.ts`, `sse.ts`, `websocket.ts`, `packages/dashboard/app/App.tsx` |
|
||||
| `@runfusion/fusion` | **Published** | CLI binary (`fn`) + Pi extension | `packages/cli/src/bin.ts`, `commands/*`, `project-resolver.ts`, `extension.ts` |
|
||||
| `@fusion/desktop` | Private | Electron shell around Fusion dashboard/client | `packages/desktop/src/main.ts`, `ipc.ts`, `preload.ts`, `scripts/build.ts` |
|
||||
@@ -284,7 +284,7 @@ The QMD backend (`qmd`) delegates read/write I/O to the file backend and schedul
|
||||
**Dashboard API:**
|
||||
- `GET /api/memory/backend` — Returns current backend status and capabilities
|
||||
|
||||
See [Memory Plugin Contract](./memory-plugin-contract.md) for the full specification.
|
||||
See [Memory Plugin Contract](./memory-plugin-contract.md) for the full plan.
|
||||
|
||||
---
|
||||
|
||||
@@ -293,7 +293,7 @@ See [Memory Plugin Contract](./memory-plugin-contract.md) for the full specifica
|
||||
`@fusion/engine` executes the autonomous workflow.
|
||||
|
||||
### Agent roles
|
||||
- **Triage**: `TriageProcessor` (`triage.ts`) generates task specs (`PROMPT.md`) and selects eligible triage tasks by priority first, then FIFO (`createdAt` ascending) within each priority tier.
|
||||
- **Planning**: the planning processor generates task plans (`PROMPT.md`) and selects eligible planning tasks by priority first, then FIFO (`createdAt` ascending) within each priority tier.
|
||||
- **Executor**: `TaskExecutor` (`executor.ts`) implements tasks in worktrees
|
||||
- **Reviewer**: `reviewStep()` (`reviewer.ts`) performs plan/code reviews
|
||||
- **Merger**: `aiMergeTask()` (`merger.ts`) merges approved work
|
||||
@@ -596,14 +596,14 @@ Some data remains intentionally filesystem-based:
|
||||
## 9) Task Lifecycle
|
||||
|
||||
Lifecycle constants are defined in `packages/core/src/types.ts`:
|
||||
- Columns: `triage`, `todo`, `in-progress`, `in-review`, `done`, `archived`
|
||||
- Columns: `planning`, `todo`, `in-progress`, `in-review`, `done`, `archived`
|
||||
- Transition rules via `VALID_TRANSITIONS`
|
||||
|
||||
### Lifecycle flow
|
||||
|
||||
```text
|
||||
triage
|
||||
│ (TriageProcessor writes PROMPT.md)
|
||||
planning
|
||||
│ (Planning processor writes PROMPT.md)
|
||||
▼
|
||||
todo
|
||||
│ (Scheduler selects task, dependencies satisfied)
|
||||
@@ -620,7 +620,7 @@ done
|
||||
```
|
||||
|
||||
### Execution detail
|
||||
- **Triage phase**: `TriageProcessor` generates executable spec
|
||||
- **Planning phase**: the planning processor generates an executable plan
|
||||
- **Execution phase**: `TaskExecutor` performs implementation, tool calls, tests/build commands
|
||||
- **Review phase**: optional `reviewStep()` workflow depending on prompt review level (bypassed in fast mode)
|
||||
- **Merge phase**: `aiMergeTask()` handles merge strategy and post-merge workflow steps
|
||||
@@ -641,7 +641,7 @@ Task steps use statuses: `pending`, `in-progress`, `done`, `skipped`.
|
||||
|
||||
Fusion has two complementary agent models:
|
||||
|
||||
1. **Task pipeline agents** (triage/executor/reviewer/merger) managed by engine runtime
|
||||
1. **Task pipeline agents** (planning/executor/reviewer/merger) managed by engine runtime
|
||||
2. **Persistent registered agents** managed by `AgentStore`
|
||||
|
||||
### Persistent agent storage
|
||||
|
||||
@@ -71,7 +71,7 @@ fn dashboard --dev
|
||||
| `--no-auth` | Disable bearer-token auth. Not recommended when binding to `0.0.0.0`. |
|
||||
| `--paused` | Start with the engine paused (automation disabled). |
|
||||
| `--interactive` | Interactive port selection. |
|
||||
| `--dev` | Start dashboard only (no AI engine, no triage/scheduler). |
|
||||
| `--dev` | Start dashboard only (no AI engine, no planning/scheduler). |
|
||||
|
||||
### Interactive Terminal UI (TTY Mode)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ pnpm build
|
||||
|---|---|
|
||||
| `@fusion/core` | Shared domain types, stores, persistence, and core utilities |
|
||||
| `@fusion/dashboard` | Express API + React UI (including dashboard TUI in CLI) |
|
||||
| `@fusion/engine` | Scheduling, triage, execution, merge orchestration |
|
||||
| `@fusion/engine` | Scheduling, planning, execution, merge orchestration |
|
||||
| `@fusion/desktop` | Electron shell around Fusion dashboard/client |
|
||||
| `@fusion/mobile` | Capacitor + PWA mobile packaging |
|
||||
| `@fusion/plugin-sdk` | Plugin SDK for building Fusion extensions |
|
||||
|
||||
@@ -37,7 +37,7 @@ Method used:
|
||||
| dashboard | 8 | `mission-routes.ts`, `script-store.ts`, `subtask-breakdown.ts`, `terminal.ts`, `plugins/{network,splash-screen,status-bar}.ts`, `test-request.ts` |
|
||||
| cli | 4 | `commands/settings-export.ts`, `commands/settings-import.ts`, `companies-sh-parser.ts`, `runtime/native-patch.ts` |
|
||||
|
||||
### Finding 2.2 — Risk triage for key backend files
|
||||
### Finding 2.2 — Risk review for key backend files
|
||||
|
||||
- **High**
|
||||
- `packages/dashboard/src/subtask-breakdown.ts` — AI/session orchestration and persistence interactions; only route-level behavior is exercised.
|
||||
|
||||
@@ -191,7 +191,7 @@ You can create tasks from the board or CLI.
|
||||
|
||||
1. Type a short request in the quick entry input.
|
||||
2. Press Enter.
|
||||
3. Task appears in **Triage** and the triage agent generates `PROMPT.md`.
|
||||
3. Task appears in **Planning** and the planning agent generates `PROMPT.md`.
|
||||
|
||||
### Option B: Plan Mode (Board)
|
||||
|
||||
@@ -231,8 +231,8 @@ fn task plan "Implement role-based access control"
|
||||
|
||||
Fusion uses six columns:
|
||||
|
||||
1. **Triage** — raw idea; AI writes spec
|
||||
2. **Todo** — specified and queued
|
||||
1. **Planning** — raw idea; AI writes plan
|
||||
2. **Todo** — planned and queued
|
||||
3. **In Progress** — executor implements in a dedicated worktree
|
||||
4. **In Review** — implementation complete, awaiting merge/finalization
|
||||
5. **Done** — merged and complete
|
||||
|
||||
@@ -55,7 +55,7 @@ Fusion currently has two related but distinct memory systems:
|
||||
| `readProjectMemoryWithBackend()` | Backend-aware read helper |
|
||||
| `searchProjectMemory()`, `getProjectMemory()` | Backend-aware search/get wrappers |
|
||||
| `resolveMemoryInstructionContext()` | Backend-aware instruction context |
|
||||
| `buildTriageMemoryInstructions()` | Triage prompt memory instructions |
|
||||
| Planning instruction builder | Planning prompt memory instructions |
|
||||
| `buildExecutionMemoryInstructions()` | Executor prompt memory instructions |
|
||||
| `buildReviewerMemoryInstructions()` | Reviewer prompt memory instructions |
|
||||
| `readProjectMemory()` | Direct canonical long-term file read |
|
||||
@@ -319,10 +319,10 @@ Contract-critical behavior is covered by:
|
||||
- `packages/core/src/project-memory.test.ts`
|
||||
- canonical long-term path bootstrap/read behavior
|
||||
- `resolveMemoryInstructionContext()` branching
|
||||
- triage/execution/reviewer instruction generation per backend
|
||||
- planning/execution/reviewer instruction generation per backend
|
||||
- `packages/core/src/store.test.ts`
|
||||
- memory bootstrap behavior when memory is enabled/disabled and toggled
|
||||
- `packages/engine/src/triage.test.ts` and `packages/engine/src/executor.test.ts`
|
||||
- Engine planning and executor tests
|
||||
- memory instruction injection behavior by settings/backend
|
||||
|
||||
---
|
||||
|
||||
@@ -53,7 +53,7 @@ The dashboard supports mission planning workflows where you can:
|
||||
When missions are created through the interview planning workflow, Fusion automatically generates contract assertions for each feature:
|
||||
|
||||
- **Assertion text source priority**: `acceptanceCriteria` → `feature.description` → fallback text (`"Verify implementation of: {feature.title}"`)
|
||||
- **Assertions are linked to features**: Each auto-generated assertion is automatically linked to its feature, enabling mission validation rollup and enriched triage context
|
||||
- **Assertions are linked to features**: Each auto-generated assertion is automatically linked to its feature, enabling mission validation rollup and enriched planning context
|
||||
- **Verification fields**: Milestone and slice verification criteria from the interview are stored in dedicated `verification` fields rather than concatenated into descriptions
|
||||
- **Partial plans handled**: Auto-generation is robust to partial plans (missing slices/features or empty criteria) without throwing errors
|
||||
|
||||
@@ -87,13 +87,13 @@ Typical flow:
|
||||
|
||||
## `autopilotEnabled` vs `autoAdvance`
|
||||
|
||||
- **`autopilotEnabled`**: primary control for autopilot behavior — enables background monitoring, orchestration, and automatic slice activation when a slice completes. Also triggers auto-triage (converting features to tasks) when a slice is activated.
|
||||
- **`autopilotEnabled`**: primary control for autopilot behavior — enables background monitoring, orchestration, and automatic slice activation when a slice completes. Also triggers auto-planning (converting features to tasks) when a slice is activated.
|
||||
- **`autoAdvance`**: legacy fallback for backward compatibility with existing mission data. Kept for compatibility — new missions should use `autopilotEnabled`.
|
||||
|
||||
**Auto-triage behavior:**
|
||||
**Auto-planning behavior:**
|
||||
|
||||
- `autopilotEnabled=true` → features in activated slices are automatically triaged (converted to tasks)
|
||||
- `autopilotEnabled=false`, `autoAdvance=true` → features are triaged (legacy compat)
|
||||
- `autopilotEnabled=true` → features in activated slices are automatically planned (converted to tasks)
|
||||
- `autopilotEnabled=false`, `autoAdvance=true` → features are planned (legacy compat)
|
||||
- `autopilotEnabled=false`, `autoAdvance=false` → manual slice activation only
|
||||
|
||||
**Slice progression (on slice completion):**
|
||||
@@ -144,7 +144,7 @@ interface MissionContractAssertion {
|
||||
id: string; // e.g., "CA-A3B7CD-E9F2"
|
||||
milestoneId: string; // Parent milestone
|
||||
title: string; // Human-readable title
|
||||
assertion: string; // Behavioral specification
|
||||
assertion: string; // Behavioral plan
|
||||
status: AssertionStatus; // pending | passed | failed | blocked
|
||||
orderIndex: number; // Sort order within milestone
|
||||
featureIds: string[]; // Linked features (many-to-many)
|
||||
@@ -246,7 +246,7 @@ interface MissionFixFeatureLineage {
|
||||
}
|
||||
```
|
||||
|
||||
The fix feature is **auto-triaged** (converted to tasks) for immediate execution. Each fix increments `implementationAttemptCount`.
|
||||
The fix feature is **auto-planned** (converted to tasks) for immediate execution. Each fix increments `implementationAttemptCount`.
|
||||
|
||||
**Default retry budget:** 3 (`DEFAULT_IMPLEMENTATION_RETRY_BUDGET`). When `implementationAttemptCount >= maxRetryBudget`, the feature transitions to `blocked`.
|
||||
|
||||
@@ -291,7 +291,7 @@ These are independent tracking mechanisms — autopilot monitors mission progres
|
||||
- `inProgressCount`, `passedCount`, `failedCount`, `blockedCount`
|
||||
|
||||
**MissionEvent audit types:**
|
||||
- `slice_activated`, `feature_triaged`, `feature_completed`
|
||||
- `slice_activated`, `feature_planned`, `feature_completed`
|
||||
- `validation:started`, `validation:passed`, `validation:failed`, `validation:blocked`
|
||||
- `fix_feature:created`, `feature:blocked`
|
||||
|
||||
@@ -320,7 +320,7 @@ interface MissionAssertionFailureRecord {
|
||||
| Symptom | Diagnosis | Resolution |
|
||||
|---------|-----------|------------|
|
||||
| Feature stuck in "validating" | `activeValidations` set may be stale; engine restart needed | Check logs for validator errors; restart engine to trigger `recoverActiveMissions()` |
|
||||
| Fix feature not auto-triaging | `triageFeature()` may have errored; check logs | Manual triage via `fn mission triage-feature <id>`; investigate `triageFeature()` errors |
|
||||
| Fix feature not auto-planning | `planFeature()` may have errored; check logs | Manual planning via `fn mission plan-feature <id>`; investigate `planFeature()` errors |
|
||||
| Budget exhaustion loop | `implementationAttemptCount >= maxRetryBudget` (default: 3) | Increase `maxRetryBudget` in mission settings or fix root cause |
|
||||
| Blocked mission not advancing | `MilestoneValidationRollup.state` shows `blocked` | Identify blocked assertions; operator must resolve root cause |
|
||||
| Validation agent errors | AI session creation failed or `VALIDATION_TIMEOUT_MS` (10 min) exceeded | Check model configuration and logs; verify AI provider auth |
|
||||
|
||||
@@ -51,8 +51,8 @@ Defaults from `DEFAULT_GLOBAL_SETTINGS`; key scope from `GLOBAL_SETTINGS_KEYS`.
|
||||
| `modelOnboardingComplete` | `boolean` | `undefined` | Whether AI onboarding has been completed or dismissed. |
|
||||
| `executionGlobalProvider` | `string` | `undefined` | Global baseline provider for task execution. Project `executionProvider` overrides this. |
|
||||
| `executionGlobalModelId` | `string` | `undefined` | Global baseline model ID for task execution. |
|
||||
| `planningGlobalProvider` | `string` | `undefined` | Global baseline provider for planning/triage. Project `planningProvider` overrides this. |
|
||||
| `planningGlobalModelId` | `string` | `undefined` | Global baseline model ID for planning/triage. |
|
||||
| `planningGlobalProvider` | `string` | `undefined` | Global baseline provider for planning. Project `planningProvider` overrides this. |
|
||||
| `planningGlobalModelId` | `string` | `undefined` | Global baseline model ID for planning. |
|
||||
| `validatorGlobalProvider` | `string` | `undefined` | Global baseline provider for validator/reviewer runs. Project `validatorProvider` overrides this. |
|
||||
| `validatorGlobalModelId` | `string` | `undefined` | Global baseline model ID for validator/reviewer runs. |
|
||||
| `titleSummarizerGlobalProvider` | `string` | `undefined` | Global baseline provider for title summarization. Project `titleSummarizerProvider` overrides this. |
|
||||
@@ -78,8 +78,8 @@ Defaults from `DEFAULT_PROJECT_SETTINGS`; key scope from `PROJECT_SETTINGS_KEYS`
|
||||
| `globalPause` | `boolean` | `false` | Hard stop: terminate active engine sessions and pause scheduling immediately. |
|
||||
| `globalPauseReason` | `string` | `undefined` | Optional reason for `globalPause` (`"rate-limit"` for automatic pauses, `"manual"` for user-triggered pauses). Cleared on unpause. |
|
||||
| `enginePaused` | `boolean` | `false` | Soft pause: stop dispatching new work while letting active sessions finish. |
|
||||
| `maxConcurrent` | `number` | `2` | Max concurrent task-lane AI agents (triage, executor, merge). |
|
||||
| `maxTriageConcurrent` | `number` | `2` | Max concurrent triage/specification agents. |
|
||||
| `maxConcurrent` | `number` | `2` | Max concurrent task-lane AI agents (planning, executor, merge). |
|
||||
| `maxTriageConcurrent` | `number` | `2` | Max concurrent planning agents. |
|
||||
| `globalMaxConcurrent` | `number` | `4` | System-wide max concurrent agents across all projects. |
|
||||
| `maxWorktrees` | `number` | `4` | Max git worktrees. |
|
||||
| `pollIntervalMs` | `number` | `15000` | Scheduler poll interval (ms). |
|
||||
@@ -100,10 +100,10 @@ Defaults from `DEFAULT_PROJECT_SETTINGS`; key scope from `PROJECT_SETTINGS_KEYS`
|
||||
| `commitAuthorEnabled` | `boolean` | `true` | Apply explicit `--author` attribution on Fusion commits. |
|
||||
| `commitAuthorName` | `string` | `"Fusion"` | Commit author name when `commitAuthorEnabled` is true. |
|
||||
| `commitAuthorEmail` | `string` | `"noreply@runfusion.ai"` | Commit author email when `commitAuthorEnabled` is true. |
|
||||
| `planningProvider` | `string` | `undefined` | Provider for planning/triage agents. |
|
||||
| `planningModelId` | `string` | `undefined` | Model ID for planning/triage agents. |
|
||||
| `planningFallbackProvider` | `string` | `undefined` | Fallback provider for planning/triage. |
|
||||
| `planningFallbackModelId` | `string` | `undefined` | Fallback model ID for planning/triage. |
|
||||
| `planningProvider` | `string` | `undefined` | Provider for planning agents. |
|
||||
| `planningModelId` | `string` | `undefined` | Model ID for planning agents. |
|
||||
| `planningFallbackProvider` | `string` | `undefined` | Fallback provider for planning. |
|
||||
| `planningFallbackModelId` | `string` | `undefined` | Fallback model ID for planning. |
|
||||
| `defaultProviderOverride` | `string` | `undefined` | Project-level override for global default provider baseline. |
|
||||
| `defaultModelIdOverride` | `string` | `undefined` | Project-level override for global default model baseline. |
|
||||
| `executionProvider` | `string` | `undefined` | Provider for task execution agents. |
|
||||
@@ -121,8 +121,8 @@ Defaults from `DEFAULT_PROJECT_SETTINGS`; key scope from `PROJECT_SETTINGS_KEYS`
|
||||
| `buildRetryCount` | `number` | `0` | Build retry attempts during merge. |
|
||||
| `verificationFixRetries` | `number` | `3` | Auto-fix retry attempts when verification fails during merge. |
|
||||
| `buildTimeoutMs` | `number` | `300000` | Build timeout in milliseconds (5 minutes). |
|
||||
| `requirePlanApproval` | `boolean` | `false` | Require manual approval before triage → todo. |
|
||||
| `specStalenessEnabled` | `boolean` | `false` | Enforce automatic re-triage for stale specs. |
|
||||
| `requirePlanApproval` | `boolean` | `false` | Require manual approval before planning → todo. |
|
||||
| `specStalenessEnabled` | `boolean` | `false` | Enforce automatic re-planning for stale plans. |
|
||||
| `specStalenessMaxAgeMs` | `number` | `21600000` | Spec staleness threshold in ms (6 hours). |
|
||||
| `taskStuckTimeoutMs` | `number` | `undefined` | Inactivity timeout for stuck-task recovery. |
|
||||
| `aiSessionTtlMs` | `number` | `604800000` | TTL in ms for persisted planning/subtask/mission sessions (7 days). |
|
||||
@@ -268,7 +268,7 @@ Short-lived token bounds are enforced server-side:
|
||||
|
||||
Fusion uses a dual-scope model settings system with five lanes. Global settings provide baseline defaults, and project settings provide per-project overrides.
|
||||
|
||||
### Triage/specification model
|
||||
### Planning model
|
||||
|
||||
1. Per-task `planningModelProvider` + `planningModelId`
|
||||
2. Project `planningProvider` + `planningModelId`
|
||||
@@ -442,11 +442,11 @@ Fusion supports fine-grained customization of AI agent prompts through the `prom
|
||||
| `executor-guardrails` | executor | Behavioral guardrails and constraints |
|
||||
| `executor-spawning` | executor | Instructions for spawning child agents |
|
||||
| `executor-completion` | executor | Completion criteria and signaling |
|
||||
| `triage-welcome` | triage | Introductory section for the triage/specification agent |
|
||||
| `triage-context` | triage | Context-gathering instructions |
|
||||
| `triage-welcome` | planning | Introductory section for the planning agent |
|
||||
| `triage-context` | planning | Context-gathering instructions |
|
||||
| `reviewer-verdict` | reviewer | Verdict criteria and format |
|
||||
| `merger-conflicts` | merger | Merge conflict resolution instructions |
|
||||
| `agent-generation-system` | — | System prompt for AI-assisted agent specification generation |
|
||||
| `agent-generation-system` | — | System prompt for AI-assisted agent plan generation |
|
||||
| `workflow-step-refine` | — | System prompt for refining workflow step descriptions into detailed agent prompts |
|
||||
|
||||
### How It Works
|
||||
@@ -485,7 +485,7 @@ To clear all overrides, set `promptOverrides` to `null`:
|
||||
"promptOverrides": {
|
||||
"executor-welcome": "Custom executor welcome message for this project...",
|
||||
"executor-guardrails": "## Custom Guardrails\n- Project-specific rules...",
|
||||
"triage-welcome": "Custom triage introduction..."
|
||||
"triage-welcome": "Custom planning introduction..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +79,8 @@ API endpoints reviewed:
|
||||
| `modelOnboardingComplete` | Global | `GET/PUT /api/settings/global` | Onboarding completion flag |
|
||||
| `executionGlobalProvider` | Global | `GET/PUT /api/settings/global` | Global baseline AI provider for task execution |
|
||||
| `executionGlobalModelId` | Global | `GET/PUT /api/settings/global` | Global baseline AI model ID for task execution |
|
||||
| `planningGlobalProvider` | Global | `GET/PUT /api/settings/global` | Global baseline AI provider for planning/triage |
|
||||
| `planningGlobalModelId` | Global | `GET/PUT /api/settings/global` | Global baseline AI model ID for planning/triage |
|
||||
| `planningGlobalProvider` | Global | `GET/PUT /api/settings/global` | Global baseline AI provider for planning |
|
||||
| `planningGlobalModelId` | Global | `GET/PUT /api/settings/global` | Global baseline AI model ID for planning |
|
||||
| `validatorGlobalProvider` | Global | `GET/PUT /api/settings/global` | Global baseline AI provider for validator/reviewer |
|
||||
| `validatorGlobalModelId` | Global | `GET/PUT /api/settings/global` | Global baseline AI model ID for validator/reviewer |
|
||||
| `titleSummarizerGlobalProvider` | Global | `GET/PUT /api/settings/global` | Global baseline AI provider for title summarization |
|
||||
|
||||
@@ -12,7 +12,7 @@ Use the inline input on board/list view:
|
||||
|
||||
- Type description
|
||||
- Press Enter
|
||||
- Task is created in `triage`
|
||||
- Task is created in `planning`
|
||||
|
||||
### 2) Plan Mode (AI interview)
|
||||
|
||||
@@ -55,7 +55,7 @@ fn task create "Bug" --attach screenshot.png --depends FN-002
|
||||
|
||||
Fusion task columns:
|
||||
|
||||
1. **triage** — idea intake; AI writes a full specification
|
||||
1. **planning** — idea intake; AI writes a full plan
|
||||
2. **todo** — ready for scheduling
|
||||
3. **in-progress** — executor active in isolated worktree
|
||||
4. **in-review** — implementation complete; awaiting finalization
|
||||
@@ -139,9 +139,9 @@ The task detail modal exposes multiple tabs:
|
||||
- **Comments** — collaboration thread + steering controls
|
||||
- **Model** — per-task model overrides and thinking level
|
||||
|
||||
## `PROMPT.md` Specification Structure
|
||||
## `PROMPT.md` Plan Structure
|
||||
|
||||
After triage, each task gets a structured `PROMPT.md` with sections like:
|
||||
After planning, each task gets a structured `PROMPT.md` with sections like:
|
||||
|
||||
- Mission
|
||||
- Dependencies
|
||||
@@ -163,7 +163,7 @@ Steering comments can be injected mid-run into active executor sessions.
|
||||
|
||||
## Refinement Tasks
|
||||
|
||||
`fn task refine <id>` creates a new triage task that depends on the original done/in-review task.
|
||||
`fn task refine <id>` creates a new planning task that depends on the original done/in-review task.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -175,7 +175,7 @@ Behavior:
|
||||
|
||||
- New title format: `Refinement: <source label>`
|
||||
- New task depends on source task
|
||||
- Created in `triage`
|
||||
- Created in `planning`
|
||||
|
||||
## Archive and Restore
|
||||
|
||||
@@ -252,8 +252,8 @@ Review levels control the rigor of the review process for a task:
|
||||
| Level | Name | Description |
|
||||
|-------|------|-------------|
|
||||
| 0 | None | No review |
|
||||
| 1 | Plan Only | Review only the specification/plan |
|
||||
| 2 | Plan and Code | Review both the specification and implementation |
|
||||
| 1 | Plan Only | Review only the plan |
|
||||
| 2 | Plan and Code | Review both the plan and implementation |
|
||||
| 3 | Full | Full review with all checks |
|
||||
|
||||
Review level can be set during task creation (in the New Task dialog under More options) or when editing a task (in the task detail modal).
|
||||
|
||||
@@ -205,7 +205,7 @@ This audit acknowledges and does not duplicate the following existing backlog it
|
||||
### 3.3 Color Contrast in Status Badges
|
||||
|
||||
- **Component:** `styles.css` (34 color themes)
|
||||
- **Current behavior:** Status badge colors (triage, todo, in-progress, etc.) may have insufficient contrast with background colors in certain themes, especially light themes.
|
||||
- **Current behavior:** Status badge colors (planning, todo, in-progress, etc.) may have insufficient contrast with background colors in certain themes, especially light themes.
|
||||
- **Recommended fix:** Audit all status badge colors across all 34 themes for WCAG AA compliance (4.5:1 for text). Use darker variants of status colors in light themes. Test with accessibility tools.
|
||||
- **Impact:** Users with visual impairments may struggle to distinguish status badges.
|
||||
- **Effort estimate:** M
|
||||
|
||||
@@ -51,7 +51,7 @@ The **Frontend UX Design** template verifies visual polish and consistency with
|
||||
|
||||
## Model Overrides for Prompt Steps
|
||||
|
||||
A prompt-mode workflow step can specify its own model with:
|
||||
A prompt-mode workflow step can set its own model with:
|
||||
|
||||
- `modelProvider`
|
||||
- `modelId`
|
||||
|
||||
@@ -427,7 +427,7 @@ describe("fn pi extension", () => {
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("Triage (2)");
|
||||
expect(result.content[0].text).toContain("Planning (2)");
|
||||
expect(result.content[0].text).toContain("FN-001");
|
||||
expect(result.content[0].text).toContain("FN-002");
|
||||
expect(result.details.count).toBe(2);
|
||||
@@ -451,7 +451,7 @@ describe("fn pi extension", () => {
|
||||
undefined,
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
expect(triageResult.content[0].text).toContain("Triage (1)");
|
||||
expect(triageResult.content[0].text).toContain("Planning (1)");
|
||||
expect(triageResult.content[0].text).toContain("FN-001");
|
||||
|
||||
const todoResult = await listTool.execute(
|
||||
@@ -485,7 +485,7 @@ describe("fn pi extension", () => {
|
||||
makeCtx(tmpDir),
|
||||
);
|
||||
|
||||
expect(result.content[0].text).toContain("Triage (5)");
|
||||
expect(result.content[0].text).toContain("Planning (5)");
|
||||
expect(result.content[0].text).toContain("FN-001");
|
||||
expect(result.content[0].text).toContain("FN-002");
|
||||
expect(result.content[0].text).not.toContain("FN-003");
|
||||
@@ -515,7 +515,7 @@ describe("fn pi extension", () => {
|
||||
|
||||
expect(result.content[0].text).toContain("FN-001");
|
||||
expect(result.content[0].text).toContain("Implement caching layer");
|
||||
expect(result.content[0].text).toContain("Triage");
|
||||
expect(result.content[0].text).toContain("Planning");
|
||||
expect(result.details.task).toBeDefined();
|
||||
expect(result.details.task.id).toBe("FN-001");
|
||||
});
|
||||
|
||||
@@ -418,7 +418,7 @@ describe("ListView", () => {
|
||||
|
||||
// Check that all column badges are rendered in the table
|
||||
// Use getAllByText and check length since column names appear in both drop zones and badges
|
||||
expect(screen.getAllByText("Triage").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("Planning").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("Todo").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("In Progress").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("In Review").length).toBeGreaterThanOrEqual(1);
|
||||
@@ -426,7 +426,7 @@ describe("ListView", () => {
|
||||
|
||||
// Check that badges have the correct styling by querying within the table
|
||||
const table = document.querySelector(".list-table");
|
||||
expect(table?.textContent).toContain("Triage");
|
||||
expect(table?.textContent).toContain("Planning");
|
||||
expect(table?.textContent).toContain("Todo");
|
||||
expect(table?.textContent).toContain("In Progress");
|
||||
expect(table?.textContent).toContain("In Review");
|
||||
@@ -589,7 +589,7 @@ describe("ListView", () => {
|
||||
it("renders drop zones for each column", () => {
|
||||
renderListView();
|
||||
|
||||
expect(screen.getByText("Triage")).toBeDefined();
|
||||
expect(screen.getByText("Planning")).toBeDefined();
|
||||
expect(screen.getByText("Todo")).toBeDefined();
|
||||
expect(screen.getByText("In Progress")).toBeDefined();
|
||||
expect(screen.getByText("In Review")).toBeDefined();
|
||||
@@ -719,7 +719,7 @@ describe("ListView", () => {
|
||||
renderListView({ tasks });
|
||||
|
||||
// Check that section headers are rendered with column names
|
||||
expect(screen.getAllByText("Triage").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("Planning").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("Todo").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("In Progress").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getAllByText("In Review").length).toBeGreaterThanOrEqual(1);
|
||||
@@ -740,7 +740,7 @@ describe("ListView", () => {
|
||||
expect(sectionHeaders.length).toBe(6); // One for each column
|
||||
|
||||
// Check that triage section shows count of 2
|
||||
const triageHeader = sectionHeaders.find(h => h.textContent?.includes("Triage"));
|
||||
const triageHeader = sectionHeaders.find(h => h.textContent?.includes("Planning"));
|
||||
expect(triageHeader?.textContent).toContain("2");
|
||||
|
||||
// Check that todo section shows count of 1
|
||||
@@ -796,7 +796,7 @@ describe("ListView", () => {
|
||||
// Only triage section should be visible (todo section should be hidden)
|
||||
const sectionHeaders = screen.getAllByRole("row").filter(r => r.className.includes("list-section-header"));
|
||||
expect(sectionHeaders.length).toBe(1);
|
||||
expect(sectionHeaders[0].textContent).toContain("Triage");
|
||||
expect(sectionHeaders[0].textContent).toContain("Planning");
|
||||
|
||||
// Verify the filtered task is visible
|
||||
expect(screen.getByText("FN-001")).toBeDefined();
|
||||
@@ -818,7 +818,7 @@ describe("ListView", () => {
|
||||
|
||||
// Get only data rows within the triage section
|
||||
const allRows = screen.getAllByRole("row");
|
||||
const triageSectionStart = allRows.findIndex(r => r.className.includes("list-section-header") && r.textContent?.includes("Triage"));
|
||||
const triageSectionStart = allRows.findIndex(r => r.className.includes("list-section-header") && r.textContent?.includes("Planning"));
|
||||
|
||||
// The next 3 rows after the section header should be the sorted tasks
|
||||
const dataRows = allRows.slice(triageSectionStart + 1, triageSectionStart + 4).filter(r => r.getAttribute("data-id"));
|
||||
@@ -836,7 +836,7 @@ describe("ListView Column Filtering", () => {
|
||||
|
||||
it("filters tasks by column when drop zone is clicked", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "todo", title: "Todo Task" }),
|
||||
createMockTask({ id: "FN-003", column: "in-progress", title: "In Progress Task" }),
|
||||
];
|
||||
@@ -855,12 +855,12 @@ describe("ListView Column Filtering", () => {
|
||||
// Only triage section header should be visible
|
||||
const sectionHeaders = screen.getAllByRole("row").filter(r => r.className.includes("list-section-header"));
|
||||
expect(sectionHeaders.length).toBe(1);
|
||||
expect(sectionHeaders[0].textContent).toContain("Triage");
|
||||
expect(sectionHeaders[0].textContent).toContain("Planning");
|
||||
});
|
||||
|
||||
it("clears column filter when same drop zone is clicked again", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "todo", title: "Todo Task" }),
|
||||
];
|
||||
|
||||
@@ -888,7 +888,7 @@ describe("ListView Column Filtering", () => {
|
||||
|
||||
it("switches column filter when different drop zone is clicked", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "todo", title: "Todo Task" }),
|
||||
];
|
||||
|
||||
@@ -918,7 +918,7 @@ describe("ListView Column Filtering", () => {
|
||||
|
||||
it("clears column filter when clear button is clicked", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "todo", title: "Todo Task" }),
|
||||
];
|
||||
|
||||
@@ -942,8 +942,8 @@ describe("ListView Column Filtering", () => {
|
||||
|
||||
it("shows correct filtered stats when column filter is active", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Triage Task 2" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Planning Task 2" }),
|
||||
createMockTask({ id: "FN-003", column: "todo", title: "Todo Task" }),
|
||||
];
|
||||
|
||||
@@ -954,13 +954,13 @@ describe("ListView Column Filtering", () => {
|
||||
fireEvent.click(triageZone);
|
||||
|
||||
// Stats should show filtered count with column name
|
||||
expect(screen.getByText("2 of 3 tasks in Triage")).toBeDefined();
|
||||
expect(screen.getByText("2 of 3 tasks in Planning")).toBeDefined();
|
||||
});
|
||||
|
||||
it("applies text filter within column filter", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Alpha Triage Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Beta Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Alpha Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Beta Planning Task" }),
|
||||
createMockTask({ id: "FN-003", column: "todo", title: "Alpha Todo Task" }),
|
||||
];
|
||||
|
||||
@@ -976,12 +976,12 @@ describe("ListView Column Filtering", () => {
|
||||
expect(screen.queryByText("FN-003")).toBeNull();
|
||||
|
||||
// Stats should reflect combined filtering
|
||||
expect(screen.getByText("1 of 3 tasks in Triage")).toBeDefined();
|
||||
expect(screen.getByText("1 of 3 tasks in Planning")).toBeDefined();
|
||||
});
|
||||
|
||||
it("applies active class to selected column drop zone", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
@@ -1180,7 +1180,7 @@ describe("ListView Column Visibility", () => {
|
||||
expect(screen.getByText("pending")).toBeDefined();
|
||||
// Check for column badge specifically using the class
|
||||
const columnBadge = document.querySelector(".list-column-badge");
|
||||
expect(columnBadge?.textContent).toContain("Triage");
|
||||
expect(columnBadge?.textContent).toContain("Planning");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1371,9 +1371,9 @@ describe("ListView Hide Done Tasks", () => {
|
||||
);
|
||||
expect(archivedSection).toBeUndefined();
|
||||
|
||||
// Triage section should still be visible
|
||||
// Planning section should still be visible
|
||||
const triageSection = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
expect(triageSection).toBeDefined();
|
||||
});
|
||||
@@ -1438,7 +1438,7 @@ describe("ListView Hide Done Tasks", () => {
|
||||
it("shows done section when selectedColumn is done even with hide done active", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "done", title: "Done Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Planning Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
@@ -1462,7 +1462,7 @@ describe("ListView Hide Done Tasks", () => {
|
||||
it("shows archived section when selectedColumn is archived even with hide done active", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "archived", title: "Archived Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Planning Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
@@ -1652,8 +1652,8 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
it("clicking section header toggles collapse and hides task rows", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task 1" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Triage Task 2" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task 1" }),
|
||||
createMockTask({ id: "FN-002", column: "triage", title: "Planning Task 2" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
@@ -1664,7 +1664,7 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
// Find and click the triage section header
|
||||
const triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
expect(triageHeader).toBeDefined();
|
||||
fireEvent.click(triageHeader!);
|
||||
@@ -1683,14 +1683,14 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
it("clicking again expands section and shows task rows", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
|
||||
// Find the triage section header
|
||||
let triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
|
||||
// Click to collapse
|
||||
@@ -1707,7 +1707,7 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
// Re-query for the header to get fresh DOM reference after re-render
|
||||
triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
|
||||
// Section header should not have collapsed class
|
||||
@@ -1719,14 +1719,14 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
it("collapse state persists to localStorage", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
|
||||
// Click to collapse
|
||||
const triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
fireEvent.click(triageHeader!);
|
||||
|
||||
@@ -1742,28 +1742,28 @@ describe("ListView Collapsible Sections", () => {
|
||||
localStorage.setItem(scopedStorageKey("kb-dashboard-list-collapsed"), JSON.stringify(["triage"]));
|
||||
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "todo", title: "Todo Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
|
||||
// Triage task should be hidden initially (collapsed from localStorage)
|
||||
// Planning task should be hidden initially (collapsed from localStorage)
|
||||
expect(screen.queryByText("FN-001")).toBeNull();
|
||||
|
||||
// Todo task should be visible
|
||||
expect(screen.getByText("FN-002")).toBeDefined();
|
||||
|
||||
// Triage section header should have collapsed class
|
||||
// Planning section header should have collapsed class
|
||||
const triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
expect(triageHeader?.className).toContain("list-section-header--collapsed");
|
||||
});
|
||||
|
||||
it("multiple sections can be collapsed independently", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
createMockTask({ id: "FN-002", column: "todo", title: "Todo Task" }),
|
||||
createMockTask({ id: "FN-003", column: "in-progress", title: "In Progress Task" }),
|
||||
];
|
||||
@@ -1774,7 +1774,7 @@ describe("ListView Collapsible Sections", () => {
|
||||
const allHeaders = screen.getAllByRole("row").filter(r =>
|
||||
r.className.includes("list-section-header")
|
||||
);
|
||||
const triageHeader = allHeaders.find(h => h.textContent?.includes("Triage"));
|
||||
const triageHeader = allHeaders.find(h => h.textContent?.includes("Planning"));
|
||||
const todoHeader = allHeaders.find(h => h.textContent?.includes("Todo"));
|
||||
|
||||
// Collapse triage section
|
||||
@@ -1783,7 +1783,7 @@ describe("ListView Collapsible Sections", () => {
|
||||
// Collapse todo section
|
||||
fireEvent.click(todoHeader!);
|
||||
|
||||
// Triage and todo tasks should be hidden
|
||||
// Planning and todo tasks should be hidden
|
||||
expect(screen.queryByText("FN-001")).toBeNull();
|
||||
expect(screen.queryByText("FN-002")).toBeNull();
|
||||
|
||||
@@ -1813,7 +1813,7 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
// Collapse triage section
|
||||
const triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
fireEvent.click(triageHeader!);
|
||||
|
||||
@@ -1841,7 +1841,7 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
// Collapse triage section
|
||||
const triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
fireEvent.click(triageHeader!);
|
||||
|
||||
@@ -1855,14 +1855,14 @@ describe("ListView Collapsible Sections", () => {
|
||||
|
||||
it("section header has aria-expanded attribute for accessibility", () => {
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
|
||||
// Find triage section header
|
||||
const triageHeader = screen.getAllByRole("row").find(r =>
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Triage")
|
||||
r.className.includes("list-section-header") && r.textContent?.includes("Planning")
|
||||
);
|
||||
|
||||
// Should have aria-expanded="true" when expanded
|
||||
@@ -1878,7 +1878,7 @@ describe("ListView Collapsible Sections", () => {
|
||||
it("collapsed section hides No tasks placeholder", () => {
|
||||
// Create tasks in one column, leave another column empty
|
||||
const tasks = [
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Triage Task" }),
|
||||
createMockTask({ id: "FN-001", column: "triage", title: "Planning Task" }),
|
||||
];
|
||||
|
||||
renderListView({ tasks });
|
||||
@@ -2376,7 +2376,7 @@ describe("ListView - Bulk Selection", () => {
|
||||
tasks: [createMockTask({ id: "FN-001", title: "Collapsible task" })],
|
||||
});
|
||||
|
||||
const sectionHeader = screen.getByRole("button", { name: /Triage/i });
|
||||
const sectionHeader = screen.getByRole("button", { name: /Planning/i });
|
||||
expect(container.querySelector('.list-card[data-id="FN-001"]')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(sectionHeader);
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// agent is expected to review each flagged item itself (diff the matching
|
||||
// main commit against HEAD, confirm its net contribution survived) and
|
||||
// restore anything silently dropped. No human handoff required; the audit
|
||||
// is only "clean" once the agent has triaged every item or reapplied any
|
||||
// is only "clean" once the agent has reviewed every item or reapplied any
|
||||
// losses as a follow-up commit on the same branch.
|
||||
//
|
||||
// Example:
|
||||
@@ -105,5 +105,5 @@ if (overlaps.length === 0) {
|
||||
}
|
||||
|
||||
const issues = dupes.length + overlaps.length;
|
||||
console.log(`Audit complete. ${issues} item(s) for the calling agent to triage.`);
|
||||
console.log(`Audit complete. ${issues} item(s) for the calling agent to review.`);
|
||||
process.exit(issues === 0 ? 0 : 1);
|
||||
|
||||
Reference in New Issue
Block a user