# vulnmine

`vulnmine` 是一个面向大型源码仓库的、分层式漏洞挖掘运行时系统。

这份 README 是当前系统的详细说明文档，面向：

- 开发者
- 调参与运维人员
- 需要理解全流程行为的人

如果你想看：

- bug 历史、根因和修复记录：看 `BUGS.md`
- 最近到底实现了什么：看 `IMPLEMENT.md`
- 架构边界规则：看 `docs/02-architecture/runtime-boundary.md`
- 当前能力状态：看 `docs/02-architecture/capability-status.md`
- 后续实现清单：看 `docs/02-architecture/implementation-checklist.md`
- Agent Handoff文档：看 `docs/AGENT_HANDOFF_VULDATABASE_PLANNER_TRIAGE_2026-04-02.md`

---

## 安装说明

- 主入口脚本是 `bash scripts/bootstrap.sh`
- `install.sh` 为兼容旧流程，仅转发到 bootstrap
- 默认安装模式是 degraded usable（可降级可用）
- 使用 `--strict` 时，provider 失败会被视为阻塞错误

## 当前主线状态

- 正式支持的端到端入口仍然是 CLI：`python -m vulnmine.app.cli`
- Web UI 是对 workspace artifacts 的薄层展示与控制界面，不是独立于 CLI 的另一套运行时
- `main` 当前已支持在 Dashboard 中展示项目和运行的完成时间，并按最近完成时间排序项目列表
- Web UI 当前已支持 batch workspace 项目展示，嵌套 workspace 会映射为稳定 slug
- `scoring` 已支持 `score_drive=auto`，会按仓库规模和 issue 规模自动选择更合适的打分驱动模式
- planner-enabled 路径下，`planning -> surface -> triage -> scoring` 的 artifact 传递是当前主线路径，README 以下说明以这一行为为准

## Web UI 运行说明

- 推荐通过 `scripts/web_server_ctl.sh` 管理 Web 服务的启动、停止和重启
- Dashboard 依赖 `src/vulnmine/web/frontend/dist/` 下的静态构建产物；修改 `src/vulnmine/web/frontend/src/` 后，需要重新执行 `npm run build`
- 如果页面没有反映最新代码，优先检查三件事：
  - Web 进程是否已经重启
  - 当前访问的端口/反代是否指向最新实例
  - 浏览器是否缓存了旧 bundle

---

## 核心设计理念

`vulnmine` 的目标不是单纯做一个 sink scanner，也不是单纯做一个 LLM prompt 链。

它试图同时做两件事：

1. **保留 deterministic/provider 证据的高召回能力**（CodeQL/Joern/static facts）
2. **使用多阶段 LLM reasoning，产出结构化、可审查的漏洞报告**

核心设计哲学：

- **Evidence-grounded**: 每个结论必须有证据支撑
- **Proof-oriented**: Audit阶段以证明义务驱动
- **Multi-stage convergence**: 多轮迭代收敛而非一次性推断
- **Scope decomposition**: 将大型仓库分解为可操作的scope单元

---

## 开发流程要求

后续代码实现统一遵循：

1. 先写完整测试
2. 先确认测试在实现前是失败的
3. 再按实现计划改代码直到全部测试通过

尤其适用于：

- workflow phase 变更
- verifier / review / re-audit 行为变更
- scoring 模式变更
- evidence model 变更
- export/reporting 语义变更

---

## 架构分层详解

当前支持的分层方向是：

```text
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 1: App (CLI入口)                                               │
│   - cli.py: 参数解析、服务组装、Pipeline启动                          │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 2: Workflow (编排层)                                           │
│   - pipeline.py: RuntimePipeline编排各阶段                           │
│   - repository_planning_workflow.py (~2953行)                       │
│   - surface_workflow.py (~1162行)                                    │
│   - issue_triage_workflow.py (~1704行)                              │
│   - audit_orchestrator.py (~1147行)                                  │
│   - review_workflow.py (~1131行)                                     │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 3: Agents (推理层)                                             │
│   - prompts.py (~2190行): 所有Agent的Prompt模板                      │
│   - schemas.py (~1402行): 响应解析和规范化                           │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 4: BaseAPI (底层服务)                                          │
│   - repo_api.py (~45521行): Repository树、文件卡片、函数卡片          │
│   - static_facts_api.py: Entry/PDS/Sink收集                          │
│   - codeql_api.py (~30384行): CodeQL查询执行                         │
│   - joern_api.py (~20248行): Joern图查询                             │
│   - llm_api.py (~23961行): LLM调用和模型路由                         │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 5: Core (核心数据)                                             │
│   - models.py (~3766行): 全部数据模型定义                            │
│   - knowledge.py (~1039行): 知识状态管理                             │
│   - navigation.py: Repository导航                                    │
│   - memory.py: 记忆管理                                              │
└─────────────────────────────────────────────────────────────────────┘
```

支持的直接依赖关系：

```text
workflow -> baseapi
workflow -> core
agents   -> core
baseapi  -> core
```

不允许的架构漂移：

- `agents` 不能承担 workflow orchestration 或持久化职责
- `workflow` 不能重写 provider adapter
- `baseapi` 不能反向依赖 `agents`
- experimental 子系统不能伪装成正式支持的 runtime 路径

当前支持的端到端入口是：

```bash
python -m vulnmine.app.cli --repo /path/to/repo --workspace /path/to/workspace
```

Web 入口用于查看 artifacts、启动任务和浏览状态，但主线语义仍然以 CLI/workspace artifacts 为准。

---

## Pipeline 总览

当前支持的主流程是：

```text
initialize
  -> repository_index
  -> planning
  -> surface
  -> triage
  -> scoring
  -> audit
  -> review
  -> export
  -> dynamic_checking (placeholder)
```

每个阶段都会向 workspace 写 artifacts，后续阶段再消费这些 artifacts。

---

## 核心数据模型详解

### Issue 生命周期状态

```python
class IssueLifecycleStatus(str, Enum):
    SEEDED = "seeded"                  # Triage生成
    RANKED = "ranked"                  # Scoring排序后
    AUDIT_PENDING = "audit_pending"    # 等待Audit
    AUDIT_IN_PROGRESS = "audit_in_progress"  # Audit进行中
    REPORT_DRAFT = "report_draft"      # Audit完成，等待Review
    REVIEW_PENDING = "review_pending"  # 等待Review
    NEEDS_REVISION = "needs_revision"  # Review要求补证据
    ACCEPTED = "accepted"              # 最终接受（漏洞）
    ACCEPTED_NON_FINDING = "accepted_non_finding"  # 接受非漏洞结论
    REJECTED = "rejected"              # 最终驳回
```

### IssueCandidate (Issue候选)

核心字段：
- `issue_id`, `title`, `summary`, `file_path`, `risk`
- **三要素关联**: `entry_hint`, `sink_identity`, `pds_identity`
- **Chain关联**: `chain_id`, `hypothesis_id`
- **Planning关联**: `module_ids`, `connector_ids`
- **证明状态**: `sink_proven`, `pds_proven`, `entry_proven`, `bridge_unproven`

### RepositoryPlanningResult (规划结果)

核心产物：
- `modules`: 模块分解
- `connectors`: 连接器（跨模块桥梁）
- `analysis_scopes`: 分析范围（可操作单元）
- `scope_nodes`: Scope树结构
- `scope_tasks`: Surface任务
- `round_no`, `stop`: 多轮控制

### PlannerScopeNode (Scope树节点)

关键字段：
- `scope_id`, `scope_kind`, `title`
- **树结构**: `parent_scope_id`, `child_scope_ids`, `depth`, `is_leaf`
- **文件集合**: `focus_files`, `supporting_files`, `member_files`
- **规模估算**: `estimated_code_files`, `member_loc`, `focus_loc`
- **安全信号**: `sink_count`, `pds_count`, `estimated_surface_budget`
- **分类决策**: `static_classification`, `planner_classification`, `surface_decision`

### AttackSurface (攻击面)

三类surface：
- `execution`: 执行点（入口、危险调用等）
- `bridge`: 桥接点（跨模块/状态桥梁）
- `invariant`: 不变式点（逻辑漏洞相关）

核心字段：
- `kind`, `path`, `rationale`, `confidence`
- `attack_modes`: 攻击模式标签
- `origin_scope`, `scope_id`, `surface_role`
- `module_ids`, `connector_ids`

### EvidencePack (证据包)

组成部分：
- `anchors`: 代码锚点
- `cf_edges`: 控制流边
- `df_edges`: 数据流边
- `paths`: 数据流路径
- `coverage`: 覆盖信息
- `prompt_summary`: Provider摘要

---

## 分阶段详细说明

### 1. `initialize`

作用：

