I've been using OpenAnt (primarily on Windows) and have accumulated some changes in my fork that I'd like to contribute back. I've already opened PR #15 (test suite + CI). Before submitting the rest as individual PRs, I wanted to check which changes you'd be interested in.
I have working implementations for all of these — happy to open PRs for whichever ones you'd like. Check the ones you're interested in, or let me know if you have questions about any of them.
These exist in the current codebase.
These prevent OpenAnt from working correctly on Windows.
New capabilities addressing the cost and time implications of long-running scans failing mid-way.
Detailed implementation notes, dependencies, and cherry-pick risks
Detailed Implementation Notes
Change 1: Test suite + CI (fork PRs #1 + #4, already upstream PR #15)
- Dependencies: None — this is the foundation for all other changes
- Cherry-pick difficulty: Already submitted
Change 2: Ruff lint in CI (fork PR #8, partial)
- Files:
.github/workflows/test.yaml
- Dependencies: Change 1 (CI workflow to add the step to)
- Cherry-pick difficulty: Clean
- Note: Run
ruff check . against upstream/master before submitting to verify clean baseline
Change 3: Findings count (fork PR #12)
Superseded by upstream PR #23 — no longer needs to be submitted.
Change 4: Parse --level default (fork PR #16)
- Files:
apps/openant-cli/cmd/parse.go
- Dependencies: None
- Cherry-pick difficulty: Clean (one-line change)
Change 5: Analyze summary verdicts (fork PR #18)
Superseded by upstream PR #23 — no longer needs to be submitted.
Change 6: Report paths (fork PR #19)
Superseded by upstream PR #23 — no longer needs to be submitted.
Change 7: DI-aware call resolution for TypeScript/NestJS (fork PR #20)
- Files:
parsers/javascript/typescript_analyzer.js, utilities/agentic_enhancer/agent.py, utilities/agentic_enhancer/prompts.py
- Dependencies: None (parser-level change)
- Cherry-pick difficulty: Clean
Change 8: JS/Go parser Windows paths (fork PR #3)
- Files:
parsers/javascript/typescript_analyzer.js, parsers/javascript/test_pipeline.py, parsers/go/test_pipeline.py
- Dependencies: None
- Cherry-pick difficulty: Clean
Change 9: UTF-8 file I/O (fork PR #13)
- Files: New
utilities/file_io.py, plus migrations across ~20 files
- Dependencies: None (but benefits from test suite for coverage)
- Cherry-pick difficulty: Clean — large diff but mechanical replacement
- Note: Cross-cutting change that touches many files. Best submitted early to avoid conflicts with other PRs.
Change 10: Checkpoint and resume (fork PRs #7, #9, #10, #21)
Largely superseded by upstream PR #23. Upstream added core/checkpoint.py with directory-based per-unit StepCheckpoint for enhance, analyze, and verify. The remaining sub-features not in upstream are now tracked as separate items: change 19 (--fresh for parse) and change 20 (atomic JSON writes).
Original fork implementation (kept as historical reference):
| Stage |
Fork PR |
Scope |
Status |
| 1 |
#7 |
Atomic writes (atomic_write_json) + enhance auto-checkpoint + --fresh flag |
Atomic writes split to change 20; --fresh for parse split to change 19 |
| 2 |
#9 |
Analyze/verify per-unit checkpointing + --fresh for analyze/verify |
Superseded by upstream PR #23 |
| 3 |
#10 |
Scan step-level resume (skip completed steps on re-run) |
Superseded by upstream PR #23 |
| — |
#21 |
--fresh flag for parse (forces full reparse, clears dataset in scan) |
Now change 19 |
Change 11: Auto-retry errored units (fork PR #15)
Superseded by upstream PR #23 — no longer needs to be submitted. Upstream's StepCheckpoint.load_ids(skip_errors=True) provides equivalent behavior.
Change 12: Parallel LLM calls (fork PR #17)
Superseded by upstream PR #23 — no longer needs to be submitted. Upstream uses --workers/--backoff with ThreadPoolExecutor and GlobalRateLimiter. The fork's parallel_executor.py has been deleted as dead code.
Change 13: Auto-detect language (fork PR #6)
- Dependencies: None
- Cherry-pick difficulty: Clean
Change 14: Auto-detect dependency changes (fork PR #23)
- Dependencies: None (Go CLI only)
- Cherry-pick difficulty: Clean
Change 15: Centralize model IDs (fork PR #24)
- Dependencies: None
- Cherry-pick difficulty: Clean
- Note: Upstream may want different default model IDs but the centralization pattern is valuable
Change 16: Claude Agent SDK migration (fork PR #25)
Status: complete on fork as of 2026-04-19 via fork PRs #30, #31, #32, #33, #34, #36, #37, #38. Not yet submitted upstream.
Timeline:
| Date |
Event |
| 2026-03-23 |
Fork PR #25 completed the migration — zero anthropic refs in the four call sites. |
| 2026-04-14 |
Upstream PR #23 expanded anthropic usage on its own copy of the four files, adding typed rate-limit handling for the new parallel execution path. |
| 2026-04-16 |
Fork PR #29 merged upstream/master into fork and silently absorbed the regression — anthropic was back in all four files (8 / 5 / 5 / 3 refs). |
| 2026-04-19 |
Fork PR #30 re-declared anthropic and added a regression-guard test; PRs #31 through #38 completed the re-migration and dropped the dep. |
This is the largest change. It subsumes all earlier local Claude attempts (fork PRs #2, #5, #11, #14) which should NOT be submitted individually.
Key benefits:
Fork PRs that re-implement change 16 against current upstream (cherry-pick targets, in dependency order):
| Fork PR |
Scope |
| #30 |
Regression guard (tests/test_declared_dependencies.py) that fails CI if any imported distribution isn't in pyproject.toml. Cherry-pick this even if nothing else lands — it prevents the same silent drift repeating on any future upstream merge. |
| #31 |
New utilities/sdk_errors.py taxonomy — one exception class per AssistantMessageError literal value. |
| #33 |
Wires AssistantMessage.error detection into llm_client._run_query. Centralises rate-limit reporting to GlobalRateLimiter. |
| #32 |
Ports report/generator.py (single-turn, smallest site). |
| #34 |
Ports context_enhancer._build_error_info's isinstance chain to sdk_errors.*. |
| #37 |
Ports finding_verifier.py — manual tool-dispatch loop replaced with run_native_verification (SDK native tools). |
| #36 |
Ports agentic_enhancer/agent.py; absorbed shared_client removal in context_enhancer.py. |
| #38 |
Drops anthropic>=0.40.0 from pyproject.toml; cleans up one straggler in openant/cli.py's remediation-guidance LLM call that the earlier ports didn't touch. |
Technical note on SDK rate-limit surfacing: the Claude Agent SDK exposes API errors via AssistantMessage.error: AssistantMessageError | None, a Literal including "rate_limit", "authentication_failed", "billing_error", "invalid_request", "server_error", "unknown" (see claude_agent_sdk/types.py:767). No stderr parsing required — earlier notes suggesting otherwise were wrong. retry-after and request-id headers are not surfaced; the GlobalRateLimiter's default 30s backoff replaces the former in practice.
Change 17: generate-context CLI command with auto-discovery (fork PR #26)
- Files: New
apps/openant-cli/cmd/generatecontext.go, modified apps/openant-cli/cmd/root.go, apps/openant-cli/cmd/analyze.go, apps/openant-cli/cmd/verify.go, libs/openant-core/openant/cli.py, libs/openant-core/tests/test_go_cli.py
- Documentation: Also includes doc updates (fork PR #28) to
PIPELINE_MANUAL.md, CURRENT_IMPLEMENTATION.md, README.md, and DOCUMENTATION.md — these should be included when cherry-picking this change.
- Dependencies: None — standalone addition reusing existing
context.application_context module
- Cherry-pick difficulty: Clean
- Testing status: Automated tests cover help output and API key validation. Manual testing with an API key is still needed for: generate-context with active project, auto-discovery in analyze/verify,
--force/--show-prompt/--json flags, and explicit --app-context precedence over auto-discovery.
Change 18: Override merge mode for generate-context (fork PR #27)
- Files: Modified
apps/openant-cli/cmd/generatecontext.go, libs/openant-core/context/application_context.py, libs/openant-core/openant/cli.py, libs/openant-core/tests/test_go_cli.py, plus docs (CLAUDE.md, CURRENT_IMPLEMENTATION.md, PIPELINE_MANUAL.md, context/OPENANT_TEMPLATE.md)
- Dependencies: Change 17 (
generate-context command must exist)
- Cherry-pick difficulty: Clean — builds on top of change 17's
generatecontext.go and application_context.py
- Implementation: Go CLI adds interactive prompt (following
uninstall.go pattern) and --override-mode flag. Python core adds find_override_file() helper, override_mode parameter to generate_application_context(), and merge prompt supplement fed to the LLM. Terminal detection via os.Stdin.Stat() skips the prompt in non-interactive/CI environments (defaults to use).
- Testing status: Automated tests cover
--override-mode in help output and --force/--override-mode mutual exclusion. Manual testing needed for: interactive prompt with a repo containing OPENANT.md, merge mode LLM output (verify with --show-prompt), and non-interactive default behavior.
Change 19: --fresh flag for parse (fork PR #21)
- Files:
apps/openant-cli/cmd/parse.go, libs/openant-core/openant/cli.py, libs/openant-core/core/scanner.py
- Dependencies: None
- Cherry-pick difficulty: Clean
Change 20: Atomic JSON writes (fork PR #7, partial)
- Files:
core/utils.py (new), core/analyzer.py, core/enhancer.py, core/verifier.py
- Dependencies: None
- Cherry-pick difficulty: Clean
Change 21: JS parser lazy npm bootstrap (fork PR #39)
- Files:
libs/openant-core/core/parser_adapter.py (new _ensure_js_parser_dependencies() helper called from _parse_javascript), new libs/openant-core/tests/test_js_parser_bootstrap.py (5 unit tests covering: node_modules present → skip, missing → run install, npm missing → clear error, install failure → surfaced, bootstrap failure aborts before running Node).
- Dependencies: None — self-contained addition. Does not require change 9 (UTF-8 I/O) since the helper uses only
subprocess.run and shutil.which.
- Cherry-pick difficulty: Clean — ~40 lines of production code plus a tests file.
- Design note: Lazy (on first JS parse) rather than eager (at CLI startup) so users who only scan Python/Go repos never need Node or npm installed. Mirrors the Go CLI's existing venv bootstrap pattern in
apps/openant-cli/internal/python/runtime.go (check → install → cache), just scoped to the JS parser subdir instead of the user's home dir. The "installing deps (first run, this may take a minute)..." message is printed to stderr so it's visible but doesn't pollute machine-readable output.
- Alternative considered: Adding
npm install to apps/openant-cli/Makefile's build target or to a new setup target — rejected because it forces npm on users who never parse JS, and because users who skip the README still hit the same opaque Cannot find module 'ts-morph' error. The lazy check in _parse_javascript fails at the exact point the dep is needed, and either installs automatically or prints a clear "run npm install in X" message with the exact directory path.
- Testing status: Automated unit tests cover all four branches (present / missing+install / npm-missing / install-fails) plus the integration surface (
_parse_javascript does not fall through to Node when bootstrap raises). Tests monkeypatch subprocess.run and shutil.which so they run without Node installed. Manually verified against Linux cloudshell repro (the exact Cannot find module 'ts-morph' failure documented in the PR description).
Superseded Changes (Not for Upstream)
These were intermediate steps toward local Claude Code support or features now covered by upstream. They should NOT be submitted upstream individually.
| Fork PR |
Description |
Why superseded |
| #2 |
feat: support local Claude Code session for LLM calls |
Replaced by SDK's native auth support in #25 |
| #5 |
test: add tests for local Claude Code mode |
Tests for LocalClaudeClient which is deleted in #25 |
| #8 |
fix: missing os import in context_enhancer |
Bug introduced by fork's checkpoint code, not present in upstream |
| #11 |
feat: add text-based tool use simulation to LocalClaudeClient |
Replaced by SDK's native tool support in #25 |
| #14 |
fix: pass prompt via stdin to avoid WinError 206 |
Fix for local_claude.py which is deleted in #25 |
| #22 |
fix: pass --fresh flag to chained verify |
Depends on --fresh for analyze/verify which is superseded by upstream's checkpoint system |
Recommended Submission Order
The order below respects dependencies and minimizes merge conflicts:
| # |
Change |
Fork PR(s) |
Type |
Depends on |
| 1 |
1 |
#1, #4 |
Tests + CI |
— (already upstream PR #15) |
| 2 |
2 |
#8 (partial) |
Ruff lint in CI |
1 |
| 3 |
8 |
#3 |
Windows JS/Go parser paths |
— |
| 4 |
9 |
#13 |
UTF-8 file I/O |
1 (for tests) |
| 5 |
4 |
#16 |
Parse --level default |
— |
| 6 |
7 |
#20 |
DI-aware call resolution |
— |
| 7 |
13 |
#6 |
Auto-detect language |
— |
| 8 |
14 |
#23 |
Auto-detect dependency changes |
— |
| 9 |
20 |
#7 (partial) |
Atomic JSON writes |
— |
| 10 |
19 |
#21 |
--fresh flag for parse |
— |
| 11 |
15 |
#24 |
Centralize model IDs |
— |
| 12 |
16 |
#30, #31, #33, #32, #34, #37, #36, #38 (in this order) |
Claude Agent SDK migration — see change 16's detail section |
15 |
| 13 |
17 |
#26 |
generate-context CLI command + auto-discovery |
— |
| 14 |
18 |
#27 |
Override merge mode for generate-context |
17 |
| 15 |
21 |
#39 |
JS parser lazy npm bootstrap |
— |
Cherry-Pick Risks & Notes
Critical: Each PR must be rebased, not cherry-picked directly
Every fork PR was developed incrementally on top of previous fork PRs (including the superseded local Claude ones: #2, #5, #11, #14). Direct git cherry-pick will produce diffs relative to the fork's history, not upstream's. Each change must be rebased or re-authored as a standalone diff against upstream/master (or against the upstream branch that includes previously merged contributions).
Import chain dependencies
Several fork PRs create new modules that later PRs import. Missing a dependency in the chain will cause ImportError at runtime:
| Module |
Created by |
Required by |
core/utils.py (atomic_write_json) |
Change 20 (atomic writes) |
Change 9 |
utilities/file_io.py (open_utf8, etc.) |
Change 9 (UTF-8 I/O) |
Every subsequent PR that touches file I/O |
utilities/model_config.py (MODEL_PRIMARY, etc.) |
Change 15 (model IDs) |
Change 16 (SDK) |
cli.py flag accumulation
The fork added CLI flags across multiple PRs. When rebasing against upstream, each PR needs to add its flags against upstream's version of cli.py, not the fork's. The same applies to the Go CLI cmd/*.go files.
Go CLI cmd files
Upstream has 17 files in apps/openant-cli/cmd/. Upstream may have modified the same cmd files the fork changed. Each Go CLI PR should be diffed against upstream's current version of those files.
llm_client.py divergence
This file diverged between fork and upstream:
- Upstream: Uses
anthropic.Anthropic() directly, model IDs hardcoded as "claude-opus-4-20250514" / "claude-sonnet-4-20250514"
- Change 15 (model IDs): Replaced hardcoded strings with
model_config imports
- Change 16 (SDK): Rewritten. Fork PRs #31 (new
utilities/sdk_errors.py) and #33 (wires SDK error surfacing into _run_query) are the clean cherry-pick targets for this file. Change 15 still needs re-authoring against upstream's version since it predates the SDK work.
pyproject.toml dependency divergence
Upstream has anthropic>=0.40.0. The fork has fully migrated off it (as of 2026-04-19) via the PR sequence listed under change 16. Change 16, when ported upstream, removes anthropic and adds claude-agent-sdk>=0.1.48. Intermediate fork PRs (#30 through #37) keep both deps declared while individual ports land; only the final PR (#38) removes anthropic. If cherry-picking incrementally upstream, follow the same order — removing the dep before all usages are ported breaks the build.
Ruff lint may fail on upstream code
Change 2 adds ruff with F821/F811 rules to CI. If upstream has introduced code that violates these rules, CI will fail. Run ruff check . against upstream/master before submitting to verify clean baseline.
context_enhancer.py and finding_verifier.py evolution
These files were modified by multiple fork PRs:
context_enhancer.py: change 9 (UTF-8 I/O), change 16 — fork PRs #34 (_build_error_info port) and #36 (shared_client removal).
finding_verifier.py: change 9 (UTF-8 I/O), change 16 — fork PR #37 replaces the manual tool loop with run_native_verification.
Each of those change-16 PRs applies cleanly against current upstream. Ordering matters (see the table under change 16); the files compile mid-chain because anthropic stays declared in pyproject.toml until the final PR (#38) removes it.
SDK migration (change 16): cherry-pick order matters
Change 16 is cherry-pickable one fork PR at a time in the order listed under change 16's detail section. The historical PR #25 diff alone is not sufficient — upstream has since added GlobalRateLimiter and parallel execution paths that rely on typed anthropic exceptions. The fork's re-migration PRs (#31, #33, #32, #34, #37, #36, #38) already handle those additions.
Key facts when cherry-picking:
- The SDK surfaces rate limits via
AssistantMessage.error: AssistantMessageError | None (Literal including "rate_limit"). No stderr parsing required.
GlobalRateLimiter.report_rate_limit() is called centrally from llm_client._run_query whenever that field fires. Callers do not need their own except anthropic.RateLimitError blocks.
retry-after is lost in translation — the SDK does not surface the header value. The limiter's default backoff (30s, configurable) replaces that hint in practice.
- Cherry-pick fork PR #30 first. It adds a regression-guard test that catches any future merge silently re-introducing an undeclared import. Cheap insurance; landing it alone provides value even if the rest of change 16 is deferred.
Upstream may have moved
All analysis is based on upstream/master as of 2026-04-19. Upstream PR #23 has been merged, superseding fork changes 3, 5, 6, 10 (stages 2-3), 11, and 12. If upstream merges other PRs before these contributions land, additional conflicts may arise. Re-fetch and recheck before each submission.
I've been using OpenAnt (primarily on Windows) and have accumulated some changes in my fork that I'd like to contribute back. I've already opened PR #15 (test suite + CI). Before submitting the rest as individual PRs, I wanted to check which changes you'd be interested in.
I have working implementations for all of these — happy to open PRs for whichever ones you'd like. Check the ones you're interested in, or let me know if you have questions about any of them.
Testing & CI
F821(undefined name) andF811(redefined unused name). Unlike compiled languages, Python won't report an undefined name until that code path executes at runtime, so a missing import can ship undetected and only crash when a user hits that branch. These two rules catch that statically with zero false positives and no style noise.Bug Fixes
These exist in the current codebase.
3. Findings count shows 0 in build-output —Addressed in upstream PR feat: parallelization, HTML report overhaul, Zig parser, dynamic test hardening #23 —PrintBuildOutputSummaryin the Go CLI expectsfindings_countin the JSON response, but the Python CLI returns only{"pipeline_output_path": path}— so "Findings included: 0" is always displayed regardless of actual results.build_pipeline_outputnow returnsfindings_countandcmd_build_outputincludes it in the JSON response.--level allinstead ofreachable— The Go CLIparsecommand defaults--levelto"all", while bothscanand the Python CLI default to"reachable". This meansopenant parsestandalone produces a different (larger, noisier) dataset thanopenant scanwith no indication to the user.5. Analyze summary missing verdict categories — The Python analyzer tracks 6 verdict categories (vulnerable, bypassable, inconclusive, protected, safe, errors) butAddressed in upstream PR feat: parallelization, HTML report overhaul, Zig parser, dynamic test hardening #23 —PrintAnalyzeSummaryonly displays Vulnerable and Safe — so the totals don't add up.PrintScanSummaryV2already displays all categories correctly; analyze should match.PrintAnalyzeSummarynow displays all verdict categories (vulnerable, bypassable, protected, safe, inconclusive, errors).6. Report paths missing from Go CLI output —Addressed in upstream PR feat: parallelization, HTML report overhaul, Zig parser, dynamic test hardening #23 —PrintReportSummaryexpectshtml_path/csv_path/summary_pathkeys, butReportResult.to_dict()only returnsoutput_pathandformat— so "Reports Generated" is always blank.PrintReportSummaryredesigned to readformat+output_pathdirectly, matchingReportResult.to_dict(). Also added a Go-based HTML report renderer.this.userService.findById()) are unresolved in the call graph. This means the security analysis silently misses data flow through injected services — a major blind spot for most production NestJS apps. This adds DI-aware resolution by extractingconstructorDepsmetadata from the AST and using it to resolvethis.service.method()calls to the correct class.openant parseon a JS/TS repo fails out of the box withCannot find module 'ts-morph'because nothing in the install flow populatesparsers/javascript/node_modules/. Fix adds lazy bootstrap in_parse_javascript: runsnpm installonce if missing, mirroring the Go CLI's venv bootstrap. See fork PR #39.Windows Compatibility
These prevent OpenAnt from working correctly on Windows.
path.relative()produces backslash paths on Windows, but ts-morph treats backslashes as escape characters — so the JS parser finds 0 files. Additionally, Windows\r\nline endings leave trailing\rin file lists, and Unicode symbols (✓✗→) crash on cp1252 consoles.open()calls use the system encoding (cp1252 on Windows), causingcharmap codec can't decodeerrors on any target codebase containing non-ASCII characters. This adds centralized UTF-8 helpers (open_utf8,read_json,write_json,run_utf8) and migrates all file I/O.Pipeline Resilience
New capabilities addressing the cost and time implications of long-running scans failing mid-way.
10. Crash recovery: checkpoint and resume — Enhance already supports checkpoint/resume viaAddressed in upstream PR feat: parallelization, HTML report overhaul, Zig parser, dynamic test hardening #23 — added--checkpoint <path>, but it's opt-in and manual. This makes checkpointing always-on and automatic (replaces--checkpointwith--freshto opt out), extends per-unit checkpointing to analyze and verify (which currently have none), adds scan step-level resume (re-running skips completed steps), adds--freshto parse (so parser improvements take effect without manually deletingdataset.json), and wraps all JSON writes in atomic temp-file-then-rename to prevent corrupt files on crash. Currently, if a multi-hour scan crashes mid-analyze or mid-verify, all progress (and API spend) for that stage is lost.core/checkpoint.pywith directory-based per-unitStepCheckpointfor enhance, analyze, and verify. Uses--checkpointflag and--workers/--backofffor parallelization. Does not include atomic JSON writes or--freshflag.11. Auto-retry errored units — Enhance and analyze automatically retry units that errored on the previous run instead of treating errors as complete.Addressed in upstream PR feat: parallelization, HTML report overhaul, Zig parser, dynamic test hardening #23 —--skip-errorsflag to opt out. Currently, API errors (rate limits, timeouts) permanently mark units as "done" — the only recovery is to re-process everything.StepCheckpoint.load_ids(skip_errors=True)excludes errored units by default so they are retried on resume. No opt-out flag; errors are always retried.12. Parallel LLM calls —Addressed in upstream PR feat: parallelization, HTML report overhaul, Zig parser, dynamic test hardening #23 —ThreadPoolExecutor-based parallelization for enhance, analyze, and verify with lock-protected checkpoints.--concurrency/-jflag (default 4). A scan that takes 2 hours serially completes in approximately 30 minutes with--concurrency 4.ThreadPoolExecutorwith--workers(default 8) and--backoff(default 30s) flags, plus aGlobalRateLimiterfor coordinated backoff across all workers.New Features
init— Makes--languageoptional by auto-detecting the project language from file extensions. Also makes git repository optional for local paths. Reduces friction for new users —openant initjust works.pyproject.tomlafterpip install -eand re-runs install automatically when dependencies change. Prevents stale venv issues aftergit pull.model_config.pywithMODEL_PRIMARY/MODEL_AUXILIARY/MODEL_DEFAULTconstants, replacing hardcoded model strings across 15 files. Makes model updates a one-line change instead of find-and-replace across the codebase.anthropicAPI. SDK handles API-key and local Claude Code session auth natively, provides Read/Grep/Glob/Bash native tools for enhance/verify (replacing the manual multi-turn tool loop), and gives accurate cost tracking viaResultMessage.total_cost_usd. Fully implemented on fork, not yet submitted upstream — see detail section for history and cherry-pick order.generate-contextCLI command with auto-discovery — Addsopenant generate-context [repo-path]as a standalone pipeline step to generateapplication_context.json. Fully integrated with the project system (openant init/project switch) — defaults output to the project scan directory. Also wires up auto-discovery ofapplication_context.jsoninanalyzeandverifycommands (both Go and Python CLIs) so--app-contextis no longer required when the file exists in the scan dir. Previously, running individual steps required manually passing--app-contextto every command or skipping application context entirely.generate-context— Whengenerate-contextdetects a manual override file (OPENANT.md/OPENANT.json), it now prompts the user to choose: use (as-is, skip LLM), merge (feed override into LLM alongside other sources), or ignore (skip override, generate from scratch). New--override-mode <use|merge|ignore>flag bypasses the prompt for CI/automation.--forcekept as backward-compatible shortcut for--override-mode ignore. Previously, override files were all-or-nothing — either they fully replaced LLM generation or were ignored entirely, with no way to combine developer-provided hints with LLM analysis.--freshflag for parse — Adds--freshtoopenant parseto force a full reparse from scratch without manually deletingdataset.json. Useful when parser improvements are deployed and the existing dataset needs to be regenerated. Also clears the dataset duringopenant scanwhen--freshis passed. See fork PR #21.results.json,enhanced_dataset.json,results_verified.json) in atomic temp-file-then-rename viaatomic_write_json(). If a crash or power loss occurs mid-write, the previous output file is preserved intact rather than being left truncated or corrupt. Upstream currently uses plainjson.dump()withopen(), which can corrupt multi-hour scan results on interrupt. See fork PR #7.Detailed implementation notes, dependencies, and cherry-pick risks
Detailed Implementation Notes
Change 1: Test suite + CI (fork PRs #1 + #4, already upstream PR #15)
Change 2: Ruff lint in CI (fork PR #8, partial)
.github/workflows/test.yamlruff check .against upstream/master before submitting to verify clean baselineChange 3: Findings count (fork PR #12)
Change 4: Parse --level default (fork PR #16)
apps/openant-cli/cmd/parse.goChange 5: Analyze summary verdicts (fork PR #18)
Change 6: Report paths (fork PR #19)
Change 7: DI-aware call resolution for TypeScript/NestJS (fork PR #20)
parsers/javascript/typescript_analyzer.js,utilities/agentic_enhancer/agent.py,utilities/agentic_enhancer/prompts.pyChange 8: JS/Go parser Windows paths (fork PR #3)
parsers/javascript/typescript_analyzer.js,parsers/javascript/test_pipeline.py,parsers/go/test_pipeline.pyChange 9: UTF-8 file I/O (fork PR #13)
utilities/file_io.py, plus migrations across ~20 filesChange 10: Checkpoint and resume (fork PRs #7, #9, #10, #21)
Original fork implementation (kept as historical reference):
atomic_write_json) + enhance auto-checkpoint +--freshflag--freshfor parse split to change 19--freshfor analyze/verify--freshflag for parse (forces full reparse, clears dataset in scan)Change 11: Auto-retry errored units (fork PR #15)
Change 12: Parallel LLM calls (fork PR #17)
Change 13: Auto-detect language (fork PR #6)
Change 14: Auto-detect dependency changes (fork PR #23)
Change 15: Centralize model IDs (fork PR #24)
Change 16: Claude Agent SDK migration (fork PR #25)
Status: complete on fork as of 2026-04-19 via fork PRs #30, #31, #32, #33, #34, #36, #37, #38. Not yet submitted upstream.
Timeline:
anthropicrefs in the four call sites.anthropicusage on its own copy of the four files, adding typed rate-limit handling for the new parallel execution path.anthropicwas back in all four files (8 / 5 / 5 / 3 refs).anthropicand added a regression-guard test; PRs #31 through #38 completed the re-migration and dropped the dep.This is the largest change. It subsumes all earlier local Claude attempts (fork PRs #2, #5, #11, #14) which should NOT be submitted individually.
Key benefits:
Single backend: All LLM calls route through the SDK
Local Claude Code support: SDK natively supports both API key auth and local session auth
Native tools: SDK provides Read/Grep/Glob/Bash tools natively, replacing the manual tool loop
Accurate cost tracking: Uses SDK's
ResultMessage.total_cost_usdDependency change: Drops
anthropicin favour ofclaude-agent-sdk. History: fork PR feat(webui): add web user interface #25 originally dropped it; upstream PR feat: parallelization, HTML report overhaul, Zig parser, dynamic test hardening #23 re-expanded its usage; fork PR feature: release: 2026-04-27 - KIR-0000 disclosure fidelity, auth validation, dynamic-test hardening #29 absorbed that back; fork PR release: Python parser dedent fix #30 re-declaredanthropicas a minimal bugfix; fork PRs ci(gitleaks): switch to pull_request_target so fork PRs get GITLEAKS_LICENSE #31–38 completed the re-migration and PR feat: add --fresh flag to parse for forced reparse #38 droppedanthropicfrompyproject.tomlfor good. Current fork master has zeroimport anthropicanywhere inlibs/openant-core/.Dependencies: Centralize model IDs (change 15) should go first.
Cherry-pick difficulty: Cherry-pickable in order if done one fork PR at a time (see table below). The original PR feat(webui): add web user interface #25 diff alone is no longer sufficient — upstream has since added
GlobalRateLimiterand parallel execution paths that rely on typedanthropicexceptions. The fork's re-migration PR chain already handles those additions and can be cherry-picked against current upstream.Note: Must account for upstream's parallelization, rate limiter, and checkpoint system (introduced in upstream PR #23). The fork's re-migration (PRs ci(gitleaks): switch to pull_request_target so fork PRs get GITLEAKS_LICENSE #31–38) already does this.
Fork PRs that re-implement change 16 against current upstream (cherry-pick targets, in dependency order):
tests/test_declared_dependencies.py) that fails CI if any imported distribution isn't inpyproject.toml. Cherry-pick this even if nothing else lands — it prevents the same silent drift repeating on any future upstream merge.utilities/sdk_errors.pytaxonomy — one exception class perAssistantMessageErrorliteral value.AssistantMessage.errordetection intollm_client._run_query. Centralises rate-limit reporting toGlobalRateLimiter.report/generator.py(single-turn, smallest site).context_enhancer._build_error_info's isinstance chain tosdk_errors.*.finding_verifier.py— manual tool-dispatch loop replaced withrun_native_verification(SDK native tools).agentic_enhancer/agent.py; absorbedshared_clientremoval incontext_enhancer.py.anthropic>=0.40.0frompyproject.toml; cleans up one straggler inopenant/cli.py's remediation-guidance LLM call that the earlier ports didn't touch.Technical note on SDK rate-limit surfacing: the Claude Agent SDK exposes API errors via
AssistantMessage.error: AssistantMessageError | None, a Literal including"rate_limit","authentication_failed","billing_error","invalid_request","server_error","unknown"(seeclaude_agent_sdk/types.py:767). No stderr parsing required — earlier notes suggesting otherwise were wrong.retry-afterandrequest-idheaders are not surfaced; theGlobalRateLimiter's default 30s backoff replaces the former in practice.Change 17:
generate-contextCLI command with auto-discovery (fork PR #26)apps/openant-cli/cmd/generatecontext.go, modifiedapps/openant-cli/cmd/root.go,apps/openant-cli/cmd/analyze.go,apps/openant-cli/cmd/verify.go,libs/openant-core/openant/cli.py,libs/openant-core/tests/test_go_cli.pyPIPELINE_MANUAL.md,CURRENT_IMPLEMENTATION.md,README.md, andDOCUMENTATION.md— these should be included when cherry-picking this change.context.application_contextmodule--force/--show-prompt/--jsonflags, and explicit--app-contextprecedence over auto-discovery.Change 18: Override merge mode for
generate-context(fork PR #27)apps/openant-cli/cmd/generatecontext.go,libs/openant-core/context/application_context.py,libs/openant-core/openant/cli.py,libs/openant-core/tests/test_go_cli.py, plus docs (CLAUDE.md,CURRENT_IMPLEMENTATION.md,PIPELINE_MANUAL.md,context/OPENANT_TEMPLATE.md)generate-contextcommand must exist)generatecontext.goandapplication_context.pyuninstall.gopattern) and--override-modeflag. Python core addsfind_override_file()helper,override_modeparameter togenerate_application_context(), and merge prompt supplement fed to the LLM. Terminal detection viaos.Stdin.Stat()skips the prompt in non-interactive/CI environments (defaults touse).--override-modein help output and--force/--override-modemutual exclusion. Manual testing needed for: interactive prompt with a repo containingOPENANT.md, merge mode LLM output (verify with--show-prompt), and non-interactive default behavior.Change 19:
--freshflag for parse (fork PR #21)apps/openant-cli/cmd/parse.go,libs/openant-core/openant/cli.py,libs/openant-core/core/scanner.pyChange 20: Atomic JSON writes (fork PR #7, partial)
core/utils.py(new),core/analyzer.py,core/enhancer.py,core/verifier.pyChange 21: JS parser lazy npm bootstrap (fork PR #39)
libs/openant-core/core/parser_adapter.py(new_ensure_js_parser_dependencies()helper called from_parse_javascript), newlibs/openant-core/tests/test_js_parser_bootstrap.py(5 unit tests covering: node_modules present → skip, missing → run install, npm missing → clear error, install failure → surfaced, bootstrap failure aborts before running Node).subprocess.runandshutil.which.apps/openant-cli/internal/python/runtime.go(check → install → cache), just scoped to the JS parser subdir instead of the user's home dir. The "installing deps (first run, this may take a minute)..." message is printed to stderr so it's visible but doesn't pollute machine-readable output.npm installtoapps/openant-cli/Makefile'sbuildtarget or to a newsetuptarget — rejected because it forces npm on users who never parse JS, and because users who skip the README still hit the same opaqueCannot find module 'ts-morph'error. The lazy check in_parse_javascriptfails at the exact point the dep is needed, and either installs automatically or prints a clear "run npm install in X" message with the exact directory path._parse_javascriptdoes not fall through to Node when bootstrap raises). Tests monkeypatchsubprocess.runandshutil.whichso they run without Node installed. Manually verified against Linux cloudshell repro (the exactCannot find module 'ts-morph'failure documented in the PR description).Superseded Changes (Not for Upstream)
These were intermediate steps toward local Claude Code support or features now covered by upstream. They should NOT be submitted upstream individually.
LocalClaudeClientwhich is deleted in #25local_claude.pywhich is deleted in #25--freshfor analyze/verify which is superseded by upstream's checkpoint systemRecommended Submission Order
The order below respects dependencies and minimizes merge conflicts:
--freshflag for parsegenerate-contextCLI command + auto-discoverygenerate-contextCherry-Pick Risks & Notes
Critical: Each PR must be rebased, not cherry-picked directly
Every fork PR was developed incrementally on top of previous fork PRs (including the superseded local Claude ones: #2, #5, #11, #14). Direct
git cherry-pickwill produce diffs relative to the fork's history, not upstream's. Each change must be rebased or re-authored as a standalone diff against upstream/master (or against the upstream branch that includes previously merged contributions).Import chain dependencies
Several fork PRs create new modules that later PRs import. Missing a dependency in the chain will cause
ImportErrorat runtime:core/utils.py(atomic_write_json)utilities/file_io.py(open_utf8, etc.)utilities/model_config.py(MODEL_PRIMARY, etc.)cli.pyflag accumulationThe fork added CLI flags across multiple PRs. When rebasing against upstream, each PR needs to add its flags against upstream's version of
cli.py, not the fork's. The same applies to the Go CLIcmd/*.gofiles.Go CLI cmd files
Upstream has 17 files in
apps/openant-cli/cmd/. Upstream may have modified the same cmd files the fork changed. Each Go CLI PR should be diffed against upstream's current version of those files.llm_client.pydivergenceThis file diverged between fork and upstream:
anthropic.Anthropic()directly, model IDs hardcoded as"claude-opus-4-20250514"/"claude-sonnet-4-20250514"model_configimportsutilities/sdk_errors.py) and #33 (wires SDK error surfacing into_run_query) are the clean cherry-pick targets for this file. Change 15 still needs re-authoring against upstream's version since it predates the SDK work.pyproject.tomldependency divergenceUpstream has
anthropic>=0.40.0. The fork has fully migrated off it (as of 2026-04-19) via the PR sequence listed under change 16. Change 16, when ported upstream, removesanthropicand addsclaude-agent-sdk>=0.1.48. Intermediate fork PRs (#30 through #37) keep both deps declared while individual ports land; only the final PR (#38) removesanthropic. If cherry-picking incrementally upstream, follow the same order — removing the dep before all usages are ported breaks the build.Ruff lint may fail on upstream code
Change 2 adds ruff with F821/F811 rules to CI. If upstream has introduced code that violates these rules, CI will fail. Run
ruff check .against upstream/master before submitting to verify clean baseline.context_enhancer.pyandfinding_verifier.pyevolutionThese files were modified by multiple fork PRs:
context_enhancer.py: change 9 (UTF-8 I/O), change 16 — fork PRs #34 (_build_error_infoport) and #36 (shared_clientremoval).finding_verifier.py: change 9 (UTF-8 I/O), change 16 — fork PR #37 replaces the manual tool loop withrun_native_verification.Each of those change-16 PRs applies cleanly against current upstream. Ordering matters (see the table under change 16); the files compile mid-chain because
anthropicstays declared inpyproject.tomluntil the final PR (#38) removes it.SDK migration (change 16): cherry-pick order matters
Change 16 is cherry-pickable one fork PR at a time in the order listed under change 16's detail section. The historical PR #25 diff alone is not sufficient — upstream has since added
GlobalRateLimiterand parallel execution paths that rely on typedanthropicexceptions. The fork's re-migration PRs (#31, #33, #32, #34, #37, #36, #38) already handle those additions.Key facts when cherry-picking:
AssistantMessage.error: AssistantMessageError | None(Literal including"rate_limit"). No stderr parsing required.GlobalRateLimiter.report_rate_limit()is called centrally fromllm_client._run_querywhenever that field fires. Callers do not need their ownexcept anthropic.RateLimitErrorblocks.retry-afteris lost in translation — the SDK does not surface the header value. The limiter's default backoff (30s, configurable) replaces that hint in practice.Upstream may have moved
All analysis is based on
upstream/masteras of 2026-04-19. Upstream PR #23 has been merged, superseding fork changes 3, 5, 6, 10 (stages 2-3), 11, and 12. If upstream merges other PRs before these contributions land, additional conflicts may arise. Re-fetch and recheck before each submission.