Skip to content
Merged
148 changes: 148 additions & 0 deletions docs/forge-hardening-execution-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"$schema": "plan-v1",
"id": "forge-hardening-execution",
"title": "Forge Hardening Execution",
"goal": "Implement Forge architecture, template, and quality improvements with test-first, task-scoped commits and a PR.",
"context": {
"tech_stack": [
"TypeScript",
"React",
"Vitest",
"Rust",
"Tauri"
],
"constraints": [
"Tests first for each task",
"One commit per task",
"Keep app/daemon behavior parity",
"Do not break existing Forge contracts"
],
"references": [
{
"path": "src/features/forge/components/Forge.tsx",
"description": "Forge UI orchestration and polling"
},
{
"path": "src/features/forge/hooks/useForgeExecution.ts",
"description": "Execution loop and phase orchestration"
},
{
"path": "src-tauri/src/shared/forge_execute_core.rs",
"description": "Execution backend source of truth"
},
{
"path": "src-tauri/src/bin/codex_monitor_daemon/rpc/workspace.rs",
"description": "Daemon RPC workspace dispatcher"
}
]
},
"tasks": [
{
"id": "task-1",
"name": "Wire Forge Daemon RPC Routing",
"description": "Add missing Forge RPC method routing in daemon dispatcher and cover it with RPC-level tests so remote mode can invoke every forge_* command through JSON-RPC.",
"depends_on": [],
"files": [
"src-tauri/src/bin/codex_monitor_daemon/rpc/workspace.rs",
"src-tauri/src/bin/codex_monitor_daemon.rs"
],
"verification": [
"New daemon RPC tests fail before implementation and pass after.",
"forge_list_plans and forge_get_plan_prompt routes are exercised via rpc::handle_rpc_request.",
"No regression in existing daemon tests for workspace/codex methods."
]
},
{
"id": "task-2",
"name": "Enforce AI Review Evidence Gate",
"description": "Implement machine-enforced AI review completion by requiring a structured per-task review report artifact and validating it in Forge checks before final task completion.",
"depends_on": [
"task-1"
],
"files": [
"src-tauri/src/shared/forge_execute_core.rs",
"src-tauri/resources/forge/templates/test-first-loop/prompts/execute.md",
"src-tauri/resources/forge/templates/test-first-loop/phases.json"
],
"verification": [
"Forge execute core tests cover missing report, non-zero findings, and zero-findings success paths.",
"ai-review phase can no longer pass with empty/no evidence artifact.",
"Existing ai-review happy-path test remains green with valid artifact."
]
},
{
"id": "task-3",
"name": "Add Risk-Adaptive Template",
"description": "Add a new forge template optimized for efficiency (risk-adaptive-loop) with tighter phase flow and script/prompt coverage so teams can choose a faster default model when appropriate.",
"depends_on": [
"task-2"
],
"files": [
"src-tauri/resources/forge/templates/risk-adaptive-loop/template.json",
"src-tauri/resources/forge/templates/risk-adaptive-loop/phases.json",
"src-tauri/resources/forge/templates/risk-adaptive-loop/prompts/plan.md",
"src-tauri/resources/forge/templates/risk-adaptive-loop/prompts/execute.md",
"src-tauri/resources/forge/templates/risk-adaptive-loop/scripts/post-plan.mjs",
"src-tauri/resources/forge/templates/risk-adaptive-loop/scripts/post-step.mjs",
"src/features/forge/scripts/riskAdaptiveLoopScripts.test.ts"
],
"verification": [
"Script tests prove phase initialization and prompt phase progression for the new template.",
"Bundled template listing includes risk-adaptive-loop metadata.",
"Template manifest files and entrypoints are complete and installable."
]
},
{
"id": "task-4",
"name": "Harden Forge Polling",
"description": "Refine Forge panel polling to prevent stale async results from mutating state after workspace/plan switches and centralize polling intervals into constants.",
"depends_on": [
"task-3"
],
"files": [
"src/features/forge/components/Forge.tsx",
"src/features/forge/components/Forge.plans.test.tsx"
],
"verification": [
"Component tests validate stale polling responses are ignored after selection/workspace change.",
"Polling intervals are no longer duplicated magic numbers.",
"No regression in plan-selection and execution-toggle tests."
]
},
{
"id": "task-5",
"name": "Bound Execution Retries and Waits",
"description": "Add explicit timeout and retry-budget controls in useForgeExecution for phase-final polling and failed phase checks to avoid infinite loops and runaway runs.",
"depends_on": [
"task-4"
],
"files": [
"src/features/forge/hooks/useForgeExecution.ts",
"src/features/forge/hooks/useForgeExecution.test.ts"
],
"verification": [
"Hook tests fail before and pass after for timeout and max-check-retry behavior.",
"Execution returns user-visible errors when budgets are exceeded.",
"Existing Forge execution UI tests stay green."
]
},
{
"id": "task-6",
"name": "Fresh Skill Sync and Icon Fallback Cleanup",
"description": "Improve template skill synchronization so changed source skills refresh in .agents/skills and align frontend phase icon fallback IDs with valid icon names.",
"depends_on": [
"task-5"
],
"files": [
"src-tauri/src/shared/forge_templates_core.rs",
"src/services/tauri.ts",
"src/services/tauri.test.ts"
],
"verification": [
"New rust test proves sync updates stale files when source content changes.",
"Phase metadata fallback icon id is valid and consistent.",
"tauri forge phase-view tests pass with updated fallback behavior."
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"iconId": "folder-review",
"order": 6,
"goal": "Run an AI quality review pass and enforce a strict zero-findings completion gate.",
"description": "Execute an AI review over the final change set and treat any finding as a release blocker for this phase. Completion checks: (1) AI review is run against the final diff, (2) all findings are resolved and rechecked, and (3) the phase is marked completed only with zero findings. If any finding remains, phase status must stay non-completed (`failed` or `blocked`) and execution must stop until fixes are applied and the review is rerun clean.",
"description": "Execute an AI review over the final change set and treat any finding as a release blocker for this phase. Completion checks: (1) AI review is run against the final diff, (2) all findings are resolved and rechecked, (3) `plans/<plan_id>/ai-review/<task_id>.json` is written with schema `forge-ai-review-v1`, matching `taskId`, and `findings: []`, and (4) the phase is marked completed only with zero findings. If any finding remains, phase status must stay non-completed (`failed` or `blocked`) and execution must stop until fixes are applied and the review is rerun clean.",
"checks": []
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ If any completion check is unmet, do not mark the phase `completed`; use `in_pro
- Forge finalizes phase/task completion statuses after checks.
- If checks fail, Forge reopens the phase and you retry.
- If checks pass on the last phase, Forge creates the task commit and records `commit_sha`.
- For `ai-review`, Forge requires a report file at `plans/{{plan_id}}/ai-review/{{current_task_id}}.json` with:

```json
{
"schema": "forge-ai-review-v1",
"taskId": "{{current_task_id}}",
"findings": []
}
```

- If any finding remains, include each finding in `findings` and keep phase status non-completed (`blocked` or `failed`).
5. Do NOT run `git` commands yourself in execute mode.
- Do NOT run `git add`, `git commit`, `git commit --amend`, or `git push`.
- Do NOT set `commit_sha` in `state.json`; Forge manages it.
Expand Down
55 changes: 55 additions & 0 deletions src-tauri/src/bin/codex_monitor_daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1760,6 +1760,61 @@ mod tests {
let _ = std::fs::remove_dir_all(&tmp);
});
}

#[test]
fn rpc_forge_list_bundled_templates_routes_to_daemon_state() {
run_async_test(async {
let tmp = make_temp_dir("rpc-forge-list-bundled");
let state = test_state(&tmp);

let result = rpc::handle_rpc_request(
&state,
"forge_list_bundled_templates",
json!({}),
"daemon-test".to_string(),
)
.await
.expect("forge_list_bundled_templates should succeed");

let templates = result.as_array().expect("array result");
assert!(
templates.iter().any(|entry| {
entry
.get("id")
.and_then(Value::as_str)
.is_some_and(|id| id == "ralph-loop")
}),
"expected forge_list_bundled_templates to include ralph-loop"
);
let _ = std::fs::remove_dir_all(&tmp);
});
}

#[test]
fn rpc_forge_list_plans_routes_to_workspace_handler() {
run_async_test(async {
let tmp = make_temp_dir("rpc-forge-list-plans");
let workspace_id = "ws-forge-plans";
let workspace_dir = tmp.join("workspace");
std::fs::create_dir_all(workspace_dir.join("plans")).expect("create workspace plans dir");

let state = test_state(&tmp);
insert_workspace(&state, workspace_id, &workspace_dir.to_string_lossy()).await;

let result = rpc::handle_rpc_request(
&state,
"forge_list_plans",
json!({ "workspaceId": workspace_id }),
"daemon-test".to_string(),
)
.await
.expect("forge_list_plans should succeed");

let plans = result.as_array().expect("array result");
assert!(plans.is_empty(), "expected no plans in empty plans directory");
let _ = std::fs::remove_dir_all(&tmp);
});
}
}

fn main() {
Expand Down
Loading