- 识别仓库语言
- 初始化 provider coverage
- 准备 CodeQL / Joern 的工作状态

主要输出：

- provider coverage summary
- CodeQL DB / Joern project readiness

注意：

- 混合语言仓库可能得到 partial provider coverage
- provider 失败会明确写入 coverage 和 runtime log

### 2. `repository_index`

作用：

- 构建后续所有阶段都要依赖的 deterministic repository map

主要内容：

- repository tree JSON
- file cards（文件级摘要：imports、exports、defined_functions、call_targets）
- function cards（函数级摘要：parameters、return_type、callers、callees）
- deterministic relations（import、call等确定性关系）
- semantic atlas slices
- invariant facts

主要输出：

- `repository_index.json`
- 压缩过的 repository navigation summary

这个阶段是后续 reasoning 的地基。如果这个阶段太弱，后面的 prompt 就会又大又模糊。

### 3. `planning`

作用：

- 在 `surface` 之前，先给仓库做 repository-level decomposition
- 给后续 `surface / triage / scoring` 提供稳定的模块、scope、connector、复杂度和热点顺序

`planning` 是 advisory，不是 filtering。

它不会把仓库其他区域从后续分析里删掉，但它会强烈影响：

- `surface` 先分析哪些模块 / connector scopes
- `triage` 怎么按 module / connector / family-role 分 seed passes
- `scoring` 如何利用 planner 产物做优先级增强

它当前主要产出：

- modules
- analysis scopes
- connectors
- scope tasks
- planner notes
- planner scope tree
- scope complexity summary
- connector ranking
- scan-origin candidates

#### Planning 核心实现逻辑

```python
def run_repository_planning_workflow(spec, services):
    planning = None
    round_memory = []
    
    for round_no in range(1, max_planning_rounds + 1):
        # 1. 构建seed packet（prompt上下文）
        seed_packet = build_planner_seed_packet(
            repo, repository_index, entries, pds, sinks,
            planning, round_memory, round_no
        )
        
        # 2. 执行planner agent
        response = planner_agent.run_session_turn(...)
        
        # 3. 处理tool requests（grep、tree_slice等）
        if response.tool_requests:
            tool_results = execute_planner_tool_requests(...)
        
        # 4. 验证scope和connector
        scope_warnings = validate_analysis_scopes(...)
        connector_warnings = validate_connector_scopes(...)
        
        # 5. 构建scope tree
        planning = build_planner_scope_tree(...)
        
        # 6. No-regression检查
        no_regression_warnings = check_no_regression(...)
        
        # 7. 更新round memory
        append_planner_round_memory(...)
        
        # 8. 检查是否停止
        if response.stop and not no_regression_warnings:
            break
    
    # 9. 自动第5轮reconcile（如果仍有未解决项）
    if round_no == max_planning_rounds and unresolved_items:
        # 执行额外一轮
    
    # 10. 保存结果
    save_repository_planning(...)
```

#### build_planner_seed_packet 核心逻辑

```python
def build_planner_seed_packet(...):
    # 1. 构建LOC树（带动态裁剪）
    full_tree = build_loc_tree(repo)
    
    # 动态深度控制
    max_depth = compute_dynamic_depth(repo_size, round_no)
    
    # 裁剪background路径（node_modules/vendor等）
    reduced_tree = reduce_tree(full_tree, roots, suppressed, depth=max_depth)
    
    # 2. 构建directory_meta（层级聚合）
    directory_meta = {
        "totals": {...},
        "level1": [...],  # 顶层目录聚合
        "level2": [...],  # 二级目录聚合
    }
    
    # 3. 按kind分组facts
    entries_by_kind = build_grouped_fact_inventory(entries)
    pds_by_kind = build_grouped_fact_inventory(pds)
    sinks_by_kind = build_grouped_fact_inventory(sinks)
    
    # 4. 构建accepted structure snapshot（前轮稳定结构）
    accepted_structure_md = render_accepted_structure_md(planning)
    
    # 5. 构建round memory summary
    round_memory_md = render_planner_round_memory_md(round_memory)
```

#### Scope复杂度估算

```python
LEAF_CODE_FILE_LIMIT = 80      # 叶节点最多80个代码文件
LEAF_FOCUS_LOC_LIMIT = 2500    # 叶节点聚焦LOC上限
LEAF_MEMBER_LOC_LIMIT = 8000   # 叶节点成员LOC上限
LEAF_SURFACE_BUDGET_LIMIT = 80 # 叶节点surface预算上限

def estimate_scope_complexity(scope_files, repo, entries, pds, sinks):
    code_files = count_code_files(scope_files)
    member_loc = compute_total_loc(scope_files)
    focus_loc = compute_focus_loc(scope_files)
    sink_count = count_facts_in_scope(sinks, scope_files)
    pds_count = count_facts_in_scope(pds, scope_files)
    
    estimated_surfaces = min(sink_count * 2 + pds_count + 10, LEAF_SURFACE_BUDGET_LIMIT)
```

#### Scope分类逻辑

```python
def classify_scope_paths(scope_files):
    for path in scope_files:
        # 检测背景路径
        if "node_modules" in path:
            return ("third_party", "background_third_party", "skip")
        if "/vendor/" in path:
            return ("third_party", "background_third_party", "skip")
        if "/generated/" in path:
            return ("generated", "background_generated", "skip")
        if "/test/" in path:
            return ("tests_examples_docs", "background_tests_examples_docs", "deprioritize")
        
        # 检测layered结构
        if any(token in path for token in ["/web/", "/service/", "/dao/", "/model/"]):
            return ("first_party", "layered_first_party", "keep")
        
        return ("first_party", "first_party", "keep")
```

#### Planning loop 图

```text
repository_index + static facts + optional semantic/invariant context + planner notes
        |
        v
  round 1: coarse decomposition
        |
        +--> optional planner tool requests
        |            |
        |            v
        |      deterministic tool results
        |            |
        +------------+
        |
        v
  later rounds: refine oversized modules/scopes, preserve stable ones
        |
        v
  final planning_result.json + planner artifacts
```

当前行为：

- 支持 prompt mode 和 SDK mode
- 支持多轮 refinement，且后续 round 会显式收到 budget-aware feedback
- 已有"保留稳定边界、只细化超预算模块"的 round guidance
- 如果模型漏掉 analysis scopes，会从 modules 合成
- 如果模型漏掉 connector 字段，会尝试绑定或合成 connector
- SDK mode 会写 `sdk_chat_history.md`、`sdk_turn_*`、`llm_call_*`

当前 planning 和其他 phase 的配合关系：

- `repository_index -> planning`
  - 提供文件树、函数卡片、deterministic relations、interesting paths
- `static facts -> planning`
  - 提供 entries / PDS / sinks，帮助 planner 判断 entry layer、data layer、危险热点和 connector 候选
- `planning -> surface`
  - 提供 module scopes、connector scopes、scope tasks、connector files 和 planner notes
- `planning -> triage`
  - 提供 planning summary、scope tree、connector ranking、scan-origin candidates
- `surface -> planning`
  - 当前 triage 内已能调用 planning pass helper，用 surface summary 帮助后续范围收敛或解释补强

从实际运行经验看：

- 好的 planning 对后面三个 phase 都有影响，不只是 `surface`
- 给 planning 很大的 token / round budget 未必有收益，常见收益点在于 boundary 稳定和 connector 质量
- SDK mode 下模型有时会少用甚至不用工具，因此 fallback 逻辑和 refinement guidance 很重要

### 4. `surface`

作用：

- 在 issue 生成之前，先把"可能值得怀疑的执行点、桥接点、状态点、效果点"结构化出来

`surface` 最终会产出三类 surfaces：

- `execution`: 执行点（入口、危险调用等）
- `bridge`: 桥接点（跨模块/状态桥梁）
- `invariant`: 不变式点（逻辑漏洞相关）

这些不是最终漏洞，只是结构化的攻击相关面。

当前 `surface` 不再是"对整个 repo 做几个大 prompt"。

它已经变成：

- 基于 planning 产物生成很多 `SurfaceRoundPlan`
- 每个 round plan 带着自己的 scope、connector、local facts、role 进入 agent
- LLM 可以要求 deterministic recon action
- 每轮结果会被注解回所属 scope/module/connector，再和全局 surface set 合并

#### SurfaceRoundPlan 结构

```python
@dataclass(frozen=True)
class SurfaceRoundPlan:
    round_kind: str  # "scope_baseline", "scope_helper", "scope_gap", "scope_connector_aware"
    scope_id: str
    round_index: int
    focus_paths: tuple[str, ...]
    module_ids: tuple[str, ...]
    connector_ids: tuple[str, ...]
    local_fact_paths: tuple[str, ...]
    summary_md: str
    scope_depth: int
```

#### Surface去重和注解

