Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from rich.align import Align


STAGES = ["init", "generate_phases_json", "generate_domain_context", "spec_generation", "verification", "bug_validation"]
STAGES = ["init", "generate_source_manifest", "generate_domain_context", "spec_generation", "verification", "bug_validation"]
CACHE_WINDOW = 200
LLM_STATUS_WINDOW = 80

Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ OpenCode setup.
| Parameter | Default | Description |
| ------------------------------- | ------------------------------ | ------------------------------------------------------------ |
| `LLM_MODEL` | `anthropic/claude-sonnet-4.6` | Default model used as the fallback for all task-specific model settings |
| `OPENCODE_SETUP_MODEL` | `LLM_MODEL` | Model used by OpenCode for codebase understanding, phase planning, and domain context generation |
| `OPENCODE_SETUP_MODEL` | `LLM_MODEL` | Model used by OpenCode for codebase understanding, module planning, and domain context generation |
| `OPENCODE_SPEC_MODEL` | `LLM_MODEL` | Model used by OpenCode for batch behavioral spec generation |
| `OPENCODE_BUG_VALIDATION_MODEL` | `LLM_MODEL` | Model used by OpenCode to validate `MISMATCH` results with probe scripts and bug reports |
| `REASONER_POST_CONDITION_MODEL` | `LLM_MODEL` | Model used by direct llm calls to generate block post-conditions |
Expand Down
77 changes: 21 additions & 56 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
from src.file_utils import (
collect_file_names,
_has_source_code,
_get_all_phase_files,
_write_file_names,
_json_file_is_valid,
_is_under_submodules,
)
from src.extract import run_extraction, EXT_TO_LANG
from src.generate_topdown_layers import generate_topdown_layers
from src.spec_generation_and_verification import run_spec_generation_and_verification
from src.backend import DEFAULT_BACKEND
from src.incremental_reasoner import run_incremental_pipeline
from src.git import (
frozen_worktree,
Expand All @@ -21,9 +19,6 @@
from src.languages.codegraph import try_codegraph_init
from src.pipeline_setup import (
_run_setup_extract,
_run_generate_phases,
_post_process_phases,
_run_generate_domain_context,
)
from src.domain_knowledge import (
collect_domain_knowledge_paths,
Expand Down Expand Up @@ -111,12 +106,13 @@ def run_pipeline(
required_source_files=None,
domain_knowledge_files=None,
submodules=None,
one_phase=False,
extra_call_edges_path=None,
only_spec=False,
bug_validator_path=None,
plugin_config=None,
backend=None,
):
backend = backend or DEFAULT_BACKEND
if not os.path.isdir(proj_dir):
print(f"[Pipeline] ERROR: proj_dir does not exist or is not a directory: {proj_dir}")
sys.exit(1)
Expand All @@ -133,7 +129,7 @@ def run_pipeline(
extra_call_edges = load_call_edges(extra_call_edges_path)

# Clean files from the previous run — unless resuming, where we keep all
# prior progress (phases.json, generated specs, verification results) and
# prior progress (setup manifests, generated specs, verification results) and
# only do the remaining work.
if resume:
if os.path.isdir(work_dir):
Expand All @@ -153,35 +149,17 @@ def run_pipeline(
f"{len(domain_knowledge_relpaths)} markdown file(s)."
)

# Stage 1: generate phase.json (input: target code → phases.json)
# Stage 2: generate domain context (input: phases.json → domain context files)
phase_stage = plugin_config.get_stage("generate_phase_plan") if plugin_config else None
context_stage = plugin_config.get_stage("generate_domain_context") if plugin_config else None
plugin_root = plugin_config.root if plugin_config else None

print("[Pipeline] Stage 1/6: Generating phase plan...")
_run_generate_phases(
proj_dir, work_dir, script_dir, resume=resume,
submodules=submodules,
plugin_stage=phase_stage,
plugin_root=plugin_root,
)

phases_modified = _post_process_phases(
# Stage 1/2: generate source/module manifests and domain context.
print("[Pipeline] Stage 1/6: Generating source/module manifests...")
print("[Pipeline] Stage 2/6: Generating domain context...")
_run_setup_extract(
proj_dir, work_dir,
script_dir,
resume=resume,
required_source_files=required_source_files,
submodules=submodules,
one_phase=one_phase,
)

print("[Pipeline] Stage 2/6: Generating domain context...")
_run_generate_domain_context(
proj_dir,
work_dir,
script_dir,
resume=resume and not phases_modified,
plugin_stage=context_stage,
plugin_root=plugin_root,
plugin_config=plugin_config,
backend=backend,
)

# Build (or rebuild) the codegraph index if codegraph is installed. Both
Expand Down Expand Up @@ -214,17 +192,9 @@ def run_pipeline(
os.path.join(spec_prompts_dir, "file_utils.py"),
)

phases_path = os.path.join(work_dir, "phases.json")
with open(phases_path, "r") as f:
phases_data = json.load(f)

print("[Pipeline] Stage 4/6: Collecting file list...")
file_list_path = os.path.join(work_dir, "fm_agent_file_list.json")
file_list = collect_file_names(input_dir, file_list_path)
if submodules:
file_list = _write_file_names(
_get_all_phase_files(phases_data, input_dir), file_list_path
)

if not file_list:
print("[Pipeline] No functions found to verify. Skipping spec generation.")
Expand All @@ -234,22 +204,25 @@ def run_pipeline(
print("[Pipeline] Stage 5/6: Generating topdown layers...")
generate_topdown_layers(work_dir, extra_call_edges=extra_call_edges)

# --- Stage 6: Execute spec generation workflow (per phase, per layer) ---
# --- Stage 6: Execute spec generation workflow (per global layer) ---
if only_spec:
print("[Pipeline] Stage 6/6: Generating specs (reasoning & bug validation disabled)...")
else:
print("[Pipeline] Stage 6/6: Generating specs & verification...")
with open(os.path.join(work_dir, "modules.json"), "r") as f:
modules_data = json.load(f)
run_spec_generation_and_verification(
proj_dir,
work_dir,
input_dir,
output_dir,
script_dir,
spec_prompts_dir,
phases_data,
modules_data,
resume=resume,
extra_call_edges=extra_call_edges,
only_spec=only_spec,
backend=backend,
bug_validator_path=bug_validator_path,
)

Expand All @@ -272,7 +245,7 @@ def run_pipeline(
if __name__ == "__main__":
parser = argparse.ArgumentParser(
usage="python3 main.py <proj_dir> [--resume] [--incremental INTENT_FILE] "
"[--domain-knowledge FILE ...] [--one-phase] [--isolate] "
"[--domain-knowledge FILE ...] [--isolate] "
"[--submodule PATH [PATH ...]] [--entry-func PATH] "
"[--end-func PATH ...] [--extra-edge FILE] "
"[--bug-validator FILE] [--only-spec] "
Expand All @@ -284,8 +257,8 @@ def run_pipeline(
"--resume",
action="store_true",
help="continue a previous run in <proj_dir>/fm_agent instead of wiping it: "
"keeps phases.json, generated specs, and existing verification results; "
"only does the remaining work.",
"keeps the setup manifests, generated specs, and existing verification "
"results; only does the remaining work.",
)
parser.add_argument(
"--incremental",
Expand All @@ -299,11 +272,6 @@ def run_pipeline(
help="Run the pipeline against an isolated git worktree snapshot of "
"the project instead of the project directory itself.",
)
parser.add_argument(
"--one-phase",
action="store_true",
help="Put all planned source files into a single analysis phase.",
)
parser.add_argument(
"--only-spec",
action="store_true",
Expand Down Expand Up @@ -453,7 +421,6 @@ def run_pipeline(
end_funcs=args.end_func,
resume=resume,
domain_knowledge_files=domain_knowledge_files,
one_phase=args.one_phase,
extra_call_edges_path=extra_call_edges_path,
only_spec=args.only_spec,
bug_validator_path=bug_validator_path,
Expand Down Expand Up @@ -493,7 +460,7 @@ def run_pipeline(
new_commit = _get_head_commit(proj_dir)

# With --isolate, the pipeline runs against the snapshot's fm_agent/. Resuming
# needs the previous run's fm_agent/ (phases.json, specs, verification results)
# needs the previous run's fm_agent/ (setup manifests, specs, verification results)
# to be present in the snapshot, so copy the excluded workspace in for resume
# too — not just incremental mode.
run_ctx = (
Expand All @@ -514,7 +481,6 @@ def run_pipeline(
old_commit,
domain_knowledge_files=domain_knowledge_files,
submodules=submodules,
one_phase=args.one_phase,
extra_call_edges_path=extra_call_edges_path,
bug_validator_path=bug_validator_path,
plugin_config=plugin_config,
Expand All @@ -525,7 +491,6 @@ def run_pipeline(
resume=resume,
domain_knowledge_files=domain_knowledge_files,
submodules=submodules,
one_phase=args.one_phase,
extra_call_edges_path=extra_call_edges_path,
only_spec=args.only_spec,
bug_validator_path=bug_validator_path,
Expand Down
7 changes: 7 additions & 0 deletions md/bug_validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ python3 fm_agent/bug_validation/probe_<bug_id>.py

Capture both stdout and the exit code.

#### Environment failures

Do not confirm a bug solely because the local environment is broken. Missing or
corrupted runtimes, package managers, dependencies, or analysis tools (for
example `codegraph: command not found`) are **error**, not **confirmed**. A bug is
confirmable only through the package's normal public entry point on a valid input.

### 2e. Classify the Result and Retry if Needed

Based on the output:
Expand Down
72 changes: 40 additions & 32 deletions md/workflow_generate_domain_context.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,70 @@
# Generate Domain Context

> **YOUR SOLE OBJECTIVE**: Read `fm_agent/phases.json` and write domain context files describing the types, invariants, and architecture of each phase. Do NOT modify `phases.json`. Do NOT edit any existing project files. Only create files inside `fm_agent/`.
> **YOUR SOLE OBJECTIVE**: Read `fm_agent/source_files.json` and `fm_agent/modules.json`, then write domain context files describing the types, invariants, and architecture of the project and its modules. Do NOT modify the manifests. Do NOT edit any existing project files. Only create files inside `fm_agent/`.

> **CRITICAL — YOU MUST CREATE FILES IN THIS SESSION**: Do NOT only research, plan, or delegate to background/sub-agents. You MUST directly write the domain context files yourself before this session ends.

**Required output files:**
Required outputs:

1. `fm_agent/spec_prompts/domain_context/engine_overview.txt`
2. `fm_agent/spec_prompts/domain_context/phase_NN_types.txt` (one per phase)
2. Either `fm_agent/spec_prompts/domain_context/types.txt` for smaller projects, or `fm_agent/spec_prompts/domain_context/module_types/<module_slug>.txt` for larger projects

Rules:

**Rules:**
- `fm_agent/phases.json` has already been generated and finalized. Read it — do NOT modify it.
- `fm_agent/` is NOT part of the project source code. It is a scratch workspace for storing YOUR output files only.
- Do NOT modify any existing files in the repository (including `fm_agent/phases.json`).
- `fm_agent/source_files.json` and `fm_agent/modules.json` have already been generated and finalized. Read them, but do NOT modify them.
- `fm_agent/` is not part of the project source code. It is a scratch workspace for your output files only.
- Do NOT modify any existing files in the repository.
- Do NOT create or edit AGENTS.md, README.md, or any file outside `fm_agent/`.
- Do NOT run the project or install dependencies.
- Keep exploration minimal — read only the source files listed in `phases.json` for the types and invariants they define.
- Keep exploration minimal. Read only the source files listed in the manifests for the types and invariants they define.
- Start writing output files as soon as you have enough context. Do not over-analyze.
- Do NOT delegate file creation to sub-agents. Write the files directly yourself.

---

## Step 1 — Read `fm_agent/phases.json`
## Step 1 — Read the Setup Manifests

Read `fm_agent/phases.json` to understand the phase structure. For each phase, note:
- The phase number and name
- The source files listed in each module
- The dependency relationships between phases
Read `fm_agent/source_files.json` and `fm_agent/modules.json` to understand the source scope and module structure. For each module, note:

---
- The module name
- The module description
- The source files listed in the module

## Step 2 — Write Domain Context Files

### Write `fm_agent/spec_prompts/domain_context/engine_overview.txt`

Describe the overall system:
- Architecture: what the pipeline stages are and how data flows between them
- Encoding conventions: how each data type is stored (scaled integers, date offsets, dictionary codes, string layouts)
- Key precomputed data structures and their invariants (e.g., join maps, range indices)
- Important invariants of every phase

### Write `fm_agent/spec_prompts/domain_context/phase_NN_types.txt` for each phase
- Architecture: how the system is organized and how data flows between modules
- Encoding conventions: how important data types are stored
- Key precomputed data structures and their invariants
- Important invariants shared across modules

### Write Module or Global Type Context

For smaller projects, write one `types.txt`. For larger projects, write one file per module under `module_types/`.

For each phase, describe:
- All structs and types that functions in this phase produce or consume
Name each module type file with the module name slug, not the raw module name. Build the slug by replacing every run of characters other than ASCII letters, digits, `.`, `_`, and `-` with a single `_`, then trimming leading/trailing `.`, `_`, and `-`. Examples:

- module `misc/fasttest` -> `module_types/misc_fasttest.txt`
- module `tools/validate_tool` -> `module_types/tools_validate_tool.txt`
- module `core` -> `module_types/core.txt`

Describe:

- All structs and types that functions in this module produce or consume
- Field types and valid value ranges
- Encoding rules (with explicit formulas, e.g., `date_field[i] = actual_days - base_date_days`)
- Invariants that must hold in this phase
- Encoding rules with explicit formulas where relevant
- Invariants that must hold in this module
- Cross-module contracts that are important for callers and callees
- Entry point function signatures

These files are given to spec-writing agents as context. Without them, agents will write generic specs that miss the domain-specific invariants.

---
These files are given to spec-writing agents as context. Without them, agents will write generic specs that miss domain-specific invariants.

## Checklist

**Before finishing, verify all of the following exist (use `ls` to confirm):**
Before finishing, verify all of the following exist:

- [ ] `fm_agent/spec_prompts/domain_context/engine_overview.txt` exists
- [ ] `fm_agent/spec_prompts/domain_context/phase_NN_types.txt` exists for each phase listed in `fm_agent/phases.json`
- [ ] `fm_agent/spec_prompts/domain_context/engine_overview.txt`
- [ ] `fm_agent/spec_prompts/domain_context/types.txt` or correctly slugged `fm_agent/spec_prompts/domain_context/module_types/*.txt`

**If any file is missing, create it now before ending.**
If any file is missing, create it now before ending.
Loading