3936 Commits

Author SHA1 Message Date
Andrew Kumanyaev e3354fac81 Merge pull request #662 from zzet/fix/issue-638-ambiguous-member-call
CI / test (1.26, macos-latest) (push) Waiting to run
CI / test (1.26, ubuntu-latest) (push) Waiting to run
CI / build-windows (push) Waiting to run
CI / build-linux-static (push) Waiting to run
CI / lint (push) Waiting to run
CI / build-onnx (push) Waiting to run
CI / benchmark (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
security / govulncheck (push) Waiting to run
security / trivy-fs (push) Waiting to run
fix: preserve ambiguous method-call evidence
2026-08-24 22:07:26 +02:00
Andrey Kumanyaev 4873acdf52 test(resolver): cover mixed ambiguous candidates 2026-08-24 21:29:23 +02:00
Andrey Kumanyaev cf78eb47f1 perf(analysis): batch unresolved dead-code evidence 2026-08-24 21:28:30 +02:00
Andrey Kumanyaev d7ce1a91c3 fix(analysis): skip incomplete dead-code candidates 2026-08-24 21:09:18 +02:00
Andrey Kumanyaev ae9c230563 fix(resolver): preserve ambiguous method calls 2026-08-24 21:07:34 +02:00
Andrew Kumanyaev 65dcc88b9d Merge pull request #658 from zzet/feat/localization-corroborated-terminality
CI / test (1.26, macos-latest) (push) Waiting to run
CI / test (1.26, ubuntu-latest) (push) Waiting to run
CI / build-windows (push) Waiting to run
CI / build-linux-static (push) Waiting to run
CI / lint (push) Waiting to run
CI / build-onnx (push) Waiting to run
CI / benchmark (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
security / govulncheck (push) Waiting to run
security / trivy-fs (push) Waiting to run
feat(localize): corroboration-gated terminality and an answer page that leads with its shape
2026-08-24 09:48:00 +02:00
Andrew Kumanyaev 812eb2d846 Merge pull request #656 from zzet/dependabot/github_actions/actions-minor-patch-0856f00088 2026-08-24 08:58:05 +02:00
Andrew Kumanyaev cb1337e082 Merge pull request #657 from zzet/dependabot/go_modules/go-minor-patch-f93759c639 2026-08-24 08:57:56 +02:00
dependabot[bot] 5a6d3a9670 deps(deps): bump the go-minor-patch group with 3 updates
Bumps the go-minor-patch group with 3 updates: [github.com/stretchr/testify](https://github.com/stretchr/testify), [github.com/yalue/onnxruntime_go](https://github.com/yalue/onnxruntime_go) and [modernc.org/sqlite](https://gitlab.com/cznic/sqlite).


Updates `github.com/stretchr/testify` from 1.11.1 to 1.12.1
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.11.1...v1.12.1)

Updates `github.com/yalue/onnxruntime_go` from 1.32.1 to 1.35.0
- [Commits](https://github.com/yalue/onnxruntime_go/compare/v1.32.1...v1.35.0)

Updates `modernc.org/sqlite` from 1.56.0 to 1.57.0
- [Changelog](https://gitlab.com/cznic/sqlite/blob/master/CHANGELOG.md)
- [Commits](https://gitlab.com/cznic/sqlite/compare/v1.56.0...v1.57.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: github.com/yalue/onnxruntime_go
  dependency-version: 1.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
- dependency-name: modernc.org/sqlite
  dependency-version: 1.57.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: go-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 05:19:45 +00:00
dependabot[bot] 906c4c5610 ci(deps): bump github/codeql-action/upload-sarif
Bumps the actions-minor-patch group with 1 update: [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `github/codeql-action/upload-sarif` from 4.37.7 to 4.37.8
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28)

---
updated-dependencies:
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.37.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-24 05:15:10 +00:00
Andrew Kumanyaev 7eae340e51 Merge pull request #646 from tiendungdev/fix/review-rulepack-native-path-join
CI / test (1.26, macos-latest) (push) Waiting to run
CI / test (1.26, ubuntu-latest) (push) Waiting to run
CI / build-windows (push) Waiting to run
CI / build-linux-static (push) Waiting to run
CI / lint (push) Waiting to run
CI / build-onnx (push) Waiting to run
CI / benchmark (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
security / govulncheck (push) Waiting to run
security / trivy-fs (push) Waiting to run
fix(review): join the changeset to native-separator graph paths
2026-08-23 22:03:34 +02:00
Tien Dung Dao 49b7e5ecd0 fix(analysis,mcp): convert repo-relative paths with no prefix, and hand CODEOWNERS the repo-relative spelling
Fourth review round on #646.

1. GraphKey returned early on an empty repo prefix, before filepath.FromSlash.

A standalone index carries no prefix but its keys still use native
separators, so a '/'-spelled forge or git path missed them on Windows and
get_pr_impact / suggest_reviewers(number=...) silently lost changed symbols
and reviewer signals. Only GraphKeyedPath returns unchanged now;
RepoRelativePath always converts, then takes the prefix when one applies.

2. CODEOWNERS received a graph key in ids mode.

relForRepo only strips an absolute repo root, so repo-a/pkg/auth/login.go
reached MatchFile with the prefix still on and a root-anchored rule such as
/pkg/auth/ matched nothing. matched_files reported the same spelling back to
the caller. Both now go through the new analysis.RepoRelPath, GraphKey's
inverse: it strips the prefix only for a GraphKeyedPath, because that is the
one domain that carries it by construction.

3. Tests.

- Empty-prefix multi-segment cases for GraphKey and JoinFileNodes. The
  previous a.go fixture has no separator, so it cannot fail either way.
- The ids CODEOWNERS fixture is now root-anchored (/pkg/auth/). The
  unanchored rule also matched the graph-keyed spelling and masked the bug.
- TestJoinHunksToSymbolsPrefixedDeleteAndRename covers the two hunk-less
  change kinds against a prefix-shadowed graph: the delete's Path and the
  rename's PreviousPath must resolve the nested old-side key and never the
  same-named shadow. Their existing tests use an empty prefix and so did not
  bind this contract.
- The Windows selector gains PrefixedDeleteAndRename.

Each fix is sabotage-verified separately: forcing the ids branch back to a
raw graph key leaves only the CODEOWNERS-less reviewers, passing the graph
key to MatchFile drops the codeowner signal entirely, and declaring the
vanished path GraphKeyedPath makes the delete/rename test miss both old-side
symbols.

Four fixtures needed the same correction as TestChangedSymbolsForFiles did
earlier: prToolsTestServer, conflictsTestServer and conflictsBudgetServer
hard-coded '/'-spelled graph keys while supplying the same strings as forge
paths, which describes an index a Windows daemon never writes. They now store
native and supply the forge spelling, which is the shape under test.

Windows: internal/mcp 27 -> 24 against main, failing test names diffed,
newly-broken set empty. internal/analysis passes clean. internal/review keeps
its four pre-existing failures. Lint: 6 staticcheck findings, pre-existing.
2026-08-24 00:24:12 +07:00
Tien Dung Dao a1a5b87fb2 fix(review): carry the ids path domain and normalize file risk before stripping
Third review round on #646.

1. suggest_reviewers(ids=...) double-prefixed graph-keyed paths.

resolveReviewerChangeset's three sources do not share a vocabulary: ids
returns graph node FilePaths, base (git) and number (forge) return
repo-relative paths. Forcing analysis.RepoRelativePath on all three
looked up repo-a/pkg/auth/login.go as repo-a/repo-a/pkg/auth/login.go, so
the ownership and co-change signals disappeared while CODEOWNERS - which
matches the repo-relative spelling - still answered and the tool looked
healthy.

The resolver now returns the domain with the files and the two lookups
use it. That also gives GraphKeyedPath its first production caller.

My round-two audit missed this: I read the call site's comment, which
says the paths are repo-relative, without walking all three producers
feeding it. The comment was true for two of them.

2. File risk stripped the prefix after filepath.Clean.

fromGraphKey called cleanPath first, and cleanPath ends in filepath.Clean,
so on Windows repo-a/repo-a\widget.go became repo-a\repo-a\widget.go and
the '/'-joined prefix no longer matched. Changed-symbol impact stayed on a
graph-prefixed row while findings and ChangedFiles produced a second
repo-relative row for the same file. Both domains now normalize through
graphpath.Norm before the strip and leave in the documented repo-relative
'/' spelling.

This also fixes three pre-existing Windows failures in internal/review:
TestRankFileRiskUsesImpact, TestRankFileRiskNormalizesRepoPrefix and
TestRankFileRiskExemptsTestFilesFromCoverageDebt. The package goes from
seven failures to four; the remaining four are untouched by this change.

3. Windows coverage.

The selector gains GraphKey, JoinFileNodes, ChangedSymbolsForFiles, both
prefix-shadow end-to-end tests, the new ids test and RankFileRisk, and
./internal/review joins the package list.

Tests. The new prefixed-graph ids test asserts all three reviewer signals
survive; the existing ids tests use an unprefixed graph where both domains
coincide, which is why they could not see the regression. Both
prefix-shadow end-to-end tests now require exactly one FileRisk row equal
to the changed file - the previous NotEqual(shadow) assertion passed even
when both erroneous rows were present.

Each fix is sabotage-verified separately: restoring the ids branch to
RepoRelativePath leaves only the CODEOWNERS reviewer, and restoring
cleanPath-before-strip produces exactly the two rows described above.

Also audited, and clean: every other JoinFileNodes / GraphKey caller.
changedSymbolsForFiles is fed only by forge PR file lists through
resolvePRFiles and prImpactForNumber, so its repo-relative domain is
correct. The only two remaining repoPrefix strips both normalize first and
both have an established domain.
2026-08-23 20:54:52 +07:00
Andrey Kumanyaev 12179c2ec5 fix(localize): corroboration requires novel aligned evidence, not overlap
A recovery returning rows the page already ranked was the recovery agreeing
with itself, and it terminalized sessions onto the same wrong siblings. A
result must now contribute at least one declaration the frozen baseline
lacks, aligned by file or task anchors; a pure subset spends its allowance
and re-arms recovery. Feeding only novel rows to the alignment test also
closes the door where a task-cited query terminalized on the strength of
the query alone.
2026-08-23 08:47:59 +02:00
Andrey Kumanyaev a4d0a6b8b8 feat(localize): lead the answer page with its shape and present clean identities
The answer-shape instruction sat in a trailing sentence agents ignore; it
now leads the page: two lines asking for the strongest candidates as bare
qualified identifiers and an explicit statement when unconfirmed. Displayed
identities drop synthetic spellings — constructor rows render as prose and
positional suffixes are trimmed only when they match the row's own line —
while IDs and every machine field stay exact.
2026-08-22 22:42:57 +02:00
Andrey Kumanyaev 67d8842f92 feat(localize): terminalize a recovery only when its result corroborates
A successful recovery whose handler recorded no graph-backed identity
stamped answer_ready, so an obedient agent stopped on an unproven page.
A recovery now terminalizes only when its rows land on the retained page
by symbol, by file, or by the task's anchor terms; an uncorroborated
success keeps what it surfaced and re-arms recovery, bounded by a
two-allowance ledger, and the spent ledger closes with the provisional
page that says so. Corroboration is judged against the page frozen when
recovery was armed, so a second recovery cannot corroborate itself. The
refinement directive names up to three ranked candidates and phrases the
miss as non-terminal instead of prescribing a single symbol that may be
wrong.
2026-08-22 22:42:57 +02:00
Andrew Kumanyaev 699f351e8a Merge pull request #653 from zzet/fix/issue-648-hxx
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / build-linux-static (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build-onnx (push) Has been cancelled
CI / benchmark (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
2026-08-22 15:21:36 +02:00
Tien Dung Dao a7ca67b099 fix(analysis,review): carry the path domain through the diff join and file risk
Second review round on #646.

1. JoinFileNodes resolved the wrong file.

It tried the raw key first and the prefixed key second, so a git-relative
"<prefix>/<rel>" resolved against the same-named top-level file — and when
that shadow did not exist the HasPrefix branch returned nil, never trying
the real key "<prefix>/<prefix>/<rel>". That contaminated
DiffResult.ChangedSymbols, which review and review_pack derive impact,
classification, risk, receipts and the verdict from.

The domain now travels with the path, as analysis.PathDomain, and a
repo-relative path is prefixed unconditionally. All five production call
sites were audited and every one is repo-relative: joinHunksToSymbols
(hunks and vanished paths), changedSymbolsForFiles (forge file list) and
suggest_reviewers (its own comment already said so). Nothing depended on
the mixed contract.

GraphKey also converts to native separators. The key's shape is
"<prefix>/" + the remainder as the indexing machine spells it (see
internal/graphpath), so a '/'-spelled git path missed every file below the
repo root on Windows — the shadow fixture proved it, resolving 0 nodes
before and 4 after.

JoinFilePath collapsed into GraphKey: with the domain explicit it was the
same function, and probing the store to pick between two candidate keys is
what made it ambiguous.

2. File risk repeated the collision.

rankFileRisk ran one normalizer over every input, stripping the repo
prefix from graph-keyed symbol paths and from repo-relative findings and
ChangedFiles alike, so a legitimate "repo-a/pkg/widget.go" became
"pkg/widget.go" and the risk landed on the shadow. Split by domain:
fromGraphKey strips, fromRepoRel does not.

3. Tests.

End-to-end review and review_pack tests drive MapGitDiff, JoinFileNodes,
rankFileRisk and the pack gates over a repo whose tree carries a top-level
directory named like the repo prefix. Both files carry the flagged source
at the base commit and only the nested one is edited — inside the function,
so the hunk overlaps a symbol — so a wrong-target join still produces
findings and only the attributed path separates the outcomes.

Fixture paths are deliberately flat: a nested spelling differs by
separator on Windows and would mask the defect there.

Existing tests that encoded the old tolerance were rewritten rather than
deleted: TestJoinFileNodes and TestChangedSymbolsForFiles_RepoPrefixJoin
now assert the shadow resolves to the nested file, and their fixtures
build graph keys the way the indexer does instead of hard-coding the
'/'-joined form.

changedPathDomain folded into analysis.PathDomain so one vocabulary spans
both packages.

Windows: internal/mcp 27 -> 24 against main, failing test names diffed,
newly-broken set empty. The seven internal/review failures are unchanged
and pre-existing (identical with this change stashed). Lint: 6 staticcheck
findings, also pre-existing.
2026-08-22 18:33:53 +07:00
Andrey Kumanyaev 97e34b66c0 fix(cpp): index hxx headers 2026-08-22 12:13:03 +02:00
Tien Dung Dao fdcf19d251 fix(review): carry the changed-path domain instead of inferring it
Addresses the review on #646.

reviewChangedGraphPaths inferred "this path is already graph-keyed" from
strings.HasPrefix(f, repoPrefix+"/"). The two domains overlap, so that is
not recoverable by inspection: in a repo whose tree carries a top-level
directory named like the repo prefix, `repo-a/pkg/widget.go` is a valid
git-relative path AND a valid graph key for a different file. The
inference skips the real key `repo-a/repo-a/pkg/widget.go`, so the
changed file is never scanned - and when a same-named `pkg/widget.go`
exists, that unchanged shadow is scanned in its place.

Make the domain explicit and travel with the data:

  changedPathsRepoRelative  git's spelling, relative to the working tree
  changedPathsGraphKeyed    the graph's "<prefix>/<rel>" node key

All four production callers pass DiffResult.ChangedFiles, which MapGitDiff
documents as keeping "the diff-relative paths (callers re-join them with
git pathspecs)", so they pass changedPathsRepoRelative and the prefix is
now applied unconditionally. Only the test covering a caller that already
holds node keys passes changedPathsGraphKeyed.

The Norm-based join and output normalization from the first revision are
unchanged; this only replaces the inference.

Regression test: a fixture carrying both pkg/widget.go and
repo-a/pkg/widget.go, only the nested path marked changed, asserting every
match reports the changed target. Both files carry the detector fixture,
so a wrong-target scan still returns matches and only the reported path
separates the outcomes - restoring the HasPrefix inference fails it with
"pkg/widget.go" is the unchanged shadow.

The ci.yml selector hunk is dropped: #652 removes that job outright.
2026-08-22 07:33:17 +07:00
Tien Dung Dao f447a10dc1 fix(review): join the changeset to native-separator graph paths
reviewRulepackMatches builds the changed-file key correctly and then
looks it up with filepath.Clean, which rewrites the one separator a graph
path keeps as '/': the repo prefix. Probed on windows/amd64:

    changed map key            "repo-a/pkg\widget.go"
    target.GraphPath           "repo-a/pkg\widget.go"   <- identical
    filepath.Clean(GraphPath)  "repo-a\pkg\widget.go"   <- prefix slash lost
    hit                        false

So `review` narrowed the rulepack to an empty target set and reported
zero findings on code its own detector bundle flags — the same false
clean the join was written to fix, reintroduced by the Clean.

Compare in graphpath.Norm form on both sides instead. Norm is the
canonical comparison spelling for a path that is "<prefix>/" plus a
native remainder, and it is filepath.ToSlash, so this is the identity on
POSIX.

Second half, same contract: reviewRepoRelPath feeds the `.gortex.yaml`
rule globs, rankFileRisk's row keys and the forge comment API, all of
which speak '/'. It returned `pkg\widget.go` on Windows, which matches no
glob and anchors no comment. Normalize there too.

Whole package on windows before/after: 27 -> 24 failures, exactly the
three rulepack tests flip, nothing else changes state. Each half is
separately load-bearing: reverting the join fails all three, reverting
the output normalization fails the two that assert m.File.

graphpath.Norm is deliberately a no-op on POSIX — a backslash is an
ordinary filename byte there — so no test can bind this on the
linux/macos matrix. The three names join the existing windows
native-separator step, whose package list already carries internal/mcp.
2026-08-21 15:42:55 +07:00
Andrew Kumanyaev c982cf5281 Merge pull request #645 from tiendungdev/fix/resolve-symbol-id-without-multiindexer
homebrew-cask / validate (push) Has been cancelled
init-smoke / dry-run (push) Has been cancelled
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / build-linux-static (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build-onnx (push) Has been cancelled
CI / benchmark (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
fix(mcp): keep path anchoring when the server has no multiIndexer
2026-08-21 09:56:46 +02:00
Tien Dung Dao 36424cf977 fix(mcp): keep path anchoring when the server has no multiIndexer
resolveSymbolID returned early on a nil multiIndexer, before the
graphRelID rung. Only the cwd rung needs the multi-repo index — it maps a
directory to a repo prefix — so the early return also discarded the path
anchoring that works without one.

Measured on windows/amd64 with the move/inline fixture, which builds its
Server through NewServer(...) with no MultiRepoOptions:

    multiIndexer == nil:              true
    GetNode("pkga/a.go::Foo"):        false
    resolveSymbolID("pkga/a.go::Foo") "pkga/a.go::Foo"   <- unchanged
    graphRelID("pkga/a.go::Foo")      "pkga\a.go::Foo"
    GetNode(graphRelID):              true                <- resolves

graphRelPath/graphPathSpelling already reconcile the spelling, and their
doc-comment names this exact failure: a forward-slash path is "the
spelling every agent writes", and without the rewrite it "missed every
node below the repo root on Windows". The rung was simply unreachable
for a server built this way.

Scope the guard to the cwd rung. The change is additive: the graphRelID
result is still gated on GetNode(rel) != nil, so it can only turn a miss
into a hit, never redirect a resolvable id.

Whole package on windows before/after: 40 -> 27 failures. Diffing the
failing test names, exactly the 13 TestMoveSymbol_* / TestInlineSymbol_*
cases flip and nothing else changes state.

The regression test uses an absolute-path id rather than the Windows
separator, so it exercises the rung on every platform and the
linux/macos matrix protects the fix without a new windows CI step.
2026-08-21 14:23:50 +07:00
Andrew Kumanyaev b09f89cc51 Merge pull request #644 from tiendungdev/fix/windows-separator-test-assumptions
test: stop three assertions assuming POSIX separators on native paths
2026-08-21 09:00:12 +02:00
Tien Dung Dao d3a2d3b8f0 test: stop three assertions assuming POSIX separators on native paths
TestFeedbackDir, TestParseDiffGitPaths and TestParseDiffLinesNewSide all
compare against a '/'-spelled path while the value under test is a native
filesystem path. On POSIX the two coincide, so the linux/macos matrix has
never seen it; on windows/amd64 all three fail:

    "\home\user\.cache\gortex\5887dcec1741_latest"
      does not contain ".cache/gortex"
    parseDiffGitPaths("diff --git a/pkg/foo.go b/pkg/foo.go")
      = "pkg\foo.go", want "pkg/foo.go"
    diffmap_test.go:63: expected new-side lines for pkg/foo.go

The production values are correct in each case and are left alone:

  - FeedbackDir is filepath.Join(cacheDir, key) and feeds os.Open, so it
    must carry native separators.
  - cleanDiffPath is filepath.Clean, and JoinFileNodes looks a diff path
    up as repoPrefix + "/" + path. Indexed paths are repoPrefix + '/' +
    the rest in native separators (see internal/graphpath), so the
    cleaned native form is exactly what the join needs. Making diff paths
    slash-canonical would break that lookup on Windows.

So the fix is on the assertion side: build the expectation with
filepath.Join / filepath.Clean instead of a literal. On POSIX both are
identity for these inputs, so the assertions keep exactly the strength
they had on the matrix that already runs them.

Fold the three into the existing native-separator windows step rather
than adding one: internal/analysis, internal/mcp, internal/resolver and
internal/graph/store_sqlite are already in that step's package list, and
internal/persistence is already compiled by the sidecar step above it.
2026-08-21 07:17:48 +07:00
Andrew Kumanyaev b8b13ca70d Merge pull request #641 from tiendungdev/fix/plan-lock-fixture-close-store 2026-08-21 00:17:59 +02:00
Andrew Kumanyaev 2ab495a5c5 Merge pull request #634 from pbednarcik/feat/lsp-defconfirm 2026-08-21 00:17:37 +02:00
Andrew Kumanyaev 59962ab99c Merge pull request #643 from zzet/fix/telemetry-consent-cache-flake 2026-08-21 00:16:26 +02:00
Andrey Kumanyaev ccac42e02f telemetry: drive the consent-cache TTL test off a stepped clock
TestCachedConsentResolver raced a real 20ms deadline: it primed the cache,
wrote a second consent file to disk, and then asserted the cached value had
not yet expired. On a loaded runner the SaveConsent write plus scheduling
delay between those two calls can exceed 20ms, the cache re-reads, and the
"within TTL the cached value should persist" assertion fails — which is what
took down the ubuntu leg of CI on main while macOS passed.

Give CachedConsentResolver an unexported constructor that takes a clock and
have the test step it across the TTL boundary (ttl-1, then exactly ttl), so
the boundary under test is asserted exactly instead of approximately. The
exported constructor keeps its signature and gains a small test covering the
real-clock wiring. Drops a 30ms sleep from the suite as a side effect.
2026-08-20 23:45:44 +02:00
pbednarcik 5dccca620b chore: retrigger CI (lint runner failed downloading its config schema) 2026-08-20 23:23:44 +02:00
pbednarcik 9f8e9e0563 lsp: count content-cache evictions under the lifecycle opt-out too
A sendOpens=false session still evicts to keep the cache bounded, but the
counter sat inside the lifecycle gate, so doc_evictions read zero exactly
where cache churn is the only signal left. Count the eviction where it
happens; didOpens / peak stay honestly zero for a lifecycle-off pass.
2026-08-20 23:08:54 +02:00
Andrew Kumanyaev 50495e9331 Merge pull request #642 from zzet/fix/release-module-tidiness 2026-08-20 23:06:33 +02:00
pbednarcik 9b185c2bd6 lsp: review polish — group only on the sweep path, rename shared normalizer
confirmGroups was computed unconditionally but the noHeavy path never
reads it (that path deliberately builds from the raw target list); move
the grouping into the sweep branch. And normalizeOpenDocs stopped being
open-docs-specific the moment the heavy override borrowed it — rename to
normalizeOnOff for the shared on/off vocabulary.
2026-08-20 22:59:56 +02:00
pbednarcik fd681fe561 lsp: fix a comment describing the definition pass as serial
The definition-rebind fallback fans out across maxParallel grouped by
call-site file — the serial loop the comment described is what this
branch replaced.
2026-08-20 22:54:39 +02:00
pbednarcik 3c3ef0e3ba docs: cover the heavy-request opt-out and GORTEX_LSP_HEAVY
The mode that changes what C# enrichment produces was the one knob with no
docs presence. Add a section to the enrichment cost model beside its
open-docs sibling: what NoHeavyRequests skips (references confirm,
incomingCalls, references-add, on-demand find_usages/get_callers
confirmation), what replaces it (definition-at-call-site confirms, the
dispatch synthesizer), the GORTEX_LSP_HEAVY override and why it is
env-only, and the warning that heavy=on is only safe on a csharp-ls build
carrying the upstream FindReferences leak fix. Also bring the
definition-rebind phase description up to date with the declared-member
verdict: add-not-rebind for devirtualization guesses.
2026-08-20 22:53:55 +02:00
pbednarcik 44d1e65f43 lsp: pin the declared-member arm on the heavy default path
The add-not-rebind verdict for dispatched call sites lives in the shared
definition-rebind pass, so it changes edge topology for every server, not
just the heavy-opt-out ones — but every behavior test for the arm set
noHeavyRequests. Cover the default path: references sweep runs and answers
empty, definition answers the declared member, and the site must keep its
devirtualization guess while gaining the compiler-proven edge (the old
behavior rebound the impl edge instead). The dispatched-call fixture moves
into a shared helper for both tests.
2026-08-20 22:45:19 +02:00
Andrey Kumanyaev ee1b77a142 release: stop running go mod tidy at release time, assert tidiness in CI
goreleaser ran `go mod tidy` as a before-hook inside the release container.
It took v0.63.8 down: the hook spent 4m49s walking the module graph and then
failed when proxy.golang.org returned HTTP/2 INTERNAL_ERROR on six unrelated
fetches. `go mod tidy` resolves the test dependencies of dependencies too, so
it touches far more of the network than a build needs, on the one path where
a flake costs a release.

The hook was also unsound in a quieter way. It MUTATES go.mod/go.sum, so a
drifted module set would have been silently rewritten inside the container
and the published binaries built against something no CI job had ever
compiled. A release-time rewrite is not a check.

Drop it — the config already skips tests here on the reasoning that the tag
is on a green commit, and the same argument applies.

Nothing verified module tidiness before this: no workflow, no Makefile
target. So the `lint` job gains a `go mod tidy` + `git diff --exit-code`
step, which fails the PR that introduces drift instead of papering over it
at tag time. main is already tidy, so the gate is green on landing.

Verified with the real toolchain, not by reading it:

  * `goreleaser build --snapshot` with a deliberately failing before-hook
    aborts at "running before hooks" in 0s, so hooks do execute in that mode;
    with `hooks: []` the stage never appears and the run goes straight from
    snapshotting to building. No default hook takes its place.
  * `goreleaser check` validates the config.
  * The CI gate passes on main unchanged, and fails with exit 1 (naming
    go.mod) against a commit carrying a deliberately untidy require line.
2026-08-20 21:56:42 +02:00
Tien Dung Dao 79777259ac test(store_sqlite): close the plan-lock fixture store so TempDir cleanup works on Windows
newPlanLockFixture opens an on-disk Store and never closes it. Windows
refuses to unlink an open file, so plan_lock.sqlite survives the test and
the enclosing t.TempDir() cleanup fails.

Measured on windows/amd64, go1.26.6, whole package before the change:

    --- FAIL: TestSweepPlanLocks
    --- FAIL: TestAdjacencyPlanLocksDuringBulkLoad
    --- FAIL: TestSweepPlanLockReceiverRebindBatch
    --- FAIL: TestPreparedStatementPlansNeverScanBigTables
    --- FAIL: TestSweepWarnPlanLocks
        testing.go:1464: TempDir RemoveAll cleanup: unlinkat
        ...\plan_lock.sqlite: The process cannot access the file because
        it is being used by another process.

Five failures, one cause, zero failing assertions — the fixture is the
only holder. After the change the package is ok, 147s, no failures.

Register the close with t.Cleanup after t.TempDir() so LIFO ordering
closes the database before the directory is removed. This is already the
convention at 160 of the 165 Open() sites in the package's tests.

Guard it on the windows runner. Every assertion in these tests is
platform-neutral: on linux/macos an unlinked-but-open file is removed
normally, so the whole set passes with or without the Close and the
matrix cannot protect the fixture from losing it again. Same reasoning,
and the same shape, as the sidecar-handle step added in #415.
2026-08-21 02:45:54 +07:00
Andrew Kumanyaev e8d60b80cc Merge pull request #637 from tiendungdev/fix/connectivity-note-removal-verdict
fix(connectivity): stop the note promising dead_code rows are safe to remove
2026-08-20 21:24:13 +02:00
Andrey Kumanyaev 2a33f3d200 Bump version to v0.63.8
Release / build-darwin (push) Has been cancelled
Release / release (push) Has been cancelled
Release / release-windows (push) Has been cancelled
Release / verify-signatures (push) Has been cancelled
Release / provenance (push) Has been cancelled
Release / virustotal (push) Has been cancelled
v0.63.8
2026-08-20 21:19:55 +02:00
Andrew Kumanyaev 5f9de46db9 Merge pull request #640 from zzet/fix/homebrew-cask-preflight-stanzas
release: fix the homebrew cask so it loads at all
2026-08-20 21:19:02 +02:00
Andrey Kumanyaev 9449485dcf release: fix the homebrew cask so it loads at all
The generated cask used `pre_install` / `post_install`. Those are formula
DSL methods; a cask has no such stanzas, so brew rejected the file at load
time with

  Error: Cask 'gortex' definition is invalid: undefined method 'pre_install'

Every brew command that touches the cask parses it, so `brew install` and
`brew upgrade` both failed for every macOS user on both arches, starting
with v0.63.7. Closes #639.

Two further defects made a straight rename insufficient:

  * The cask DSL's `system_command` is `SystemCommand.run!`, which raises on
    a non-zero exit. `gortex daemon status` exits 1 exactly when no daemon
    is reachable, so `next unless status.success?` could never run — brew
    would have aborted the install outright on any machine without a live
    daemon. The probe now passes `must_succeed: false`.

  * `preflight` was the wrong hook for stopping the daemon. On upgrade brew
    unlinks the old cask's binary (`start_upgrade` -> `uninstall_artifacts`)
    before installing the new cask's artifacts, so by the time a preflight
    block runs there is no gortex on disk to ask. The stop half could never
    fire on the path it was written for.

`postflight` handles both halves instead: after the new binary is linked,
probe for a daemon and, only if one answers, `daemon restart` — which stops
the old process (blocking until it exits, releasing the store lock) before
starting the new one, so a store migration still runs alone. A fresh install
and CI have no daemon answering and skip it.

The cask body moves out of the release.yml heredoc into
.github/homebrew/gortex.rb.tmpl, rendered by scripts/render-cask.sh. That
makes it reviewable as Ruby, removes the shell-expansion hazards of an
unquoted heredoc, and — the point — lets a real brew load it before
publication. The renderer also refuses a malformed sha256, a version with a
leading "v", or a placeholder that survived substitution. Rendered output is
byte-identical to the published cask apart from the hook block.

Nothing could have caught this: the file was valid Ruby, valid YAML, and the
tap has no CI, so the first machine to evaluate the cask was a user's.
scripts/validate-cask.sh renders the template with dummy values and loads it
through a real brew. It runs on every PR that touches the cask
(.github/workflows/homebrew-cask.yml) and again in build-darwin as the last
gate before the release job pushes to the tap. Both are macOS-only —
Homebrew on Linux cannot load casks. Verified by reintroducing `pre_install`
in the template: the validator fails with the exact error users reported.
2026-08-20 21:16:46 +02:00
Tien Dung Dao 1cf3a813cb fix(connectivity): stop the note promising dead_code rows are safe to remove
The `note` shipped on every connectivity_health response ended with
"genuinely unreachable code that is safe to remove", and the two
doc-comments feeding it said the same ("the code is unused and can be
removed", "a real finding to act on (delete it)").

That contradicts the shared vocabulary this analyzer says it stays in
lockstep with. ClassifyZeroEdge reaches the opposite conclusion from the
same zero-incoming shape: likely_unused is "evidence of no callers, not
proof: confirm with a text search for the symbol name before removing
it", and coverage_incomplete is "not as proof the symbol is unused or
safe to remove". FindDeadCode's own contract claims no more than "zero
incoming calls or references" — the removal verdict was added on top of
it, and only here.

The hedge is load-bearing, not stylistic: an ambiguous member call is
deliberately left unresolved rather than misattributed to a same-name
candidate, and a name-only match never earns a usage edge. Both leave a
live symbol with the exact zero-incoming shape dead_code reports on, so
a reader who trusts the note deletes working code. Measured on a 2.5k-file
Go repository, three of four dead_code rows were refuted by grep; two were
methods reached through a nested field selector.

Reword all three sites to state the signal and withhold the verdict. The
isolated-vs-dead-code distinction the note exists to draw is unchanged.

The new test pins the property rather than the prose: it builds the
dead-code shape, asserts the shared caveat for that same symbol still says
"not proof", then asserts the note keeps naming dead_code and its INCOMING
signal while carrying no removal verdict. Restoring the old sentence fails
it on the "safe to remove" assertion.
2026-08-21 01:23:00 +07:00
Andrew Kumanyaev a71cbedbb7 Merge pull request #615 from pbednarcik/feat/lsp-dispatch-predicate 2026-08-20 19:28:10 +02:00
Andrew Kumanyaev a94e056c7f Merge pull request #614 from pbednarcik/fix/mcp-tool-arg-guard 2026-08-20 19:27:18 +02:00
pbednarcik 83645eef86 lsp: confirmation preserves edge provenance so gate evidence cannot decay 2026-08-20 18:12:31 +02:00
pbednarcik 67ad30df4f mcp: pin warming-path rider survival too 2026-08-20 16:47:00 +02:00
pbednarcik daacb52e7d mcp: attach the warn rider after the response decorators 2026-08-20 16:39:58 +02:00
pbednarcik 26e9b620ed mcp: warn on unknown tool options by default, reject opt-in
Review follow-up on the dispatch arg guard. Rejecting by default broke
callers that work today: first-party surfaces inject undeclared keys
into arbitrary tools (the CLI pins format into every legacy-surface
frame, gortex call sets it for every non-facade tool, the HTTP bridge
merges ?format=), and generic layers honor max_bytes / max_tokens /
fields on every list-shaped response without any schema declaring them.

- Default is now warn: the handler runs and the result carries an
  _ignored_options rider naming the unknown keys and the valid options.
  GORTEX_TOOL_ARG_GUARD=reject restores the hard refusal; the off
  vocabulary aligns with the repo's boolean-env idiom (0/false/off/no).
- Response-shaping keys (format, fields, max_bytes, max_tokens, cursor)
  are exempt in both modes - dispatch demonstrably honors them, so
  flagging them is noise at best and a broken CLI at worst.
- The rider skips error results and is mirrored into structuredContent
  when the result carries a structured map - Content alone is invisible
  to structured readers.
- read_file declares its handler-honored max_chars; the subagent hook
  stops passing compact to smart_context (never a declared or honored
  option - the call worked only because unknown keys used to vanish).
- The RawInputSchema branch is gone: 0 shipped tools use raw schemas
  and the #597 stamp closes only structured ones, so it guarded a
  population of zero.
- End-to-end pins: the CLI's format-pinned frames for audit_health /
  verify_change / get_test_targets survive guarded dispatch untouched,
  and the #597 read_file(line_range) shape still refuses under reject
  while warning under the default.
2026-08-20 13:17:08 +02:00
pbednarcik ff76686c10 Merge main: re-base tools/list ceilings over the receipt growth
Both sides re-based the agent preset ceiling: main for the edit_file /
write_file receipt + idempotency options, this branch for the #597
additionalProperties:false stamp. Stack the two — measured on the merged
tree and re-based with ~300-400 bytes of slack. The core pre-diet
baseline moves for the same reason: main's growth had eaten most of the
diet slack and the stamp is contract, not creep.
2026-08-20 13:16:50 +02:00