```python
def _dedupe_surfaces(items):
    # 按 kind+path 去重，合并attack_modes/module_ids/connector_ids
    merged: dict[tuple[str, str], AttackSurface] = {}
    for item in items:
        key = (item.kind, item.path)
        if key in merged:
            existing = merged[key]
            merged[key] = AttackSurface(
                attack_modes=tuple(dict.fromkeys((*existing.attack_modes, *item.attack_modes))),
                module_ids=tuple(dict.fromkeys((*existing.module_ids, *item.module_ids))),
                connector_ids=tuple(dict.fromkeys((*existing.connector_ids, *item.connector_ids))),
                ...
            )
```

#### Surface loop 图

```text
planning result + repository_index + static facts + scan-origin + optional graph context
        |
        v
  build surface round plans
        |
        +--> global round
        +--> scope_baseline rounds
        +--> scope_helper rounds
        +--> scope_gap rounds
        +--> scope_connector_aware rounds
                    |
                    v
              each plan -> one LLM call
                    |
                    v
              deterministic recon actions
                    |
                    v
              merged surface set / grouped families / round artifacts grow over time
```

重要说明：

- 一条日志里的 `surface round`，本质上对应一个执行过的 `SurfaceRoundPlan`
- 在启用 planner 时，总执行 round 数经常明显大于直觉上的 `max_surface_rounds`
- 因为 `max_surface_rounds` 更像"每类 planning scope 的局部轮数控制"，不是"planner-enabled 场景下的全局总 LLM 调用上限"
- 同一个 repo 最终实际执行量更接近：`scope 数 * 每 scope 派生 round plan 数`

#### Surface 的数据来源

`surface` 开始前会从 deterministic/provider 侧拿到一批初始 seeds：

- sink facts
- PDS facts
- invariant-derived surfaces
- related-file deterministic promotions

当前 `surface` 还会直接消费或间接使用：

- planning modules / analysis scopes / connectors
- planner notes
- semantic atlas 导航上下文
- scan-origin candidates
- graph bridge 相关上下文

当前系统还已经实现两段 graph-bridge 相关能力：

- `pre_graph_hints`
  - 供 planning 参考的粗粒度 bridge / family hints
  - 默认仍可关闭
- `graph_bridge_bundle`
  - surface 后产出的跨模块/跨链路 bridge 假设与上下文
  - 供 `triage / scoring / audit / review` 消费

当前系统还已经实现 provider graph strategy 与 connector-file extraction 的基础能力：

- `provider_graph_mode`
  - `auto`
  - `full_graph`
  - `sharded_graph`
  - `hybrid_graph`
- connector files 会综合：
  - planner connectors
  - bridge surfaces
  - scan-origin candidates
  - relation density
  进行候选抽取

这些能力的现实作用不是"装饰信息"，而是决定：

- 哪些 connector 更值得形成独立 scope
- 哪些文件在 graph-bridge 建模里被保留
- 哪些跨模块线索会进入后续 triage / scoring / audit

### 5. `triage`

作用：

- 把 surfaces 转成 issue candidates
- 在启用 chain-first 时，同时构造和扩展 chain sketches / hypotheses
- 给后续 scoring 准备更结构化的 issue inventory，而不是只留下"surface 标题"

这一层已经不再是单一 prompt，而是一个多输入、多批次的编排层。

它会使用：

- persisted surface set
- grouped surface families
- planning summary / scope tree / planner notes
- static facts（`entries / pds / sinks`）
- invariant bundle 与 invariant-derived surfaces
- semantic atlas related paths
- graph bridge bundle
- scan-origin candidates
- run knowledge state

#### TriageSeedPass 结构

```python
@dataclass(frozen=True)
class TriageSeedPass:
    scope_kind: str  # "global", "module", "connector", "family_role"
    scope_id: str
    summary_md: str
    surfaces: tuple[AttackSurface, ...]
    grouped_surfaces: dict[str, tuple[AttackSurface, ...]]
    focus_paths: tuple[str, ...]
    module_ids: tuple[str, ...]
    connector_ids: tuple[str, ...]
```

#### Issue合并去重

```python
def _merge_issue_candidates(issues):
    # 按多重键合并
    merged: dict[tuple[str, ...], IssueCandidate] = {}
    for issue in issues:
        normalized_title = normalize_title(issue.title)
        merge_key = (
            issue.file_path,
            normalized_title,
            issue.sink_identity,
            issue.pds_identity,
            issue.entry_hint,
            issue.chain_id,
            issue.hypothesis_id,
        )
        if merge_key in merged:
            existing = merged[merge_key]
            merged[merge_key] = replace(existing,
                tags=tuple(dict.fromkeys((*existing.tags, *issue.tags))),
                focus_paths=tuple(dict.fromkeys((*existing.focus_paths, *issue.focus_paths))),
                module_ids=tuple(dict.fromkeys((*existing.module_ids, *issue.module_ids))),
                connector_ids=tuple(dict.fromkeys((*existing.connector_ids, *issue.connector_ids))),
            )
```

#### Triage loop 图

```text
surface set + grouped families + planning context + chain/invariant/graph context
        |
        v
  build triage seed passes
        |
        +--> global pass
        +--> module scope passes
        +--> connector scope passes
        +--> family/role clustered passes
                    |
                    v
              issue seed agent per pass
                    |
                    +--> optional SDK tool phase
                    |
                    v
              issue seeds + chain sketches + notes
                    |
                    v
              aggregation / dedupe / issue materialization
```

当前 triage 的关键变化：

- triage 已经明显依赖 planning 的 module / connector 结构
- triage 不只是"从 surfaces 写标题"，而是把 surface families、hotspot roles、connector context、scan-origin 线索一起编排成 seed passes
- 如果启用了 chain-first，triage 还会进入 chain discovery / expansion 路径，产出后续 scoring 可消费的 chain state

在大仓库上，这一层最容易发生的不是 token 爆炸，而是 seed pass 数量爆炸。

#### Chain-first模式

```python
def run_chain_first_triage(spec, surfaces, services):
    # 1. Chain Discovery
    chain_sketches = chain_discovery_agent.run(surfaces, bridge_surfaces, invariant_surfaces, ...)
    
    # 2. Chain Expansion
    for chain in chain_sketches:
        expansion = chain_expansion_agent.run(chain=chain, ...)
        chain = merge_chain_expansion(chain, expansion)
        save_chain_state(workspace, run_id, chain)
    
    # 3. Chain Classification
    classification_sets = []
    for chain in chains:
        classification = chain_classification_agent.run(chain=chain, ...)
        classification_set = ClassificationSet.from_dict(classification)
        save_classification_set(workspace, run_id, classification_set)
        classification_sets.append(classification_set)
    
    # 4. Issue seeding with chain context
    for chain in chains:
        for hypothesis in classification_set.hypotheses:
            # 基于chain+hypothesis生成issue
            ...
```

### 6. `scoring`

作用：

- 对 issue candidates 排序，决定哪些进入 `audit`
- 在启用 chain-first 时，把 classification / chain lifecycle / graph hints 合并进排序
- `chain_convergence` 现在有独立开关 `enable_chain_convergence`；默认关闭，关闭时仍走 main 的原始 scoring 路径

`scoring` 不是 proof，而是 prioritization。

#### 当前 scoring 路径

```text
issues + optional chains + optional classification sets + scan-origin + graph-bridge
       |
       v
  deterministic pre-score
       |
       +--> optional mixed batching
       +--> optional family-bucket batching
       |
       v
  final rerank
       |
       v
  ranked_issues.json
```

#### 当前 scoring 三种模式

- `single`
  - 直接对候选做一次全局排序
- `mixed_batch`
  - pre-score 后保留前部候选，按 batch 局部排序，再全局 rerank
- `family_bucket`
  - 先按 family 分桶，再做 family 内排序和全局 rerank

#### Boost机制

```python
def apply_scan_origin_boosts(issues, scan_origin_candidates):
    # 强provider/scanner发现的issue获得boost
    for issue in issues:
        if any(candidate.file_path == issue.file_path for candidate in scan_origin_candidates):
            issue = replace(issue, score=issue.score + boost_factor)
    return issues

def apply_graph_bridge_boosts(issues, graph_bridge_bundle):
    # 与graph bridge hypothesis关联的issue获得boost
    for issue in issues:
        if any(hypothesis.file_path == issue.file_path for hypothesis in graph_bridge_bundle.hypotheses):
            issue = replace(issue, score=issue.score + boost_factor)
    return issues
```

当前 scoring 和其他 phase 的配合：

- `triage -> scoring`
  - 输入 issue candidates、chain candidates、classification sets
- `planning / surface -> scoring`
  - scan-origin boosts、graph bridge boosts、focus points 会影响 pre-score 与 rerank
