PATHFINDER-2026-10-13/: feature inventory, ten per-unit flowcharts with file:line evidence, cross-feature and within-unit duplication reports, a proposal that keeps every package on one of the four active lines, and copy-ready plan prompts in order. .kiro/breadth.toml: every spec and capability with a status and the deletion that closes it. No code changed. Co-Authored-By: Claude Fable 5.1 <[email protected]>
12 KiB
Unified proposal (2026-10-13)
Written by the orchestrator from 00 to 02. One rule throughout: prefer deletion over abstraction, one path over
configurable paths, and no new layer "for flexibility". Everything here serves the four active lines in
.kiro/steering/overview.md: hunter-learning-loop, pre-push-safety-net, search-with-proof,
engine-change-scoped-analysis. What serves none of them is cut.
What the map says in three sentences
The code is not rotten (14 clone findings, 13 unreferenced lines), it is wide: each spec added a package and nothing retired one. The same five jobs are done in several places: calling a model and counting its cost (seven client types, four cost paths), finding the enclosing function and its callers (two naming systems, two caller indexes, one grep), running the quick rules (nine copies), running code in a sandbox for a verdict (two runtimes, two verdict vocabularies, neither reaching the evidence ladder), and writing scan results (findings written six times, five ledgers under one directory). And three subsystems serve no active line: the improve lever, the deep-mode regress arm, and the HarnessX-era half of the plane executor.
Systems to unify (S1 to S6), in the order they pay off
S1. One source-facts module (D4 + D5): the hunter's annotation producer
Today: semantic/functions.py (tree-sitter + regex fallback, sentinel <module>), plane/tasks/repo_facts.py
(regex only, sentinel <global>, DECLARATION/KEYWORDS/product_files), learn/features.callers (own regex),
plane/tasks/verify.callers_of, learn/excerpt.function_bounds, hunter_tools.find_refs (grep), plus alias maps in
three places. This is the exact set of facts hunter-learning-loop R3 names (enclosing function, callers, entry
points, roles, def-use), so it is the first thing to own.
Unified: src/openultrasast/source.py with ranges(path, text, lang), enclosing(ranges, line),
bounds(ranges, name), call_sites(text, names), product_files(root), DECLARATION, KEYWORDS,
LANGUAGE_ALIASES (beside preprocess.LANGUAGE_BY_EXTENSION), one sentinel, one reserved set. Entry point:
source.facts_for(root, path, line) -> SourceFacts(function, span, callers, entry_points); entry points keep
coming from mapping.analyze_entry_points, called once per run and passed in.
Old call sites: semantic/functions.py becomes the implementation moved here (keep function_at as an alias
for one release); plane/tasks/repo_facts.py keeps main() and imports the tables from source.py; learn/labels.py:48,
learn/features.py:27, 365-395, learn/roles.py:35, learn/excerpt.py:35, 302, plane/tasks/alerts.py:108-114,
plane/tasks/verify.py:128, plane/harvest.py:46, hunter_tools.py:83, 317-323 → source.py.
Lost: the per-language DECLARATION patterns for Java/Go/Ruby/PHP must be merged into _DECL; re-label the pair
corpus and diff labels-<sha>.jsonl before switching. The Learn↔PlaneTasks cycle disappears.
S2. One chat client, one meter, one resolver (D1 + D10 parsers and retry policy)
Unified: model/endpoint.py keeps the single client (today's DeepSeekChatClient, renamed ChatAdapter; usage
is a list of rows) and the single resolver resolve_chat_client(config | None) -> (client, endpoint) that
honours [models] and prices by the model that runs. plane/budget.py keeps one Meter with
reserve(bound) and settle(row); MeteredClient (post-call) and the reserve-first mode replace FillMeter,
SpendBudget/SpendReservation and search/worker _call. One cost(row, prices). Caller (learn/program.py)
stays as the cache and replay layer on top of the meter. parse_json_content (provider/openrouter.py:205) is
the one JSON-in-fences parser; retry policy lives on the client only.
Old call sites: tool_hunter.py:239-262 resolve_hunter_client → resolve_chat_client; OpenRouterHunterClient
(203) and the env-flag set (36-39) deleted; _load_json/_looks_like_json (581, 409) → parse_json_content;
cli.py:976 passes a metered client; push/assistance.py:103-110 uses cost; search/worker.py:218-270 uses the
client and the reserve-first meter; learn/fill.py:44 FillMeter deleted; plane/tasks/verify.py:66 _Recording
and model/scan.py:1628 _CountingClient read counts off the meter; benchmarks/valid/hunter_arm.py:56-65 shim
deleted.
Lost: unmetered OpenRouter calls; recorded hunter dollars change because the hunter prices by its real model.
S3. One quick-rule pipeline (D3)
Unified: findings.quick_pipeline(root, targets=None, *, ruleset, policy, floors=None, include_shadow=False) -> QuickRun(targets, entry_points, rankings, findings). cli.py wraps each step in run_stage through a
callback argument. Old call sites: cli.py:641-687 (and the standard-mode pool at hunter.py:103-160, which keeps
its thread pool but calls the pipeline), gate.py:81-84, map_gate.py:52-54, pairs.py:794-815, plane/tasks/alerts.py:
120-122, improve/evolve.py:311-315 (deleted with S9), push/quick.py:87-92 (passes its changed-file targets),
hunter_tools.py:232 (deleted with S8), benchmarks/learn/build_features.py. Fixes the standard-mode floors bug
(cli.py:670 vs 683-684). Lost: nothing; gates pass ruleset=PATTERN_RULES to keep their numbers.
S4. One sandbox verdict on the evidence ladder (D2)
Unified: search/_sandbox.run implements sandbox.SandboxRunner (its options move into SandboxJob);
resolve_sandbox_runner picks bwrap or docker; one outcome record ExecutionOutcome(outcome ∈ confirmed | refuted | inconclusive | unavailable, reason, runs) that emits Rung.EXECUTION_CONFIRMED (model/ladder.py:33)
so push_scoring.py:84, mcp.py:98 and learn/schema.py:77 finally receive execution evidence. Two oracle
strategies stay (owned canary, exit code). regress/ is deleted (S9): the deep-mode REGRESS stage calls the
proof arm's verify path once that lands, and until then deep mode equals standard mode and says so in a
degradation. Lost: snippet-based regress in scan --mode deep until search-with-proof is wired to scan. This
is the one capability loss in the proposal and it is deliberate: the maintainer named proof as the block
gate and the search line owns it.
S5. Deadline type and the CPG↔Model cycle (D6 + W3.5)
Unified: ExecutionBudget moves to contracts.py with expired(), remaining(), cleanup_end, check();
DeadlineExpired and remove_owned_tree become public in a deadline.py next to it. Old call sites: the 66
budget expressions (push 47, model 13, cpg 16), cpg/backend.py:37, cpg/session.py:44, model/partitions.py:14,
cpg/artifact.py:151, push/cache.py:22. Also W4.3: one _run in cpg/backend.py so standard scans get partial
stdout; W4.1: one ARBITERS table used by scan.py and pipeline.py (and the hook_callbacks divergence closes).
Lost: nothing.
S6. Scan assembly: write once, score last, one ledger (D9 + U1 bugs + W1.4 + W1.5)
Unified: _run_scan accumulates findings per proposer in memory and writes findings.json once in a
finally; scoring and verification move after MAP so model, overlay, obligation and hunter findings are scored
and verified (the U1 bug); CalibrationLedger(target) owns the five files under .openultrasast/calibration/;
runtime.degrade(stage, reason, detail) replaces 13 inline appends; run.write_json replaces 24 write_*
wrappers; benchmark.run_benchmark is shared by cli and mcp (W1.3). fusion.py is deleted: its decisions never
reach findings or score (U1), and harness.py loses the processor machinery nobody builds. Lost: a partial
findings.json on a crash, unless the finally write is kept (it is).
S7. Memory store as a leaf; plane executor trimmed (U7 inversion + U8 + W8)
plane/memory.py loses its three lazy imports: seed moves to plane/seed.py, results_root to
plane/paths.py. The executor keeps reconciler, runner, delivery, router, egress, doctor, profile and loses
engine_alerts.py, harvest.py, workspaces.py, generate.py and receiver mode (their live helpers declares,
docker_command, frozen_source, read_record, export_pin move into benchmarks/learn, their only users).
One Cli wrapper and error_tail for ax and kubectl (reconciler, egress, benchmarks/ax/batch); one
safe_extract and one ObjectURL client shared by plane/runner, delivery, search/executor and verify_task
(plane extraction gains the size cap and the no-redirect rule). The two dispatch stacks stay two for now: the
search stack is on the active proof line and the plane stack is parked with plane-on-kubernetes; merging them
is a decision for when the ax lane is back.
Cuts (S8, S9): what leaves the tree
S8, hunter unit: the four never-enabled tools and their schemas and dispatch branches (hunter_tools.py:174-290,
tool_hunter.py:94-137, 505-521), the dead find_refs mapping branch (hunter_tools.py:86-101, 118-164),
OpenRouterHunterClient, _load_json, _looks_like_json, ScriptedChatClient stays. The facts those tools
computed come back as annotations from S1, which is what the hunter-learning-loop spec asks for.
S9, off every active line: improve/ (1,175 lines; ousast improve, the loop-improve task at
plane/generate.py:380 and plane/tasks/loop.py, plane/memory.py:1002's snapshot guard, docs/memory.md and
README sections); regress/ (549) with the REGRESS branch cli.py:839-880; skills.py + index.py (446; the routed
ids are trajectory metadata only, hunter.py:137-139, 96, 157); model/audit.py (255) to a maintainer script or
gone; test-only functions in mapping.py (188-237), calibration.py:108, findings.py:102, verification.py:39,
rank.py:46-69, model/report.py:24-75, taxonomy.hierarchical/related, config_value.verdict, taint.verdict,
pipeline.arbitrate's dead branches, learn/roles.load_roles, learn/features.language_of and build_for_scan,
folds.leave_one*, plane/tasks/features.recorded_case, coordinator.BatchExecutor, memory.purge/delete_version,
delivery.put_input, worker.main/verify.main/coordinator.main shims, cpg/queries/vocabulary.sc. Each goes with
its test file. push_inputs.py moves under benchmarks/push. mcp.py stays until the mcp-shift-left-agent spec is
decided (breadth.toml: parked, close date 2026-12-01). map_gate.py and pair_gate.py stay with CI for now; their
removal needs cli.py:49, ci.yml, dagger/ci.py and the gate baseline changed together.
Estimated effect (lines, rough): duplication removal 650 to 800 in src; cuts about 4,500 in src (improve 1,175, regress 549, plane dead modules about 1,300, skills+index 446, audit 255, hunter tools and client about 450, test-only functions about 350) and the tests that only exercised them, about 9,000. src goes from 60k toward 55k and tests from 54k toward 45k, with every remaining package on an active line or in breadth.toml as parked with a close date.
Order of work
- S9 cuts and S8 (cheap, no design, unblocks everything else; one commit per package with its tests).
- S1 source facts (the annotation producer hunter-learning-loop needs first).
- S2 client and meter (the ablation runs need one metered client with replay).
- S3 quick pipeline, S5 deadline type (small, mechanical).
- S6 scan assembly (fixes the scoring-before-MAP bug; touches cli.py most).
- S4 sandbox verdict (with search-with-proof's next increment).
- S7 plane trim (when plane-on-kubernetes resumes or is closed).
The proposed system
flowchart TD
subgraph inputs
SCAN[ousast scan<br/>cli.py:619] --> QP
PUSH[pre-push runner<br/>push/runner.py:609] --> QP
GATE[gate.py / pairs / alerts] --> QP
end
QP[quick_pipeline<br/>findings.py (S3)] --> FACTS
QP --> ENGINE
FACTS[source.facts_for<br/>source.py (S1)] --> ANN[annotation model<br/>hunter-learning-loop R3]
ENGINE[scan_repository<br/>model/scan.py:143] --> RUNG
ANN --> HUNT[run_tool_hunter<br/>tool_hunter.py:323]
HUNT --> CLIENT[ChatAdapter + Meter + Caller<br/>model/endpoint.py, plane/budget.py, learn/program.py (S2)]
CLIENT --> CLS[classify<br/>learn/program.py:473]
CLS --> RUNG[evidence ladder Rung<br/>model/ladder.py:29]
RUNG --> ADMIT[admit_candidates<br/>push/policy.py:889]
RUNG --> PROOF[search verify<br/>search/verify.py:648]
PROOF --> EXEC[ExecutionOutcome -> EXECUTION_CONFIRMED<br/>sandbox (S4)]
EXEC --> ADMIT
ADMIT --> REPORT[deliver_report<br/>push/report.py:423]
ENGINE --> ASSEMBLE[write once, score last<br/>cli.py (S6)]
HUNT --> ASSEMBLE
ASSEMBLE --> LEDGER[CalibrationLedger<br/>(S6)]
CLS --> STORE[(MemoryStore leaf<br/>plane/memory.py (S7))]
LEARN[learn compile / evaluate / experiment<br/>learn/cli.py] --> CLIENT
LEARN --> STORE
FACTS --> LEARN
BUDGET[ExecutionBudget + DeadlineExpired<br/>contracts.py (S5)] -.-> ENGINE
BUDGET -.-> PUSH