Add paths-ignore to the push and pull_request triggers so changes that
no CI step scans (markdown files, LICENSE, .gitignore) no longer spin
up the full suite. Source-relevant paths (.yml/.yaml, workflow files,
action.yml) stay triggerable.
Closes#905
Add platform-specific install options (curl|sh for MacOS/Linux,
irm|iex for Windows) to the 'More' dropdown in the homepage hero.
- Add apple.svg, linux.svg, windows.svg icons
- Add three new secondary install channels with OS-specific commands
- Reorder MacPorts to the bottom of the dropdown
- Add i18n keys for all four locales
- Fix dropdown clipping by removing overflow:hidden on hero section
- Fix dropdown alignment (left:0 instead of right:0)
Print the review session UUID on a separate line after the terminal
summary so users can copy it for `ocr resume` without --format json or
filesystem browsing. The line is omitted when the session ID is empty
(e.g. when session persistence is unavailable). JSON output is unchanged.
Co-authored-by: Kite <254839944+lizhengfeng101@users.noreply.github.com>
* fix(opencode): separate per-file and overall timeouts
* fix(opencode): default to 30-minute overall timeout instead of no timeout
Defense in depth: when overallTimeoutMinutes is not configured,
apply a 30-minute watchdog so genuinely stuck processes are reaped
even if the abort signal never fires.
---------
Co-authored-by: kite <lizhengfeng.lzf@alibaba-inc.com>
* feat(provider): add editable Base URL step to official provider wizard
The official-provider tab in `ocr config provider` only captured API key
and model, with no way to override a preset provider's Base URL. The
resolver already honored `entry.URL` over `preset.BaseURL`, but the TUI
never exposed it — litellm (a self-hosted gateway rarely at
http://localhost:4000/v1) was the canonical pain point.
Add a Base URL step to the official-tab flow (stepModel -> stepBaseURL ->
stepAPIKey), pre-filled with the effective URL (configured override or
preset default). Persist `providers.<name>.url` only when the entered
value differs from the preset default, so the preset remains the fallback
and configs without an explicit url are unchanged. Custom/manual tabs are
unaffected.
Add resolver regression tests (litellm override + default fallback) and
TUI tests (pre-fill with preset/override, Esc navigation, persistence of
override vs. clearing on preset default). Update the four official-tab
tests that assumed stepModel -> stepAPIKey to traverse the new step.
* feat(provider): surface override Base URL in model picker and document it
With the wizard now able to set a Base URL override for built-in
providers, make the override visible and discoverable.
- `ocr config model` shows the effective Base URL for a preset provider
(the configured `providers.<name>.url` override, or the preset default
when none is set) so users can confirm their gateway is in use.
- The provider-wizard model-selection step shows the same effective URL
via a tab-aware `effectiveBaseURL()` helper (official override/preset,
or custom provider URL).
- Document `providers.<name>.url` as a built-in provider override in the
configuration docs, with a litellm example and the preset-as-default
semantics; note the wizard's editable Base URL step.
Add tests covering the model-selector display (override vs preset
default) and the wizard's effectiveBaseURL resolution.
* fix(provider): address PR review — URL trim, validation, dead code, Esc display
Address 4 of 5 code review findings on PR #729:
1. URL trim consistency (provider_cmd.go): trim the Base URL once and use
the trimmed value for both comparison and persistence, preventing
whitespace-polluted URLs from being written to config.
2. URL format validation (provider_cmd.go): validate that the Base URL
has an http/https scheme and non-empty host before persisting, giving
immediate feedback instead of a runtime failure. Rejects malformed
values like bare hosts or ftp:// schemes.
3. Dead code removal (provider_tui.go): remove the init-time pre-fill of
officialURLInput that is always overwritten by loadOfficialURL() when
the user enters the Base URL step. Pre-fill logic now lives in a
single place.
4. effectiveBaseURL reflects pending edit (provider_tui.go): when the
user edits the Base URL and presses Esc back to model selection,
effectiveBaseURL() now returns the in-progress value from
officialURLInput instead of the stale on-disk config.
The SSRF/private-IP finding (#2 in review) is not addressed — it is a
false positive for a local CLI tool where localhost and private network
endpoints are the primary use case (the litellm preset default is
http://localhost:4000/v1).
* feat(provider): implement URL trimming and validation for provider configuration
* fix(provider): remove obsolete official URL handling
---------
Co-authored-by: Kite <254839944+lizhengfeng101@users.noreply.github.com>
* feat(resume): verify input identity before reusing checkpoints
`ocr review --resume` admitted a session whenever the ref text matched, which
is neither sufficient nor necessary evidence about the input: `abc1234` and
`abc1234def` name the same commit, while a branch name that did not change can
name a new one. Resuming then mixed comments computed from one input with
comments computed from another, and nothing in the report distinguished them.
Compare the resolved input identity instead. agent.ResolveIdentity replays the
run's own selection — the same diff load and the same two filter passes — and
returns the identity a real run would record, so the parent manifest and the
child candidate are directly comparable. Any mismatch on mode, repository,
source artifact or rule config rejects the whole resume rather than degrading
to partial reuse. A provider or model change must be asked for with --provider
or --model; one that arrived through config or the environment is rejected.
Two behaviour changes fall out of this. Ref text no longer decides admission,
so ValidateOptions only checks the review mode. A parent that completed zero
items is now admitted: its manifest is verifiable, so its selected set is
simply re-dispatched, which is the case resume exists for.
Reuse is then gated on the parent manifest rather than on the checkpoint lines:
only a fingerprint the manifest claims as completed or reused is reused, which
keeps the manifest the single source of coverage truth. That gate is also what
makes an unreadable checkpoint survivable, so review loads through
LoadReviewResumeState, which drops lines it cannot parse — the file such a line
described is simply reviewed again, instead of one truncated write costing every
other file its checkpoint. Scan keeps the strict LoadResumeState, because with
no manifest to arbitrate, a dropped line cannot be told apart from a checkpoint
that was never written.
Rejection happens strictly before agent.New, because session.New writes
session_start the moment it is called — validating any later would leave an
orphan session behind every rejection. Keeping it there needs the run to review
exactly what was admitted, so the pre-flight hands back the commit endpoints it
resolved that identity from, and a resumed run loads its diff from those instead
of from the refs the user typed; file_read reads at the same sealed head. Both
loads then see the same immutable objects, so a ref moving after admission can
no longer change what the run reviews, and no mid-run re-check is needed to
discover that it did. An accepted resume records one
resume_lineage event naming the parent run and the provider/model endpoints,
surfaced by `ocr session show`; it carries non-secret labels only.
Interrupted runs become unresumable, since session_end is the sole carrier of
run_manifest. That is deliberate: an unverifiable input is exactly what this
change refuses to build on, and the error says so rather than reporting the
parent as unproductive.
Refs #786
* fix(resume): freeze refs before loading identity diff
* test(resume): cover sealed input resolution
* feat(llm): task-scoped session affinity via {ocr_session_key} template variable
Derive a prompt-cache affinity key per LLM conversation, scoped to the
review session and the task within it (<session-id>-<task-type>-<hash>).
Review/scan runs bind the session ID into the request context and each
task conversation refines it where it starts, so every request carries
the real OCR session's key at per-conversation granularity — the
granularity provider prompt caches reuse prefixes at.
Embedding the {ocr_session_key} placeholder in extra_headers or
extra_body values is the opt-in: clients expand it per request, and
requests without it are unchanged. OCR never enforces a parameter or
header name, so any provider convention works with existing config
fields, e.g.:
extra_body: {"prompt_cache_key": "{ocr_session_key}"} (OpenAI)
extra_headers: x-session-affinity={ocr_session_key} (gateways)
Closes#229
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* add license headers
* edit docs
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Normalize position-dependent trailing line endings before hashing per-file diffs so unchanged files can be reused across resumed reviews.
Co-authored-by: Kite <254839944+lizhengfeng101@users.noreply.github.com>
govulncheck now fails on golang:1.26.5: the Go vulnerability database
lists fixes in 1.26.6 for seven standard-library issues reachable from
this module (GO-2026-6218, -6091, -6090, -6089, -6088, -5972, -5026),
via net/url, html/template, crypto/tls, net/http, encoding/xml and
encoding/asn1. Nothing in the module's own code changed — the step
turned red when the database was updated, and because Govulncheck runs
before "Test with coverage", every PR is now blocked before a single
test executes.
Bump the pinned image in the test, cross-compile and release jobs, and
refresh the stale version reference in the translation-sync comment so
it keeps matching the other jobs.
Verified locally under go1.26.6: govulncheck reports no vulnerabilities
and exits 0, gofmt -s and go vet are clean, go mod tidy is a no-op, and
the full test suite passes (23 packages).
Cobra's default message for a wrong positional-argument count ("accepts
2 arg(s), received 1") names neither the command nor what it expects, and
the root command sets SilenceUsage, so no usage block follows it. Running
"ocr config set provider" gave the user nothing to act on.
Add exactArgs and minimumArgs, drop-in replacements for cobra.ExactArgs
and cobra.MinimumNArgs that build the message from metadata the command
already declares: the positional signature in Use, plus Example and
ValidArgs where present. The guidance therefore cannot drift from the
command's own help output, and no command carries a hand-written error
string. This mirrors how flagErrorWithSuggestion handles the analogous
flag-error case.
Wire the seven commands that take positional arguments: config set,
config unset, rules check, session show, session comments, delegate rule
and completion. Exit codes are unchanged; only the message text differs.
The supplied count is deliberately not echoed back, since it adds nothing
the user cannot see in the line they just typed.
A tree walk over the command tree fails if a command declaring positional
placeholders still reports the raw count message, so wiring a new command
to cobra.ExactArgs directly is caught by tests.
Fixes#890
Replace the plain-text badge line in GitHub review and summary comments
with a single static shields.io badge in the format
img.shields.io/badge/<category>-<severity>-<color>, with the color keyed
off severity (low green, medium orange, high red, critical darkred).
The image renders only when both fields are known enum members; missing
or unknown metadata falls back to the existing plain-text badge so
arbitrary model output is never hotlinked into an image URL. The image
alt text keeps the plain-text badge content for screen readers and
image-load failures, the hidden ocr-id marker stays first, and the CLI's
buildBadge output is unchanged.
* feat(llmloop): add grace round after tool-request budget exhausted
When RunPerFile exits because MaxToolRequestTimes reaches zero, perform
one additional LLM call with only code_comment and task_done available.
This gives the model a final chance to submit findings it identified but
had not yet reported, preventing loss of review comments on budget stop.
* fix(llmloop): address review comments on grace round
- Check ctx.Err() before making the grace round LLM call to avoid
wasted API calls when the context is already cancelled.
- Pass messages copy to AppendTaskRecord and call rec.SetResponse so
the grace round interaction is visible in session/debug logs.
* test(llmloop): add unit tests for grace round
Cover three scenarios:
- Grace round fires and collects code_comment on budget exhaustion
- Grace round is skipped when context is already cancelled
- Grace round is NOT triggered on StopEmptyRounds
* fix(prompt): replace the fullwidth colon in the file_read tool description
tools.json advertised the example output as "File:path/to/example.go" with
a fullwidth colon (U+FF1A), while file_read.go actually emits "File: %s".
The description is sent to the model on every review, so the example did
not match the output it was describing.
Also switches action.yml's OCR_LANGUAGE example from 中文 to Chinese, for
the same reason as #861: the value is fed to the LLM and Chinese is what
the rest of the project uses.
* chore(ci): fail CI when CJK characters appear in source files
Comments, identifiers and strings in this repository are meant to be
English, but nothing enforced it — #861 had to clean up leftovers by hand,
and the same drift keeps arriving through generated code and contributions
written internally.
scripts/verify-cjk.go walks the index plus untracked files and reports Han
ideographs, kana, CJK punctuation and fullwidth forms. Written in Go rather
than shell so it does not depend on the container's grep having PCRE, and so
`unicode.Is` decides what counts as CJK instead of a byte range that would
flag the em dashes used throughout the comments. `//go:build ignore` keeps
it out of ./..., so it does not affect go vet, go build or the coverage
threshold.
Untracked files are included (--others --exclude-standard) so a new file is
checked before it lands: while writing this, the script's own comment used
Chinese punctuation as an example and went unreported until it was staged.
Two escape hatches, preferring the narrow one: an `allow-cjk: <reason>`
marker comment on a single line, or a prefix in allowedPrefixes for a whole
tree. 23 existing lines get markers (UTF-8 encoding fixtures, multibyte
truncation fixtures, language-switcher labels, the fullwidth bar used as a
terminal cursor). pages/src/i18n/ is allowlisted as translated UI copy;
extensions/vscode/ is allowlisted TEMPORARILY — its comments, test names
and zh-cn NLS bundle are still Chinese and need a follow-up pass.
Wired into CI next to the license and action-pin checks, plus
`make cjk-check` and `make check` for local runs.
* chore(ci): generalise the CJK check to all non-English text
Addresses the review feedback, and widens the rule that the feedback
exposed.
Review feedback:
- exemptMarker requires its colon, so a bare "allow-cjk" can no longer
exempt a line without giving a reason.
- The script is named for CJK but missed Hangul.
- git ls-files gains -z, so paths that are not plain ASCII arrive
unquoted, and its stderr is reported rather than a bare exit status.
- main discarded run()'s error entirely and only called os.Exit(1),
which is what made the lost stderr invisible in the first place.
- The CI step and AGENTS.md say "unapproved", since escape hatches exist.
The check was skewed by writing system rather than by language. In one
array the 'zh' and 'ja' labels each needed a marker while the adjacent
'ru' label passed untouched, and nine lines of Russian sat in the tree
unflagged: two language-switcher labels and the heading-ID fixtures.
Contributors writing Chinese had to justify every line; contributors
writing Russian had nothing to justify.
The rule is now "a letter outside ASCII", since written English needs no
letter beyond the ASCII 26 -- Cyrillic and Han as much as the diacritics
that spell German or Vietnamese. Scripts are not enumerated, so one
nobody has contributed in yet is covered when it arrives. Common and
Inherited pass, so letterlike symbols (U+2139, U+2113) are not mistaken
for prose, and combining accents are caught, so the decomposed spelling
of an accented letter cannot slip through. Symbols and emoji stay out of
scope by construction: they are not letters.
Renamed to scripts/verify-english-only.go and make english-check, and
the marker to allow-non-english:. Text spelled entirely in ASCII still
takes a dictionary to identify and stays a matter for review.
* docs(agents): restate the English-only rule as rule, homes, hatches
The rule was one dense bullet that led with the detection mechanism and
mentioned the exemptions only in passing, which is the wrong order for
the reader: an agent needs to know where a translation may go before it
needs to know which Unicode scripts are flagged. Split into three.
The homes are now spelled out from what the tree actually holds, rather
than left as "<locale> docs or an i18n table": README and CONTRIBUTING
in zh-CN, ja-JP, ko-KR and ru-RU; the doc pages under
pages/src/content/docs/ in en, zh, ja and ru; the UI copy tables in
pages/src/i18n/. Also why the two are exempt for different reasons --
Markdown by extension, the i18n tables by prefix because they are .ts --
since that decides where a new translation can safely go.
Drops the enumerated list of what "make check" runs. It duplicated the
Makefile, went stale the moment a check was added (this PR had to edit
it), and told an agent nothing it would not read in the output anyway.
What is worth saying is that the target writes to the tree.
* fix(ci): detect U+FE10–FE6F CJK punctuation in english-only check
The vertical forms (U+FE10–FE19), CJK compatibility forms (U+FE30–FE4F)
and small form variants (U+FE50–FE6F) were not caught, even though their
fullwidth counterparts (U+FF00–FFEF) already were. A small question mark
(U+FE56 ﹖) or vertical comma (U+FE10 ︐) left in source reads as correct
English punctuation and is invisible in review — the same class of typo
the fullwidth range already defends against.
Skip U+FE20–FE2F (Combining Half Marks) which are used in Latin text.
* feat(llm): add retry report data layer
Add the internal data layer for an explicit LLM request retry report: request
identity, attempt classification, and a per-run collector that freezes into an
immutable report. No behavior change — nothing is mounted on any client and no
output is produced, so this is inert until the observer is wired up.
- RequestMeta identifies one logical request (provider, model, file path, task
type, request no) and travels through the request context, so the
single-method LLMClient interface and every call site stay unchanged.
- logical_request_id is SHA-256 over a canonical NUL-terminated encoding of
run_id plus the meta. It is computed in Freeze, so the collector can be
constructed before the session exists.
- classifyAttempt derives error_class and failure_phase from the HTTP status
and the Go error type only, never from error message text. A non-2xx status
outranks the error, since it is the stronger fact.
- RetryCollector is created per run with no package-level state, is safe for
concurrent use, and drops attempts that carry no identity, which is how scan
and llm test requests stay out of the report.
- The request outcome is decided once, in Finalize, from the attempt sequence
plus the returned error and the parent context state, rather than inferred
from the last attempt: cancelling during backoff produces no new attempt, so
the sequence still ends in an error while the outcome is cancelled.
- Freeze recomputes every aggregate from the listed requests and returns a
construction error instead of publishing self-contradictory numbers. Ordering
bugs (double Finalize, mutation after Finalize) are recorded as violations
and surface there.
The report has no free-text field, so there is nothing to redact: no bodies,
prompts, URLs or raw SDK error strings. A test pins the exact set of plain
string fields so adding one has to be argued for.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: 艺临 <gongyiling.gyl@alibaba-inc.com>
* feat(llm): observe retry attempts via SDK middleware
Mount a shared observer on all three LLM clients (Anthropic, OpenAI Chat
Completions, OpenAI Responses) through option.WithMiddleware, so every real
HTTP attempt the SDK retry loop makes is recorded against the logical request
that issued it.
The observer reads response headers only -- status code, request-id /
x-request-id, Retry-After (all three forms, at the SDK's own precedence),
x-should-retry -- and never touches the body, which the SDK owns and closes
before retrying. Attempts without a RequestMeta on the context are dropped
whole, which is how scan and `ocr llm test` stay out of the report.
RecordAttempt now takes the attempt's start and end timestamps instead of
pre-computed durations. observed_backoff_ms spans two attempts, so only the
collector can derive it; deriving both durations there also means the observer
cannot desynchronize numbering from the real call order. No clock abstraction
is needed and the values stay deterministic in tests.
The collector is reached through an unexported ClientConfig field rather than
new constructor parameters, keeping the three exported constructors unchanged.
It is created per run in loadLLMRuntime, not package-level, so two runs in one
process cannot share data. Nothing consumes it yet -- P5 calls Freeze at the
run boundary.
The roadmap's X-Stainless-Retry-Count cross-check is deliberately not
implemented: the SDK stops maintaining that header once ExtraHeaders overrides
it, so the mismatch branch is only reachable from a legitimate configuration,
and the desync it guards against is already caught at build time by the
exhaustion and recovery tests asserting exact attempt counts.
WithMaxRetries(5) and WithRequestTimeout are untouched; the SDK's retry
decisions are observed, never overridden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(llm): correct attempts and finalize requests at the client boundary
The middleware can only observe real HTTP attempts, so an HTTP 200 that
carried a truncated body, undecodable JSON, a mid-stream failure, or a dead
Responses object was recorded as a success. Each client now corrects its last
attempt before returning and finalizes the logical request exactly once.
- add retry_boundary.go: classifyBoundaryError (unrecognized errors are left
alone rather than bucketed as unknown, since the only way left to tell them
apart would be message text), classifyStreamError, reviseAttempt,
finalizeRequest, streamIntegrityError and the panic sentinel
- defer the boundary on all three CompletionsWithCtx, which now use named
results; correction runs before Finalize, as the reverse order would be a
"revised after Finalize" violation and drop the whole run's report
- correct both EOF branches ahead of their ctx early return, so a parent
cancel between the two SDK calls cannot leave a truncated attempt as success
- split completionsStreaming into a wrapper with a single exit, so the four
inner returns need no correction call of their own
- replace the three bare fmt.Errorf stream integrity errors with a dedicated
type, messages unchanged
- parentCancelled reads only context.Canceled: the per-attempt deadline from
WithRequestTimeout must surface as failed, not as a user abort
- drop finalizeForTest from the observer tests; every case now reaches Freeze
through a client, so a missing defer fails that case instead of passing
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(llm): stamp request identity on review LLM requests
review 的五类逻辑请求在调用 SDK 前建立 RequestMeta,使 observer 能按请求身份收集 attempt;scan 的六类请求保持无 meta、不进报告。
- Deps 增加 NewRequestMeta 工厂字段:review 在 agent.New 注入闭包,scan 保持 nil;不用空 provider 当开关,空串是 unnamed endpoint 的合法值
- main_task / memory compression / re-location / plan / review filter 五个落点遵循固定顺序:AppendTaskRecord -> requestCtx -> 请求
- compression 的记录创建移到请求之前,使 request_no 在请求发起时即存在;orphan llm_request 对 resume 无害(applyResumeLine 无该分支),补回归断言
- ReLocateComment 拆出纯 prompt 构造 BuildReLocationMessages,internal/diff 不接触 session / meta;Duration 口径保持含 prompt 构造时间不变
- 导出 RequestMetaFromContext,供 llmloop / agent / scan 三包的测试跨包验收请求身份
* feat(cmd): publish the frozen retry report at the run boundary
在 review 运行边界冻结重试报告并经两个出口发布;scan 与 llm test 输出不变,session JSONL 与 run manifest 契约不动。
- Runner 增加后台 WaitGroup 与 WaitBackground():agent.Run 在 dispatchSubtasks 之后、finalizeManifest 之前收口 async compression,消除 Freeze 见到未 Finalize 请求而吞掉整份报告的竞态;不加第二个超时,等待依赖 SDK 遵守取消契约
- review_cmd.go 在 ag.Run 返回后调用 Freeze,run_id 取 session 内存 UUID 而非持久化门控的 SessionID();构造错误并入 emitErr 而非 runErr,不包装成 review failed、不触发失败 usage、不打 --resume 提示
- 报告以末位参数传给 emitRunResult / outputJSONWithWarnings,不扩展 ResultProvider;双出口去重:emitRunResult 已执行时 emitFailureUsage 不重复携带
- 终端摘要走 stdout,位于评审结果与项目摘要之间,全量渲染不截断,file_path / task_type 经 sanitizeTerminal 防控制字符注入
- JSON 在 jsonOutput 末位追加 retry_report(omitempty),直接复用 llm.RetryReport 的字段与 tag;首次成功运行输出逐字节不变
- 端到端:假 Anthropic server + 真 git 仓库驱动 runReview,覆盖干净运行、recovered+failed、全失败去重、Freeze 构造错误、session 持久化失败五个场景;manual_e2e tag 保留写码前的手工验证夹具
* test(cmd): consolidate retry report tests by responsibility
The retry-report tests for #368 P5 split coverage of emitRunResult and
emitFailureUsage into their own file, leaving the review-run emit
functions tested in two places. Move those emit-boundary cases into
emit_run_result_test.go beside the pre-existing emitRunResult tests, and
rename the remaining file to retry_report_render_test.go so it holds only
the report-rendering cases (outputRetryReportText, the JSON key-set
allowlist, retryAttemptChain). The shared retryReportFixture stays with
the rendering tests; both files are package main so it is still reachable.
No test logic changes; only relocation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: separate cancelled retry requests
---------
Signed-off-by: 艺临 <gongyiling.gyl@alibaba-inc.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* docs(comments): translate Chinese comments to English
Rewrite the remaining Chinese code comments outside the VSCode extension
in English, so the Go core and the pages site read consistently.
- allowed_ext.go: translate the default_exclude_patterns.json package doc.
Quote the wildcards ("*", "**", "{a,b,c}") so gofmt stops reflowing the
leading "*" as a markdown bullet, which had swallowed the first entry's
wildcard and broken the list alignment in godoc.
- HighlightsSection.tsx: translate three comments in parseStatValue and
CountUpValue.
* docs(examples): use "Chinese" instead of "中文" in OCR_LANGUAGE examples
The language config value is fed to the LLM, which understands "Chinese"
just as well, and "Chinese" is what the rest of the project already uses
(skills/open-code-review/SKILL.md, config_cmd_test.go, ApplyLanguage).
Keeps the GitLab CI example's inline docs fully English.
* fix(agent): drop the unreachable Chinese branch from planBlockPattern
task_template.json ships a single English template ("### Review Plan
(Optional)") and is embedded via go:embed with no override path, so the
"审查计划" alternative could never match anything. It came from the
pre-open-source template and survived the #33 fix as dead defensive code.
Drops the two test cases that only exercised that alternative.
Match directory-only patterns against ancestor paths with glob and root-anchor semantics aligned to Git. Add regression coverage for nested, globstar, component-glob, and anchored patterns.
Test: make test
* feat(llm): add Novita AI as a built-in provider
Novita's endpoint (https://api.novita.ai/v3/openai) is OpenAI-compatible,
so it registers like the other OpenAI-protocol providers (deepseek, kimi,
z-ai, ...). Adds the registry entry, the matching name in the
provider-order test, and the provider table in the en/zh/ja
configuration docs. Model ids are taken from the live
/v3/openai/models endpoint.
* fix(llm): correct Novita base URL to current documented endpoint
The registry entry, tests-adjacent docs tables (en/ja/zh configuration.md)
used https://api.novita.ai/v3/openai. That path still resolves today, but
Novita's current documentation (novita.ai/docs/guides/llm-api) no longer
shows it; the current documented OpenAI-compatible endpoint is
https://api.novita.ai/openai. Verified live 2026-08-01: both paths return
identical /models and /chat/completions results, so this is a stale-citation
fix, not a functional break.
* Recommend Novita's current flagship models
The models listed for Novita were older ids that no longer reflect what
the platform leads with. Point the recommendations at the three current
flagships instead, each verified against api.novita.ai:
moonshotai/kimi-k3 1M context, native vision
zai-org/glm-5.2 1M context, long-horizon agentic work
deepseek/deepseek-v4-flash-0731 1M context, cheapest of the three
Context windows, output limits, input modalities and pricing were taken
from the live /openai/v1/models response rather than carried over.
* Apply suggestion from @wu21-web
Co-authored-by: Tao Xin <wu2196674@icloud.com>
* Sync provider registry with upstream to resolve merge conflict
Rebase-equivalent update of internal/llm/providers.go,
internal/llm/providers_test.go, and the en/ja/zh/ru configuration docs
to match upstream/main's current content (minimax-cn, mistral, model
list refreshes) while keeping the novita entry this branch adds. This
is a targeted content sync of the six files that conflicted, not a
full merge, so unrelated upstream changes (workflows, CI, etc.) are
left untouched. Also adds the novita row to the ru docs table, which
was missing.
---------
Co-authored-by: kite <254839944+lizhengfeng101@users.noreply.github.com>
* fix(action): pin nested action references to full commit SHAs
A consumer who SHA-pins alibaba/open-code-review still ran whatever the
floating actions/* tags inside action.yml pointed at, so the outer pin
did not actually freeze the workflow. Pin all four nested references to
full commit SHAs with a trailing version comment, enforce the invariant
with scripts/verify-action-pins.sh in CI, and document the dual pin
(action SHA + ocr_version) that reproducible setups need.
Refs #816
* fix(scripts): fail the pin check when action.yml is missing
* feat(viewer): add review comment tag filters
* fix(viewer): normalize filter chip state values
Use the same empty-string fallback when updating filter-chip active state as
when handling clicks, preventing filters without a value attribute from
appearing inactive after selection.
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(viewer): improve comment tag filter controls
* fix(viewer): support combined comment filters
* fix(viewer): simplify active filter chip ring
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Mark the last content block of the final message with ephemeral
cache_control so multi-turn conversations cache the growing history,
not just the static system+tools prefix. Each turn then reads the
previous full prefix and only writes the new delta.
* fix(codeql): Workflow does not contain permissions
* 更新 translation-sync.yml
Co-authored-by: Lei Zhang <61303077+stay-foolish-forever@users.noreply.github.com>
---------
Co-authored-by: Lei Zhang <61303077+stay-foolish-forever@users.noreply.github.com>
- Create AGENTS.md with project guidelines for all AI coding assistants
- Simplify CLAUDE.md to reference AGENTS.md
- Remove CLAUDE.md from .gitignore so it can be tracked in the repo
* feat(llm): support custom retry status codes via retry_codes config
Add a retry_codes configuration field that allows users to specify
additional HTTP status codes (4xx) that should trigger exponential
backoff retry. This targets self-hosted LLM clusters that misuse
status codes like 403 or 400 for rate limiting.
Implementation uses SDK middleware to inject the x-should-retry: true
response header when a configured status code is encountered, leveraging
the existing SDK retry mechanism (up to 5 retries with exponential
backoff) without any SDK modifications.
Configuration is supported via:
- Provider config: providers.<name>.retry_codes / custom_providers.<name>.retry_codes
- Legacy llm config: llm.retry_codes
- CLI: ocr config set providers.<name>.retry_codes 403,400
Also fixes cloneProviderEntry to copy previously missing fields
(TimeoutSec, ExtraHeaders, RetryCodes) for TUI rollback correctness.
* test(llm): add unit tests for retry_codes feature
Cover ParseRetryCodes validation, retryCodesMiddleware behavior,
resolver integration (provider and legacy config paths, invalid codes),
and end-to-end retry via httptest for both OpenAI and Anthropic clients.
* feat(llm): silently filter redundant retry_codes (408/409/429) instead of erroring
Replace validateRetryCodes with sanitizeRetryCodes that filters out
SDK-default codes and returns warnings. The resolve phase silently
drops redundant codes without interrupting startup. The config set
command prints warnings to stderr so users are informed.
This makes the tool friendlier for users migrating from other tools
who habitually configure 429 and similar codes.
* test(tui): add coverage for cloneProviderEntry deep-copy of TimeoutSec/RetryCodes/ExtraHeaders
Verify that the clone correctly copies these fields and that
mutations to the clone do not affect the original. Also pin the
nil-stays-nil behavior for RetryCodes and ExtraHeaders.
* docs(llm): fix contradictory ParseRetryCodes comment
* test(config): cover retry_codes warning output in config set path
Verify that redundant codes (429, 408) emit a WARNING to stderr while
still writing the valid codes to config. Also verify that valid-only
codes produce no warning output.
* feat(llmloop): backfill comment thinking from the turn's reasoning or message
Expose ChatResponse.ReasoningContent and backfill per-comment thinking
with the current turn's reasoning content, falling back to the assistant
message for models that do not expose reasoning, so --format json output
carries thinking even when the model omits it.
* fix(llmloop): drop content fallback for comment thinking backfill
The turn's assistant message is usually a short user-facing preamble
rather than real reasoning, so backfill per-comment thinking only from
the model's native reasoning_content and leave it empty otherwise.
Add a full-wiring RunPerFile test for the reasoning backfill and a
regression test that fails if the content fallback returns. Sync the
thinking docs across en/zh/ja/ru.
* docs(llmloop): note that turn-level thinking is shared by design
Document in the main loop and at the code_comment backfill site that
the model emits reasoning once per turn, so every tool call and
comment in the same turn intentionally shares the same thinking.
* feat(pages): show live npm downloads in highlights stats
Add a useNpmDownloads hook that fetches real monthly download counts
from the npm registry API at runtime, and surface them in the
Highlights section, falling back to a static i18n value while loading
or on error. Also refresh the stats row copy and ordering, and sync
the label/caption/value changes across en, zh, ja and ru.
The hook encodes the (scoped) package name into the request URL and
aborts the fetch after a timeout so the UI degrades promptly on a
slow or unresponsive network.
* docs(pages): translate highlights npm-downloads comments to English
* fix(pages): give stat3 a distinct caption and refactor npm-downloads hook
- Give highlights stat3 its own caption instead of duplicating stat1's
'battle-tested' text across all four locales
- Correct the Russian stat3 label to match 'real-world tasks' in the
other locales
- Refactor the npm-downloads fetch to async/await and clear the timeout
in a finally block so it no longer lingers after the request settles
* docs: fill in missing translations
* Revert "docs: fill in missing translations"
This reverts commit c881e5da292c9f6ed8e962edea22166da4020d71.
* remove stale content from README
runPreview, runScanPreview, and delegate's preview each built a throwaway
agent via agent.New / scan.NewAgent purely to reach Preview. Both
constructors auto-create a session, and session.New opens persistence and
buffers a session_start record, so every preview created a JSONL file
under the OCR home. Preview never runs or finalizes a review, so that
file was left unfinalized and usually empty.
Make the exported entry point a package-level Preview(ctx, args) in both
internal/agent and internal/scan. It builds only what file selection
needs, so there is no session, manifest, or runner to leak. The existing
bodies stay as unexported methods, keeping the in-package tests
(including scan's regression test that Preview must not mutate a.items).
Deleting the file afterwards was rejected: it would still leak on crash
and would keep the wrong abstraction.
Tests assert at the CLI boundary, with a temporary OCR home, that no
session store is created by any of the three preview commands. They also
neutralize global git config, which git resolves via XDG_CONFIG_HOME
independently of HOME.
Navigating to a lazy-loaded page (/benchmark, /quickstart, /docs, /blog)
re-triggered the Suspense fallback — a plain black div — producing a black
screen flash while the chunk downloaded.
Route changes are now applied inside a React transition via a new
useTransitionedLocation hook: <Routes> renders from a location that trails
useLocation() by one startTransition, so a suspending route keeps the
current page on screen until its chunk is ready. The black fallback still
shows on first paint, preserving the intentional dark background on
initial load. ScrollToTop now follows the displayed location, so the
scroll reset happens when the new page actually appears.
Fixes#788