- `scoring -> audit`
  - 输出 ranked issues，并确定有限 audit 预算应该花在哪些 issue 上

现实含义：

- `scoring_enable_batching` 打开后，系统会更便宜，但也会更早裁剪候选
- `family_bucket` 适合防止某类高频热点把别的 family 完全压掉

### 7. `audit`

作用：

- 以 proof obligations 为中心，对 ranked issues 做深入调查
- 通过多轮 planner/executor/synthesizer/auditor 闭环，把 speculative issue 变成 grounded dossier 或明确驳回

`audit` 是当前最"agentic"的 phase 之一，但它仍然强依赖 deterministic/provider 证据。

它内部有多个子 agent：

- investigation planner
- intent executor
- investigation synthesizer
- audit agent

#### Audit 核心闭环

```
┌─────────────────────────────────────────────────────────────────────┐
│                         Audit Loop                                  │
│                                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  ┌──────────┐│
│  │ Investigation│→ │ Intent       │→ │ Investigation│→ │ Audit    ││
│  │ Planner      │  │ Executor     │  │ Synthesizer  │  │ Agent    ││
│  └──────────────┘  └──────────────┘  └──────────────┘  └──────────┘│
│        ↓                  ↓                  ↓              ↓      │
│    intents           observations         knowledge_delta   verdict │
│                                              ↓                     │
│                                    report_ready? stop?              │
│                                              ↓                     │
│                                    continue or break                │
└─────────────────────────────────────────────────────────────────────┘
```

#### Audit 核心实现

```python
def run_issue(issue, spec, services):
    state = load_issue_knowledge_state(workspace, run_id, issue.issue_id)
    
    for round_no in range(1, max_rounds_per_issue + 1):
        # 1. Investigation Planner - 规划调查意图
        planner_response = investigation_planner_agent.run(
            issue, repository_index_md, knowledge_summary_md,
            chain_summary_md, hypothesis_summary_md, proof_obligations_md,
            round_no, max_rounds
        )
        intents = tuple(InvestigationIntent.from_dict(item) for item in planner_response.intents)
        
        # 2. Intent Executor - 执行调查意图
        batch = intent_executor.execute(intents, repo, focus_paths, focus_functions, ...)
        
        # 3. Investigation Synthesizer - 综合知识
        delta = investigation_synthesizer_agent.run(
            issue, latest_observations_md, knowledge_summary_md, graph_bridge_md
        )
        
        # 4. 更新knowledge state
        state = rebuild_and_save_issue_knowledge_state(
            workspace, run_id, issue.issue_id,
            verified_facts=delta.verified_facts,
            hypotheses=delta.hypotheses,
            open_gaps=delta.open_gaps,
            file_relations=delta.file_relation_updates,
            control_flows=delta.control_flow_updates,
            data_flows=delta.data_flow_updates,
            focus_paths=_focus_paths(issue, state, batch, delta),
            focus_functions=_focus_functions(issue, state, batch, delta),
            draft_report_md=delta.draft_report_md,
        )
        
        # 5. 检查是否收敛
        if delta.report_ready or delta.stop_reason:
            break
    
    # 6. Audit Agent - 最终判决
    audit_response = audit_agent.run(
        issue, knowledge_summary_md, chain_summary_md, hypothesis_summary_md
    )
    
    # 7. 保存audit结果
    save_audit_result(workspace, run_id, issue.issue_id, {...})
```

#### Proof Obligations构建

```python
def _build_proof_obligations(issue, chain, hypothesis, reviewer_required_proof):
    obligations: list[ProofObligation] = []
    
    # Chain gaps
    for gap in chain.open_gaps:
        obligations.append(ProofObligation(kind="chain_gap", question=gap, status="open"))
    
    # Hypothesis missing proof
    for proof in hypothesis.missing_proof:
        obligations.append(ProofObligation(
            kind="hypothesis_missing_proof",
            question=f"Prove: {proof}",
            status="open",
        ))
    
    # INVARIANT chain special checks
    if chain.chain_kind == ChainKind.INVARIANT:
        obligations.extend([
            ProofObligation(kind="selector_control", question="Verify attacker can control the selector..."),
            ProofObligation(kind="guard_verification", question="Verify the guard is absent/weak..."),
            ProofObligation(kind="resource_binding", question="Verify selector binds to protected resource..."),
            ProofObligation(kind="protected_effect", question="Verify protected effect is reachable..."),
        ])
    
    # Reviewer required proof
    for proof in reviewer_required_proof:
        obligations.append(ProofObligation(kind="review_required", question=proof, status="open"))
    
    return tuple(obligations)
```

#### Audit loop 图

```text
ranked issue
   |
   v
round N:
  planner -> intents
      |
      v
  deterministic/provider execution
      |
      v
  synthesizer -> verified facts / gaps / memory delta
      |
      v
  auditor -> verdict / rationale / report readiness
      |
      +--> continue if more proof needed
      |
      v
  audit_result.json + round artifacts
```

当前 audit 会消费：

- ranked issue metadata
- evidence slices
- code context bundles
- chain / hypothesis state（如果启用 chain-first）
- graph bridge hints
- prior round memory and knowledge state
- review 退回产生的 `needs_revision` 状态

重要说明：

- `audit` 的核心目标不是"给 issue 写更多文字"，而是不断收敛 proof obligations
- `audit_workers` 控制 issue 级并发，不改变单个 issue 内部多轮结构
- review 退回后，pipeline 会把 issue 重新放回 `AUDIT_PENDING`，然后重新进入 audit

### 8. `review`

作用：

- 对 audit 输出做保守审查
- 明确区分"接受真实 finding""接受非漏洞结论""驳回""要求补证据"

`review` 不是简单 ballot 汇总，它现在已经包含 verifier-style targeted checking 语义。

它可以：

- `accept`
- `reject`
- `needs_revision`

#### Review模式

```python
class ReviewMode(str, Enum):
    STANDARD = "standard"      # 标准审查
    REBUTTAL = "rebuttal"      # 反驳模式（质疑已接受结论）
    ADVERSARIAL = "adversarial"  # 对抗模式（寻找反例）
```

#### Review 核心实现

```python
def run_single_issue_review(issue, spec, services):
    # 1. 加载context
    audit_result = load_audit_result(workspace, run_id, issue.issue_id)
    chain, classification_set, hypothesis = load_chain_review_context(issue)
    proof_chain = load_proof_chain(workspace, run_id, issue.issue_id)
    knowledge_state = load_issue_knowledge_state(workspace, run_id, issue.issue_id)
    
    # 2. 构建review task
    task = AgentTask(context=PromptContext(
        issue, audit_result, chain_summary_md, hypothesis_summary_md,
        proof_obligations_md, knowledge_summary_md
    ))
    
    # 3. 执行review（多模型投票）
    ballots = []
    for model in review_models:
        result = review_agent.run(task=task, model=model, ...)
        ballot = ReviewBallot.from_dict(result.parsed_response)
        ballots.append(ballot)
    
    # 4. Vote决策
    decision = vote_agent.run(ballots=ballots)
    
    # 5. 后处理检查
    if decision.verdict == "accept":
        # 检查是否有grounded evidence
        if not _has_grounded_evidence(knowledge_state):
            decision = _downgrade_hypothesis_only_accept(decision)
        
        # Prototype pollution特殊检查
        if _is_prototype_pollution_issue(issue) and review_mode == "rebuttal":
            if not _prototype_pollution_has_strong_mechanism(audit_result, knowledge_state):
                decision = _downgrade_prototype_pollution_accept(decision)
    
    # 6. 更新issue状态
    if decision.verdict == "accept":
        new_status = IssueLifecycleStatus.ACCEPTED
    elif decision.verdict == "reject":
        new_status = IssueLifecycleStatus.REJECTED
    else:  # needs_revision
        new_status = IssueLifecycleStatus.NEEDS_REVISION
        revision_requests = _build_revision_requests(issue.issue_id, decision.required_proof)
    
    transition_issue_progress(workspace, run_id, issue.issue_id, to_status=new_status)
```

#### Evidence grounding检查

```python
def _has_grounded_evidence(state):
    grounded = {ReliabilityLevel.DETERMINISTIC, ReliabilityLevel.PROVIDER_HINT}
    
    # 检查verified_facts
    if any(fact.reliability in grounded for fact in state.verified_facts):
        return True
    
    # 检查file_relations
    if any(rel.status == "verified" and rel.reliability in grounded for rel in state.file_relations):
        return True
    
    # 检查flows
    if any(flow.status == "verified" and flow.reliability in grounded for flow in (*state.control_flows, *state.data_flows)):
        return True
    
    return False
```

当前 workflow 支持自动闭环：

```text
review -> needs_revision -> re-audit -> review
```

由 `max_revision_cycles` 控制。

当前 review 还会附带 verifier-style targeted checks，并额外产出 verifier 相关 bundle。其语义已经支持：

- `verified_assertions`
- `inferred_assertions`
- `disproved_assertions`
- `unresolved_gaps`
- `revision_requests`
- conditional finding 条件集合

当前 review 语义上需要特别注意：

- `accept` 应该表示接受一个最终结论，而不等于"这个结论一定是漏洞"
- 因此 export 时会把"accepted true finding"和"accepted_non_finding"分开
- 如果只有假设、没有 grounded deterministic/provider evidence，review 可以主动降级为 `needs_revision`
- 在 prototype pollution 这类高误报问题上，review 还有更强的 rebuttal gate

### 9. `export`

作用：

- 生成最终报告目录和 run summary
- 根据 review / audit / hypothesis 状态，把结果分流到不同输出目录

系统可能做了很多工作，但最终：

- `accepted findings = 0`

这不一定意味着 pipeline 坏了，也可能只是 review 把 speculative findings 都压掉了。

当前 export 会按最终结果拆目录：

- `reports/<run_id>/findings/`  # 确认漏洞
- `reports/<run_id>/findings/conditional/`  # 条件性漏洞
- `reports/<run_id>/accepted_non_findings/`  # 接受的非漏洞结论
- `reports/<run_id>/needs_revision/`  # 需要补证据（未完成闭环）
- `reports/<run_id>/rejected/`  # 被驳回的报告
- `reports/<run_id>/supporting_variants/`  # 同一 finding 下的支撑变种

这样 export 明确区分：

- 真正漏洞
- 条件性漏洞
- 被接受的非漏洞结论
- 需要补证据的报告
- 被驳回的报告
- 同一 finding 下的 supporting variants

### 10. `dynamic_checking`

作用：

- 为 export 后的动态验证预留 phase 名称和 artifact 位置

当前状态：

- 已接入 pipeline
- 默认关闭
- 当前仍是 placeholder，只写出空结果 artifact，不做真实动态执行

因此它现在的意义主要是：

- 让 phase 顺序和 workspace 结构提前稳定下来
- 避免未来再改 pipeline step 名称和输出布局

---

## Phase 之间怎么配合

如果只看 phase 名字，很容易误以为系统还是旧版的线性串联。

当前更准确的理解是：

```text
repository_index
  -> 提供 deterministic repo map、interesting paths、relations、可选 semantic/invariant 基础

planning
  -> 把 repo 组织成 modules / connectors / scope tree / scope tasks / planner notes

surface
  -> 基于 planning 结构做多轮局部侦察，产出 attack surfaces 和 grouped families

triage
  -> 基于 surfaces + planning + graph/invariant/semantic context 产出 issue candidates / chains

scoring
  -> 基于 issue candidates + chain state + boosts 做优先级排序

audit
  -> 对 top-ranked issues 做多轮 proof-oriented investigation

review
  -> 对 audit dossier 做保守审查，必要时发 revision 再回 audit

export
  -> 按 verdict 和 finding 类型整理最终输出
```

几个最关键的现实配合点：

- `planning` 不再只是 `surface` 的前言，而是后面多个 phase 的结构地基
- `surface` 不再直接把结果变成 issue，而是先把 repo 中的"攻击相关面"系统化
- `triage` 是第一个真正把 planning、surface、graph、semantic、invariant、scan-origin 这些线索汇总起来的 phase
- `scoring` 不是 proof phase，但它已经开始消费 chain / graph / scan-origin 结构化信号
- `review` 不只是投票，还会改写 lifecycle，并可能触发自动 re-audit 闭环

---

## 知识管理系统

### RunKnowledgeState (运行级)

```python
@dataclass(frozen=True)
class RunKnowledgeState:
    run_id: str
    memory_items: tuple[MemoryItem, ...]  # 跨Issue共享记忆
    
    verified_facts: tuple[KnowledgeFact, ...]
    hypotheses: tuple[KnowledgeFact, ...]
    open_gaps: tuple[KnowledgeGap, ...]
    
    observations: tuple[ObservationRecord, ...]
    active_issue_ids: tuple[str, ...]
```

### IssueKnowledgeState (Issue级)

```python
@dataclass(frozen=True)
class IssueKnowledgeState:
    run_id: str
    issue_id: str
    
    focus_paths: tuple[str, ...]
    focus_functions: tuple[str, ...]
    
    verified_facts: tuple[KnowledgeFact, ...]
    hypotheses: tuple[KnowledgeFact, ...]
    open_gaps: tuple[KnowledgeGap, ...]
    
    file_relations: tuple[FileRelationRecord, ...]
    control_flows: tuple[FlowRecord, ...]
    data_flows: tuple[FlowRecord, ...]
    
    reviewer_constraints: tuple[str, ...]
    draft_report_md: str
```

### KnowledgeDelta (审计增量)

```python
@dataclass(frozen=True)
class KnowledgeDelta:
    verified_facts: tuple[KnowledgeFact, ...]  # 新验证的事实
    hypotheses: tuple[KnowledgeFact, ...]      # 新假设
    open_gaps: tuple[KnowledgeGap, ...]        # 新缺口
    
    file_relation_updates: tuple[FileRelationRecord, ...]
    control_flow_updates: tuple[FlowRecord, ...]
    data_flow_updates: tuple[FlowRecord, ...]
    
    next_focus_paths: tuple[str, ...]
    next_focus_functions: tuple[str, ...]
    
    report_ready: bool
    stop_reason: str
```

### ReliabilityLevel (可靠性层次)

```python
class ReliabilityLevel(str, Enum):
    DETERMINISTIC = "deterministic"     # 确定性（代码直接证据）
    PROVIDER_HINT = "provider_hint"     # Provider提示（CodeQL/Joern）
    LLM_HYPOTHESIS = "llm_hypothesis"   # LLM假设
    DERIVED = "derived"                 # 推导得出
```

**Review只接受有grounded evidence的结论**:
```python
if decision.verdict == "accept" and not _has_grounded_evidence(state):
    decision = _downgrade_hypothesis_only_accept(decision)  # 降级为needs_revision
```

---

## BaseAPI 交互模型

`baseapi` 层负责提供 deterministic 和 provider-backed evidence。

主要类别包括：

- `repo_api`
  - repository tree
  - file cards
  - function cards
  - deterministic relations
- `static_facts_api`
  - entries
  - PDS facts
  - sink facts
- `codeql_api`
  - language-specific CodeQL queries
  - provider coverage 和 query execution
- `joern_api`
  - focused graph queries
- `evidence_api`
  - slices 和 evidence index
- `llm_api`
  - stage-aware model routing 和 request execution

### Fact 交互图

```text
repo
 |
 +--> repo_api --------------------------+
 |                                       |
 +--> static_facts_api ----------------+ |
 |                                     | |
 +--> codeql_api -------------------+  | |
 |                                  |  | |
 +--> joern_api ------------------+ |  | |
 |                                | |  | |
 v                                v v  v v
     repository_index / planning / surface / triage / scoring / audit
```

当前现实情况：

- provider facts 在部分阶段还更像"强提示"，不是"必须保留的候选"
- preserved scan-origin candidates 是让强 provider/scanner 信号保留更久的开始

---

## 多轮行为总览

### Planner

- 多轮，但 round 的目标现在是 refinement，不是全局重做
- SDK mode 下一个 round 可由多个 subturn 组成，最后一个 subturn 预留给 final commit
- 每轮可能有 planner tool requests，也可能完全没有工具调用
- 现在会显式记录 `sdk_turn_*`、`llm_call_*`、planner notes 和 round memory
- **固定4轮 + 自动第5轮reconcile**：未解决项触发额外一轮

### Surface

- 多轮、多 scope、多 plan
- 每个执行过的 `SurfaceRoundPlan` 基本对应一次独立推理单元
- 有 planner 时，scope-local rounds 可能很多，所以看日志时要关注 total executed plans

### Audit

- 每个 issue 多轮
- 每轮包含 planner / execution / synthesizer / auditor 子步骤
- 直到 report_ready 或 stop_reason

### Triage

- 多 batch，不是一个大 prompt 一次吃完
- 在大仓库上真正膨胀的通常是 seed passes 数，不只是 prompt 大小

### Scoring

- 默认可视为单轮全局排序
- 也支持 mixed-batch 和 family-bucket 两类裁剪/重排路径

### Review

- revision闭环：review → needs_revision → audit → review（最多max_revision_cycles次）

---

## Agent层设计

### Prompt模板结构

核心PromptInput类：

- `RepositoryPlanningPromptInputs`: Planner Agent输入
- `SurfacePromptInputs`: Surface Agent输入
- `IssueSeedPromptInputs`: Issue Seed Agent输入
- `InvestigationPlannerPromptInputs`: Investigation Planner输入
- `InvestigationSynthesizerPromptInputs`: Synthesizer输入

### Schemas (响应解析)

JSON解析容错链：

```python
def parse_agent_json(text):
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        # 1. 尝试提取fenced JSON
        fenced = _extract_fenced_json(text)  # ```json...```
        if fenced:
            return parse_agent_json(fenced)
        
        # 2. 尝试修复truncated JSON
        repaired = _repair_truncated_json(text)  # 补闭合括号
        if repaired:
            return repaired
        
        # 3. 提取第一个JSON值
        extracted = _extract_first_json_value(text)  # raw_decode
        if extracted:
            return extracted
        
        raise
```

核心响应类：

- `RepositoryPlanningResponse`: modules, connectors, analysis_scopes, tool_requests, notes, stop
- `SurfaceAgentResponse`: surfaces, actions, stop
- `AuditAgentResponse`: verdict, summary, required_proof
- `ReviewAgentResponse`: verdict, rationale, required_proof, accepted_facts, rejected_facts

---

## 模型路由策略

运行时支持 stage-aware model routing。

当前 `config/runtime.json` 默认：

- `repository_index`: `glm-5 -> gpt-5.4`
- `planner`: `glm-5 -> gpt-5.4`
- `surface`: `glm-5 -> gpt-5.4`
- `triage`: `glm-5 -> gpt-5.4`
- `scoring`: `glm-5 -> gpt-5.4`
- `audit`: `glm-5 -> gpt-5.4`
- `review`: `glm-5 -> gpt-5.4`

工作方式：

- 先试当前 stage 的 preferred models
- 再把全局 `call_order` 中剩余模型作为 fallback 接上

---

## 重要默认值和实际含义

当前 `config/runtime.json` 里的重要默认值：

- `max_candidate_issues = 1200`
- `max_surface_rounds = 6`
- `max_rounds_per_issue = 12`
- `max_revision_cycles = 1`
- `max_intents_per_round = 10`
- `enable_repo_planning = false`
- `planning_mode = prompt`
- `surface_mode = prompt`
- `audit_mode = prompt`
- `enable_smart_slicing = false`
- `scoring_enable_batching = false`
- `scoring_mode = single`
- `enable_chain_convergence = null`（由 profile 解析后默认关闭）

重要说明：

- `repo_index_max_files = 0`, `repo_index_max_relations = 0`, `repo_index_max_functions = 0` 表示"使用 adaptive defaults"，不是 literal zero。
- `max_surface_rounds` 在当前实现里更接近"每类 scope-plan 的局部轮数预算"；有 planner 时，总执行 surface rounds 可能远大于这个值。
- `max_planning_rounds` 影响的是 repo-level refinement 轮数，不等于 SDK subturn 数；SDK subturn 由 `planning_sdk_turns_per_round` 和 `planning_sdk_max_turns` 进一步限制。
- batched scoring 已实现，但默认关闭，因为它本质上会减少进入 final scoring 的候选数。
- `scoring_mode` 现在支持 `single`、`mixed_batch`、`family_bucket`，默认仍是 `single`。
- `chain_convergence` 有独立开关，默认关闭；只有在 `enable_chain_first_analysis=true` 且 `enable_chain_convergence=true` 时才启用 pre-scoring convergence。
- `accepted_non_finding` 现在是独立生命周期，不再与真正 finding 的 `accepted` 混用。
- `graph_bridge_completion` 相关开关已经存在，但默认关闭。
- `dynamic_checking` 已经进入 pipeline step 列表，但当前仍是 placeholder phase。
- pipeline 现在支持一轮自动的 `review -> re-audit -> review` 闭环，由 `max_revision_cycles` 控制。

---

## 关键设计模式总结

### 多轮迭代收敛

- **Planning**: 4轮 + 自动第5轮reconcile
- **Surface**: 多scope、多round plan
- **Audit**: 每issue多轮，直到report_ready或stop
- **Review**: revision闭环（review → audit → review）

### Scope Tree结构

```
root_scope (repo-level)
    ↓
module_scope (模块级)
    ↓ (如果有connector)
connector_scope (连接器级)
    ↓
composite_scope (复合scope，超预算模块细分)
    ↓
leaf_scope (叶节点，可操作单元)
```

### Proof Obligations驱动

每个claim必须有证据支撑：
- Chain gaps → 必须填补
- Hypothesis missing proof → 必须证明
- INVARIANT chain → 特殊检查（selector控制、guard验证等）
- Reviewer required proof → 必须响应

### Evidence可靠性分层

```
┌─────────────────────────────────────────────────────────────────────┐
│ DETERMINISTIC (最高可靠)                                             │
│   - 代码直接读取、grep匹配、静态分析                                 │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ PROVIDER_HINT                                                        │
│   - CodeQL query结果、Joern graph结果                                │
└─────────────────────────────────────────────────────────────────────┘
                              ↓
┌─────────────────────────────────────────────────────────────────────┐
│ LLM_HYPOTHESIS (最低可靠)                                            │
│   - LLM推断的假设                                                    │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 当前工作焦点

### 已修复问题（见 BUGS.md）

1. ✅ **Planner prompt wording歧义**：round1看起来像final round → 明确"not necessarily final planning round"

2. ✅ **directory_meta过长**：path-by-path → 层级聚合（totals/level1/level2）

3. ✅ **repo_tree动态收紧**：按repo大小和round调整深度，background路径裁剪

4. ✅ **Prompt-mode memory收敛**：accepted-decision ledger + delta跟踪 + no-regression检查

5. ✅ **固定4轮 + 自动第5轮reconcile**：未解决项触发额外一轮

### 待解决问题

**Triage重复问题（BUGS #2）**:
- 大量near-duplicate hypotheses
- 高review reject率
- 原因分析：
  - seed passes overlap（global/module/connector/family_role重叠）
  - merge key不够强（只按file_path/title/sink等）
  - 缺乏static pre-filters（ORM参数化SQL、非用户控制URL）

**建议修复方向**:
- Pass ownership reduction：明确各pass的职责边界
- Merge-key de-noising：增强合并键，引入语义相似度
- Static guardrails：ORM参数化SQL直接过滤，非用户控制URL直接过滤

---

## 当前的 Recall 改进方向

最近的 recall 相关工作包括：

- `ScanOriginCandidate`
- 强 sink/PDS/provider 发现写入 workspace artifact
- `pre_graph_hints`
- post-surface `graph_bridge_bundle`
- triage prompt 注入 preserved scan-origin candidate summaries
- scoring pre-score 使用 preserved scan-origin 和 graph bridge boost
- audit/review 开始消费 graph bridge hypotheses
- `review -> re-audit -> review` 自动闭环
- conditional finding 结果通路（`vuln_confirmed_conditional`）

这是让系统逐步同时具备：

- vulngear 风格的 recall
- agentic-audit 风格的 reasoning / audit / review

相关文档：

- `improve.md`
- `question.md`

---

## 内置脚本

当前 repo 内的便捷脚本：

- `scripts/run_werkzeug_full_aggressive.sh`
- `scripts/run_tomcat_full_high_coverage.sh`

这些脚本只是 operator convenience，不是架构真相来源。

---

## 可观测性

现在长跑日志会显式打印：

- planner 总轮数和每轮 progress
- surface total round plans 和 round-kind counts
- triage total seed batches 和 cumulative seeds
- scoring issue totals、batch 计划和 rerank 输入规模

为什么重要：

- 当前 runtime 已经足够多轮、多阶段，如果日志太稀疏，就只能靠猜

此外建议结合这些文件一起看：

- `BUGS.md`
- `IMPLEMENT.md`

---

## 当前限制

- planner 在预算过大时仍然可能过重，而且 SDK mode 下模型可能少用工具
- surface 在大仓库上会膨胀成很多 scope-local rounds
- `max_surface_rounds` 仍不是 planner-enabled 场景下的真实 total hard cap
- triage 在大仓库上可能生成海量 seed passes / issue seeds
- batched scoring 默认仍然关闭
- smart slicing 仍不建议作为默认主线路径
- review 仍然非常保守
- scan-origin candidate preservation 已经打通到 planning / triage / scoring，但还没有在所有 review 语义里成为强一等信号
- `EvidenceAssertion` 三层证据模型已经开始落结构，但尚未彻底成为统一证据语言
- `RevisionRequest` 已开始结构化，但还没有完全按 typed fields 驱动所有 re-audit planning/execution
- review-phase verifier 已有完整测试矩阵和 bundle 产物，但离真正强执行器还有差距
- 超大仓库（例如 Linux）在全图 CodeQL / Joern 路径下仍然容易出现内存和耗时问题；`provider_graph_mode = auto/full_graph/sharded_graph/hybrid_graph` 已进入主线，但策略还在继续调优

---

## 使用建议

对于大仓库：

- planner rounds 不要给太高，除非你就是在研究 planner 行为
- 不要只看 `max_surface_rounds` 这个数字，要看 total executed surface plans
- 优先使用 stage-aware model routing，不要只给一个全局 model
- batched scoring 只有在 candidate 数巨大时才建议开启
- 如果某类漏洞在 final report 里没出现，先看它有没有在 `surface` 被抬成主 family

如果想知道最近系统到底改了什么：

- 看 `IMPLEMENT.md`

如果想知道已知问题、根因和修法：

- 看 `BUGS.md`

---

## 运行模式与参数影响：完整技术参考

本节是 vulnmine 所有运行模式、参数维度和配置选项的精确参考手册。数据来源：`cli.py`、`contracts.py`、`planner_scope_support.py`、`review_workflow.py`、`issue_scoring_workflow.py`、`prompts.py`、以及三套推荐配置文件。

### 模式体系总览

vulnmine 的运行行为由以下 **7 个独立维度** 共同决定：

| 维度 | 选项 | 默认值 | 来源 |
|------|------|--------|------|
| CLI 行为模式 | `--fast` / `--small` / 无 | 无（full budget） | CLI flag |
| 配置规模 | `recommended-small` / `medium` / `large` | CLI 默认值 | `--config` JSON 文件 |
| 执行模式 | `prompt` / `sdk` | `prompt` | `--planning-mode` / `--surface-mode` / `--audit-mode` |
| 分析 Profile | `classic` / `bridge` / `logic` / `full-research` | `classic` | `--analysis-profile` |
| Scoring 模式 | `single` / `mixed_batch` / `family_bucket` | `single` | `--scoring-mode` |
| Review 模式 | `standard` / `rebuttal` / `adversarial` | `standard` | `--review-mode` |
| Provider Graph 模式 | `auto` / `full_graph` / `sharded_graph` / `hybrid_graph` | `auto` | `--provider-graph-mode` |

参数叠加顺序：**RunSpec 默认值 → config JSON 覆盖 → CLI 显式参数 → `--fast`/`--small` 强制 cap**。

这部分专门解释两类"模式"：

1. CLI 行为模式：`--fast`、`--small`
2. 配置规模模式：`recommended-small.json`、`recommended-medium.json`、`recommended-large.json`

它们会叠加生效：

- 先加载配置文件（small/medium/large）
- 再应用 CLI 覆盖
- 最后应用 `--fast` / `--small` 的强制上限（如果命中）

### A. `--fast` 与 `--small` 硬覆盖对照

这两个 flag 的实现在 `cli.py:816-868`，本质是对 RunSpec 字段做 `min(current, cap)` 的单向压缩。

| 参数 | CLI 默认 | `--fast` cap | `--small` cap | 说明 |
|------|---------|-------------|--------------|------|
| `prompt_budget_tokens` | 64,000 | <= 64,000 | <= 64,000 | 适配 120K 上下文窗口 |
| `llm_max_tokens` | 32,000 | <= 32,000 | <= 32,000 | 响应 token 上限 |
| `max_surface_rounds` | 6 | <= 3 | <= 4 | surface 每 scope 局部轮数 |
| `max_rounds_per_issue` | 8 | <= 4 | <= 5 | audit 每 issue 调查轮数 |
| `max_planning_rounds` | 2 | <= 1 | <= 1 | planning refinement 轮数 |
| `review_verifier_rounds` | 2 | <= 1 | <= 1 | review verifier 子轮数 |
| `max_revision_cycles` | 1 | **= 0** | <= 1 | review->re-audit 闭环次数 |
| `max_candidate_issues` | 1,200 | <= 800 | <= 1,000 | 最大候选 issue 数 |
| `scoring_pre_score_keep` | 400 | <= 200 | <= 300 | pre-score 后保留数 |
| `scoring_rerank_input_cap` | 100 | <= 60 | <= 80 | rerank 输入上限 |

**关键差异**：
- `--fast` 完全禁用 revision 闭环（`max_revision_cycles=0`），`--small` 保留 1 次
- `--fast` 的 surface/audit 轮次比 `--small` 各少 1 轮
- 如果同时设置 `--fast` 和 `--small`，fast 先执行 cap，small 后执行；由于 fast 已经把值压得更低，small 的宽松 cap 不会生效，**实际效果等于纯 `--fast`**

**对 Prompt 模板的影响**（`prompts.py`）：

| 维度 | `fast_mode=True` | `small_mode=True` | 默认（两者均 False） |
|------|-----------------|-------------------|---------------------|
| Surface prompt | 极简任务指令，无语言清单 | 中等指令 + C/C++ 表面猎杀清单 | 完整多语言清单（Go/Java/Python/Vue/Node.js） |
| Triage 语言清单 | 全部跳过 | 全部保留（含 C/C++ 15 类检查） | 全部保留 |
| Response schema | 单行压缩 JSON | 中等：字段列表，无 few-shot | 完整 schema + 2 个 few-shot 示例 |
| Audit verdict | 5 行压缩定义 | 完整多段定义 + C/C++ 检查 | 同 small_mode |
| Review policy | 4 句压缩 | 完整多段 + review mode 修饰 | 同 small_mode |
| FP 抑制规则 | 压缩版 | 压缩版（无 few-shot） | 完整版 + 案例 |

**实际影响总结**：

| 方面 | `--fast` | `--small` | 默认 |
|------|---------|----------|------|
| 漏洞发现量 | 最低 -- 链路证明成功率下降 | 中等 -- 保留核心探索 | 最高 |
| 误报率 | 较高 -- revision 禁用，review 单轮 | 中等 | 最低 -- 多轮复审 |
| 运行时长 | 最短（通常 40-60% 于默认） | 中等（通常 60-80%） | 基准 |
| Token 消耗 | 最低 | 中等 | 最高 |
| 适用场景 | CI 冒烟、模型不稳、先跑通 | 120K 模型主跑、成本敏感 | 追求最大召回 |

### B. 配置规模完整对照表（small / medium / large）

三套配置均使用 `logic` 分析 profile，差异集中在探索宽度、候选规模、并发和预算。`0` 值表示使用 adaptive 默认。

#### 核心参数对照

| 参数 | Small (<50K LOC) | Medium (50K-500K) | Large (>500K) | 增长模式 |
|------|----------------:|------------------:|---------------:|---------|
| **候选与限制** | | | | |
| `max_candidate_issues` | 600 | 1,600 | 3,200 | ~2.5x |
| `max_issues` | 0 | 0 | 0 | 全量 |
| **Surface 探索** | | | | |
| `max_surface_rounds` | 6 | 8 | 10 | +2/tier |
| `max_surface_actions_per_round` | 8 | 8 | 10 | +2@large |
| `surface_workers` | 1 | 2 | 4 | 2x |
| **Triage** | | | | |
| `triage_seed_workers` | 6 | 10 | 14 | +4/tier |
| **Scoring** | | | | |
| `scoring_mode` | `single` | `mixed_batch` | `mixed_batch` | medium 起开 batch |
| `scoring_pre_score_keep` | 400 | 800 | 1,600 | 2x |
| `scoring_batch_top_k` | 15 | 25 | 30 | -- |
| `scoring_rerank_input_cap` | 120 | 300 | 600 | ~2x |
| `scoring_family_pre_keep` | 40 | 50 | 60 | -- |
| `scoring_family_top_k` | 15 | 15 | 20 | -- |
| `scoring_family_min_keep` | 5 | 5 | 8 | -- |
| **Audit** | | | | |
| `max_rounds_per_issue` | 8 | 10 | 12 | +2/tier |
| `max_intents_per_round` | 8 | 10 | 12 | +2/tier |
| `audit_workers` | 4 | 8 | 10 | ~2x |
| `evidence_slice_cache_limit` | 64 | 128 | 192 | ~2x |
| **Review** | | | | |
| `review_mode` | `rebuttal` | `rebuttal` | `rebuttal` | 统一 |
| `review_verifier_rounds` | 3 | 3 | 3 | 统一 |
| `review_workers` | 2 | 4 | 6 | ~2x |
| `max_revision_cycles` | 1 | 3 | 2 | medium 最多 |
| **Planning** | | | | |
| `max_planning_rounds` | 2 | 3 | 4 | +1/tier |
| `planner_tool_budget_per_round` | 3 | 5 | 6 | -- |
| **Chain Analysis** | | | | |
| `chain_max_candidates` | 256 | 512 | 1,024 | 2x |
| `chain_max_bridge_expansions` | 32 | 48 | 96 | ~2x |
| `chain_max_files_per_bundle` | 12 | 16 | 24 | -- |
| `chain_max_hypotheses_per_chain` | 8 | 8 | 12 | -- |
| **Graph Bridge** | | | | |
| `graph_bridge_max_nodes` | 150 | 240 | 480 | 2x |
| `graph_bridge_max_edges` | 400 | 600 | 1,200 | 2x |
| `graph_bridge_max_hypotheses` | 12 | 16 | 32 | 2x |
| **Semantic Atlas** | | | | |
| `semantic_atlas_max_files` | 96 | 192 | 384 | 2x |
| `semantic_atlas_max_functions` | 256 | 512 | 1,024 | 2x |
| `semantic_atlas_max_flow_summaries` | 80 | 128 | 256 | 2x |
| **Token 预算** | | | | |
| `prompt_budget_tokens` | 64,000 | 96,000 | 206,000 | ~2-3x |
| `llm_max_tokens` | 32,000 | 64,000 | 64,000 | 2x@medium |
| **Repo Index** | | | | |
| `repo_index_max_summary_paths` | 64 | 120 | 200 | ~2x |

#### 三套配置统一不变的参数

- `analysis_profile = "logic"`
- `enable_chain_first_analysis = true`、`enable_semantic_atlas = true`、`enable_invariant_facts = true`
- `enable_pre_graph_hints = true`、`enable_graph_bridge_completion = true`、`enable_repo_planning = true`
- `enable_smart_slicing = false`、`enable_post_export_dynamic_checking = false`
- `provider_graph_mode = "auto"`、`scoring_enable_batching = true`、`scoring_batch_size = 40`
- `planning_mode = "prompt"` / `surface_mode = "prompt"` / `audit_mode = "prompt"`
- `start_step = "initialize"` / `end_step = "export"`

#### 实际影响分析

| 方面 | Small | Medium | Large |
|------|-------|--------|-------|
| 典型运行时长 | 30min-2h | 2h-8h | 6h-24h+ |
| LLM 调用总量 | 百次级 | 千次级 | 数千次 |
| 模型服务压力 | 低，单机友好 | 中等，偶发 503 | 高，建议高吞吐端点 |
| 候选规模膨胀风险 | 低 | 中等 | 高（scoring 成瓶颈） |
| 深度链路漏洞召回 | 中等 | 较好 | 最好 |
| revision 闭环次数 | 1（刚好能纠正一次） | 3（最多复审修正） | 2 |

**关于 medium 的 `max_revision_cycles=3`**：这是有意的设计——中型仓库通常生成中等数量的高质量候选，多次 revision 的性价比最高。large 仓库候选太多，3 次 revision 会导致极长的 tail latency，因此降为 2。

### C. 执行模式：Prompt vs SDK

| 维度 | `prompt` 模式 | `sdk` 模式 |
|------|-------------|-----------|
| 本质 | 单次大 prompt -> JSON 解析 | 多轮对话 + 工具调用 |
| Planning | 每轮一次 LLM 调用 | 每轮最多 `planning_sdk_turns_per_round=2` subturn |
| Surface | scope 级单次调用 + recon action | 每 scope 最多 `4` turns / `8` tool calls |
| Audit | planner->executor->synthesizer->auditor | 每 issue 最多 `6` turns / `12` tool calls |
| 产物 | `llm_call_*.json` | `sdk_chat_history.md` + `sdk_turn_*` + `llm_call_*` |

### D. 分析 Profile 详解

| Profile | `chain_first` | `chain_convergence` | `semantic_atlas` | `invariant_facts` |
|---------|:-------------:|:-------------------:|:----------------:|:-----------------:|
| `classic` | false | false | false | false |
| `bridge` | true | false | true | false |
| `logic` | true | false | true | true |
| `full-research` | true | false | true | true |

### E. Scoring 模式详解

Pre-score 主公式：`pre_score = signal + evidence + bridge_evidence + risk_bonus + path_penalty`

关键打分项：

- `sink` +3.0，`pds` +2.0，`entry` +1.0，`connector` +1.0，`module` +0.5
- bridge 负项：`bridge_unproven` -2.0，`cross_module_link_unproven` -1.5，`runtime_precondition_unproven` -1.0
- 风险项：critical/high/medium/low = +4.0/+3.0/+1.5/+0.0
- 路径惩罚：test/example = -3.0/-2.5
- scan-origin boost 与 graph-bridge boost 会额外加分

| 模式 | 行为 |
|------|------|
| `single` | 直接全局排序 |
| `mixed_batch` | 固定大小 batch 排序后再全局 rerank |
| `family_bucket` | 按漏洞 family 分桶后再全局 rerank |

### F. Review 模式详解

| 维度 | `standard` | `rebuttal` | `adversarial` |
|------|-----------|-----------|--------------|
| stance | `standard` | 轮换 `affirm/rebut/judge` | 每模型 3 轮：affirm->rebut->judge |
| 调用成本 | 1x | 1x | 3x |
| 保守性 | 基准 | 更保守 | 最保守 |

共享安全检查链：

1. grounded evidence 检查（accept 但无 grounded 证据会降级）
2. prototype pollution gate（rebuttal 模式）
3. audit safety evaluator
4. by-design policy evaluator（Node.js）

### G. Scope 限制常量（planner）

| 常量 | 值 |
|------|---:|
| `MAX_SCOPE_DEPTH` | 3 |
| `LEAF_FOCUS_FILE_LIMIT` | 20 |
| `LEAF_CODE_FILE_LIMIT` | 80 |
| `LEAF_FOCUS_LOC_LIMIT` | 2500 |
| `LEAF_MEMBER_LOC_LIMIT` | 8000 |
| `LEAF_SURFACE_BUDGET_LIMIT` | 80 |
| `LEAF_DIRECT_CONNECTOR_LIMIT` | 6 |
| `PROMOTED_CONNECTOR_LIMIT` | 8 |
| `HARDENING_CONNECTOR_LIMIT` | 4 |

当 `depth < 3` 且超过任一预算阈值时会分裂；`depth >= 3` 强制叶节点。

### H. Provider Graph 模式

| 模式 | 行为 |
|------|------|
| `auto` | 自动选择 |
| `full_graph` | 完整图 |
| `sharded_graph` | 分片图 |
| `hybrid_graph` | 混合策略 |

### I. 模式组合建议

- 小项目：`--config config/recommended-small.json`
- 中项目：`--config config/recommended-medium.json`
- 大项目：`--config config/recommended-large.json`
- 120K 窗口模型：叠加 `--small`
- 冒烟/保活：叠加 `--fast`
- ARM64 无 CodeQL：叠加 `--disable-codeql`

### J. 常见误解澄清

1. CLI 没有 `--large` / `--medium`，规模是通过 `--config` 实现。
2. `--small` 是 120K 窗口优化，不是“小项目配置”。
3. `max_surface_rounds` 不是 planner 场景下的总 LLM 调用上限。
4. `--fast` 与 `--small` 同时开启时，实际效果基本由更严格的 `--fast` 主导。

## 运行示例

### 完整运行

```bash
python -m vulnmine.app.cli \
  --repo /path/to/repo \
  --workspace /path/to/workspace \
  --run-id my-analysis-001 \
  --enable-repo-planning \
  --planning-mode prompt \
  --surface-mode prompt \
  --max-planning-rounds 4 \
  --planner-model "glm-5"
```

### 只运行Planning阶段

```bash
python -m vulnmine.app.cli \
  --repo "/path/to/repo" \
  --workspace "/path/to/workspace" \
  --run-id "planning-only" \
  --enable-repo-planning \
  --planning-mode prompt \
  --surface-mode prompt \
  --max-planning-rounds 4 \
  --planner-tool-budget-per-round 10 \
  --planner-model "glm-5" \
  --start-step planning \
  --end-step planning
```

### 查看测试

```bash
# 运行planner相关测试
pytest tests/test_agent_prompts.py tests/test_planner_support.py

# 运行完整测试
pytest tests/
```

---

## 目录结构

```
.
├── config/                    # 配置文件
│   ├── runtime.json          # 运行时配置
│   └── llm.json              # LLM配置
├── docs/                      # 文档
│   ├── 02-architecture/      # 架构文档
│   └── AGENT_HANDOFF_*.md    # Agent Handoff文档
├── src/vulnmine/             # 源码
│   ├── agents/               # Agent层
│   ├── app/                  # 应用层（CLI）
│   ├── baseapi/              # 底层API
│   ├── core/                 # 核心数据模型
│   ├── workflow/             # Workflow编排
│   └── web/                  # Web UI（实验性）
├── tests/                    # 测试
├── scripts/                  # 便捷脚本
├── target/                   # 目标仓库（测试用）
├── runs/                     # 运行结果（gitignored）
└── reports/                  # 报告输出
```
