Commit graph

5 commits

Author SHA1 Message Date
Gergő Magyar
999cc9f913
fix(deps): upgrade native tree-sitter to 0.25.1 (#3531)
* fix(deps): upgrade native tree-sitter to 0.25.1

* fix(tree-sitter): preserve sync loading and update verified capture baselines

Route C# scope queries through the synchronous native grammar loader so the compiled analyze module remains require-compatible on Node 22.18. Refresh only the audited empty-Python-module and renamed-PHP-fixture captures, and pin the runtime assertion to 0.25.1.

Validated 479 targeted tests, all 16 scope-capture benchmark cases, Python capture/import guards, compiled C# worker parsing, build, typecheck and formatting. Performance budgets are unchanged.

* fix(tree-sitter): refresh upstream vendors and preserve parser semantics

* fix(ci): refresh prebuilds and support Windows ARM

Refresh macOS, Linux ARM, and Windows x64 grammar binaries from the successful native CI builds. Pin node-gyp 12.4.0 so the Windows ARM jobs recognize Visual Studio 2026.

Use local Git filtering for E2E to avoid PR-files API timeouts on generated parser diffs.

Validation: 143 focused tests, Actionlint, and all 16 scope-capture benchmark checks passed. Full npm test run started; native database suites still running.

* fix(ci): refresh Windows ARM grammar prebuilds

Import all seven Windows ARM grammar artifacts from successful native build run 37965761760. Artifact filenames and SHA-256 hashes verified; all 42 native build jobs passed.

Validation: 31 prebuild coverage tests passed.

* fix(bench): stabilize scope capture timing guard

Warm each input size seven times and use 21 median samples while retaining isolated timing blocks. Yield between languages so native finalizers release allocations outside the measured sections. Keep all fixture sizes, fingerprints, scaling formulas, and budgets unchanged.

Validation: all 16 language guards passed with a 2 GB heap limit (1.38 deep-chain ratio; 1518 MB peak RSS). Linear and quadratic controls passed their expected guard outcomes. Formatting and graph change analysis passed. Full npm test is running without failures so far.

* fix(parser): reduce TypeScript capture traversal cost

* fix(test): await parser worker readiness before teardown

* fix(parser): load C# synchronously in workers

* fix(grpc): resolve native proto detections against their own file

With the native proto grammar active, sibling .proto files that declare
the same short service name under different packages tied in the
directory-based resolveProtoConflict heuristic and both providers were
dropped. A proto-origin detection already knows its defining file, so
use that file's entry before falling back to the heuristic.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci(packaging): assert native grammars load from the installed tarball

The packaged-install smoke only ran `gitnexus --version`, so a grammar
that failed to load from the published package (bundled runtime, patched
peer manifests, vendored prebuilds, C# sync loader, Protobuf wrapper)
would ship silently; the dev-tree ABI smoke also accepts vendored
grammars that report unavailable. Parse a sample for every registered
grammar plus the native Protobuf plugin from the installed package, on
the main thread and in a worker thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* ci(tree-sitter): track compatibility workarounds instead of 0.25 readiness

The daily readiness report still targeted tree-sitter@0.25.0, so after the
upgrade it would keep reporting "Blocked" and never close its tracking issue.
Repurpose it to track what we carry for the bundled runtime: derive the target
from the package.json pin, list each pack-time peer patch from
prepare-tree-sitter-bundle.cjs with its drop condition (bump, wait for an
upstream release, transitive, or intentionally pinned), and reword the
buckets. The workflow adopts the issue opened under the old title.

Also update the grammar-update-monitor header and auto-PR text, which still
described the 0.21.1 runtime and a tree-sitter-c hold.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(dart): assert both nested labels target the loop; fix Zig call-shape doc

The nested-label CFG test only jumped to `outer` and checked reachability,
which still passes when a jump is retargeted to the inner loop (its exit
reaches the outer header). Jump to both labels and assert the exact
`continue` edges; dropping the inner label now fails the test.

Update the Zig callable-flow comment: call_expression now wraps actuals in a
required `arguments:` field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:07:59 +03:00
mengkaka
4154b63131
feat(indexing): add Objective-C semantic indexing support (#3179)
* docs: add Objective-C fork provider notes

* feat(objective-c): add deterministic provider and grammar

* feat(objective-c): finalize provider MVP

* fix(objective-c): harden provider integration

* fix(objective-c): normalize bare macro markers

* docs(objective-c): integrate provider documentation

* fix(objective-c): harden resolution and header classification

* fix(objective-c): complete provider follow-ups

* fix: address Objective-C review follow-ups

* chore: format Objective-C grammar sources

* fix(objective-c): harden review follow-ups

* Address PR review feedback (#3179)

Keep Objective-C chunking and macro recovery aligned with the grammar, and stop Community MEMBER_OF edges from leaking into symbol context.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address follow-up review on ObjC chunking and language fallback.

Keep preprocessor directive text from changing file-scope brace depth, group real ivar nodes, skip header modifiers, and restore Rakefile/Gemfile detection through getLanguageFromFilename.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Parse Objective-C headers with the objc grammar in embeddings.

ensureAndParse and structural extraction now use the same content classifier as ingest, including method snippets from .h files, so Protocol/Category/Class chunks are not re-parsed as C++.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Keep file-scope macro elision off C line splices and @interface/@protocol/@implementation bodies, and attach ivar attributes to the following instance variable when chunking.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(bench): rebaseline Objective-C CSV emit

* feat(objective-c): add workspace resolution and linear emit benches

Plain .h files are classified as C++, so the ObjC pass could not
resolve #import of those headers. Load a C/C#-style workspace once
per pass, and keep protocol-candidate USES linear.

Refs #3179

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

- Compare LadybugDB labels() as a scalar when excluding Community MEMBER_OF edges.
- Walk superclass members, skip file-static C sibling defs, and ignore comments in ObjC header/macro scans.

Note: pre-existing failure in objective-c-provider integration (worker-pool ready timeout) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit Objective-C declaration captures so compilation-unit siblings can share
header/implementation bindings, and keep class vs protocol visibility groups
distinct.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit every comma-separated property/ivar declarator, and count @interface
after a multiline block comment closes so in-declaration macros stay intact.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ximengkai <ximengkai@soyoung.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:40:21 +00:00
Gergő Magyar
c5c4fbe43c
fix(zig): vendor tree-sitter-zig so npm i -g no longer warns on peers (#3180)
* fix(zig): vendor tree-sitter-zig so npm i -g no longer warns on peers

Published overrides do not apply to dependents, so the Zig optionalDependency
kept warning that tree-sitter@0.21.1 does not satisfy peerOptional ^0.22.1.
Load it from vendor/ like Dart/Kotlin/Swift instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(ci): add zig parse snippet to prebuild validate

The six zig prebuild jobs failed at "Validate the .node loads and parses"
because snippets[GRAMMAR] was undefined and tree-sitter threw
"Input must be a function".

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: drop Unreleased changelog note from the Zig vendor PR

CHANGELOG.md is owned by the release process, not individual PRs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33949409205

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33949616521

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33949829377

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33950025077

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33950220655

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33950400275

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33950607933

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33950882912

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33951170483

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33951386477

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33951624305

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33951813452

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33951998309

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33952225172

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33952524393

* fix(ci): stop native prebuild rebuild loops

PR path filters and source checks see the cumulative diff, so generated binaries kept rebuilding the original source change. Skip output-only synchronize events using their exact before/head range, failing closed when Git cannot compare it. Exercise the workflow against real commit histories, including multi-commit source pushes and merge-ref drift.

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33953226606

* fix: address Zig PR review feedback (#3180)

Check for the vendored package without loading its native binding so a
broken installed Zig grammar fails the parsing test instead of skipping.
Match the optional child descriptor in the Zig metadata declaration and
include Zig in the two optional/vendored grammar comments.

Validation: 159 targeted tests, TypeScript, metadata type fixture, and
formatting passed. Injected native-load failure now fails instead of
skipping; explicit Zig opt-out still skips.

* chore(vendor): rebuild native prebuilds (tree-sitter-zig)

Built by https://github.com/abhigyanpatwari/GitNexus/actions/runs/33956342308

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: gitnexus-release-bot[bot] <gitnexus-release-bot[bot]@users.noreply.github.com>
2026-09-05 10:35:33 +01:00
glier
d27fd11c4b
fix(lang-kotlin): support fun interface extraction via tree-sitter-kotlin re-vendor (#2271)
* fix(lang-kotlin): support `fun interface` extraction via tree-sitter-kotlin re-vendor

Vendored tree-sitter-kotlin@0.3.8 (fwcd) parsed `fun interface Foo` as an
ERROR node and dropped the declaration plus its abstract method, so functional
(SAM) interfaces were never extracted. The fix landed upstream in
fwcd/tree-sitter-kotlin#169 (closes #87), merged to main 2025-04-25, but is not
in any npm release (latest tag 0.3.8; main is the unreleased 0.4.0).

Re-vendor the grammar from the unreleased fwcd main commit c8ac3d26:
- refresh src/{parser.c,scanner.c,node-types.json,tree_sitter/*.h} and
  bindings/node/index.js; bump the vendor version 0.3.8 -> 0.4.0; record the
  pinned SHA + rationale in _vendoredBy and the vendor README.
- switch the prebuild workflow's kotlin registry kind 'npm' -> 'vendored' (the
  fix is unreleased on npm, so prebuilds must build from the vendored C source,
  like swift/dart/proto).
- add a hold to .github/vendored-grammars.json so the weekly auto-update
  monitor does not strict-inequality-revert the pin to the broken npm 0.3.8
  (isNewer compares 0.3.8 != 0.4.0).
- add 3 regression tests + a fixture asserting fun interfaces extract as
  Interface nodes with their abstract methods, and that plain-interface
  heritage still resolves.

Existing KOTLIN_QUERIES need no change: the new grammar models `fun interface`
as a class_declaration with an "interface" keyword child (plus an extra "fun"
modifier child), which the existing interface rule already matches. Full Kotlin
suite green against the new grammar (300 unit/cfg/resolver + 233 integration).

NOTE: prebuilds/ are intentionally not in this commit. The version bump
auto-triggers .github/workflows/build-tree-sitter-prebuilds.yml, which
regenerates all 6 platform binaries from the vendored source in a separate PR.
Until that lands, CI loads the committed 0.3.8 prebuild, so the new kotlin
tests are red and the grammar change is inert at runtime. Merge the prebuild PR
first or together.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(ci): count kotlin's vendored hold as a 0.25-readiness blocker

The kotlin `hold` added in the previous commit makes the tree-sitter
upgrade-readiness report count it as a blocker — the report treats every
held vendored grammar as frozen below a runtime upgrade (same as the
intentionally-pinned tree-sitter-cpp and the ABI-held tree-sitter-c),
"in-range ABI or not". So the report's blocker count goes 2 -> 3.

Update the hardcoded count in
test_issue_update_summary_regex_matches_current_report (and the
_render_report docstring) accordingly — exactly as that test instructs:
"if a grammar is added/removed or a pin/hold changes, update the expected
counts". kotlin's ABI (14) is in range; the hold is what flags it, with the
reason recorded in .github/vendored-grammars.json.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(ci): refresh kotlin baselines for the grammar bump

Two committed baselines pinned the pre-bump kotlin state and broke when the
grammar was re-vendored (0.3.8 -> 0.4.0):

- cli-commands.test.ts pinned the vendored kotlin package version at 0.3.8 ->
  update to 0.4.0.
- bench/scope-capture/baselines.json: the new kotlin-fun-interface fixture joins
  the lang-resolution/kotlin-* corpus AND the new grammar parses `fun interface`
  as a class_declaration (not an ERROR node), so the capture fingerprint drifts.
  Rebaselined to the NEW grammar's fingerprint (verified by building the vendored
  parser.c against tree-sitter@0.21.1 and running measure.mjs --check); scaling
  ~0.83 (linear).

Like the fun-interface integration tests, the scope-capture --check passes only
once the regenerated prebuilds land; until then CI loads the committed 0.3.8
binary, so it stays red.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(prebuilds): rebuild + commit grammar prebuilds into the PR on vendored-source change

build-tree-sitter-prebuilds.yml previously rebuilt a grammar's native prebuilds
only when its package.json VERSION bumped, and delivered them via a separate bot
PR. Now any change to the vendored grammar source re-cuts the prebuilds and they
ride into the same PR.

- Trigger on any build-affecting change under gitnexus/vendor/tree-sitter-*/**
  (parser.c, grammar.js, binding.gyp, scanner, bindings), not just version bumps.
  The prebuilds/ subtree is negated in the paths filter AND excluded from the
  guard's source diff, so the bot's own prebuild commit can never retrigger the
  workflow (no build -> commit -> build loop).
- The guard builds a grammar when its recorded version changed OR its vendored
  source changed vs the PR base.
- Same-repo PRs get the rebuilt prebuilds committed straight onto their own head
  branch (included in the SAME PR) via a non-force push that only adds a commit
  on top of head. Manual dispatch still opens a fresh chore/ PR; fork PRs stay
  artifacts-only (a bot cannot push into a fork branch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci(prebuilds): deliver rebuilt prebuilds to fork PRs via a trusted workflow_run stage

A fork PR's producer run has a read-only token and no secrets, so it can build and
validate the prebuilds but can't commit them. Add the safe two-stage handoff that
mirrors the pr-autofix producer/publish split.

- build-tree-sitter-prebuilds.yml (untrusted producer): on a fork PR, upload a
  pr-meta artifact (schema, pr_number, head_sha, head_ref, head_repo, base_repo)
  alongside the prebuild artifacts. Values flow through env + jq, never
  interpolated into a shell.
- commit-fork-prebuilds.yml (trusted, workflow_run): downloads ONLY the artifacts
  (never executes fork code — it checks out the pinned HEAD SHA solely to add
  files), allowlist-validates every metadata field, cross-checks identity against
  the workflow_run authority (head_sha / head_repo / pr_number, via
  commits/{sha}/pulls for forks), then pushes the prebuilds onto the fork head
  branch with --force-with-lease + http.extraheader auth. No PAT: this works when
  the contributor left "Allow edits by maintainers" on; on push failure it posts a
  sticky comment telling them to enable it or commit the downloaded artifacts.

zizmor: allowlist commit-fork-prebuilds.yml's workflow_run dangerous-trigger with
the documented mitigation, matching the existing ci-report / pr-autofix entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(vendor): rebuild tree-sitter-kotlin prebuilds for the re-vendored fun-interface grammar

The fun-interface re-vendor changed vendor/tree-sitter-kotlin source but left main's
old (0.3.8) prebuilds in place, so all 6 platform binaries were stale relative to the
new parser. Replace them with the freshly cross-built + ABI-validated binaries from
build-tree-sitter-prebuilds run 28010841458 — each .node was require()-loaded and
parsed a snippet on its target platform-arch before upload.

This is the manual equivalent of the commit-fork-prebuilds.yml delivery, which can't
run for this fork PR until it lands on main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(lang-kotlin): read extension-function receiverType from the re-vendored grammar's `receiver` field

The fun-interface re-vendor changed the kotlin AST: an extension function's
receiver is now a `receiver_type` exposed via a named `receiver` field, where the
old grammar emitted a bare user_type before the name. extractReceiverType only
matched the old shape, so receiverType came back null
(method-extraction.test.ts > Kotlin MethodExtractor > extracts receiverType).
Prefer the `receiver` field (unwrapping it), and keep the old child-scan — now
also recognizing `receiver_type` — as a fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-06-23 10:01:28 +01:00
Gergő Magyar
96dc368d96
fix(ci): align tree-sitter readiness + grammar-update workflows on a shared manifest (#858) (#2187)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* chore(ci): add shared vendored-grammars manifest; monitor reads it

.github/vendored-grammars.json is the single source of truth for the vendored
tree-sitter grammars (c/swift/kotlin/dart/proto): name, upstream coords, and
policy holds. update-vendored-grammars.mjs now builds its GRAMMARS map from the
manifest (behavior-preserving — same exported shape). Adds manifest-agreement
tests so the loader can't silently skew from the file.

* fix(ci): classify vendored grammars from manifest, drop bare "?" (#858)

The readiness report decided "is this vendored?" via is_vendored_pin (a file:
package.json spec) — but the 5 vendored grammars aren't in package.json, so they
were misrouted through the npm path and rendered bare "?" for ABI (read from an
empty node_modules), plus a spurious "? (fetch failed)" for github-only proto.

Now vendored grammars are classified by membership in the shared manifest and
their ABI is read from gitnexus/vendor/<name>/src/parser.c (always in a
checkout). github-only vendored grammars skip the npm peer-dep fetch; the
tree-sitter-c hold is surfaced from the manifest (held, not plain "Ready"); and
every remaining unintrospectable value renders a labeled token, never a bare
"?". --assert-current now covers the vendored grammars too instead of skipping
them. Adds a stdlib unittest suite incl. a manifest⇄vendor-dir consistency
guard.

* docs(ci): document the shared vendored-grammars manifest

Both tree-sitter workflow headers now point at .github/vendored-grammars.json as
the shared source of truth; the readiness workflow gains a PR-path trigger on the
manifest + test, and runs the readiness unit tests on validation events.
CONTRIBUTING.md documents the manifest contract under CI automation contracts.

* fix(review): apply autofix feedback

- Guard manifest reads in both scripts with a clear error (was an opaque
  module-import traceback that crashed the script and test collection).
- Never render a bare "?": relabel the npm-path ABI/version/peer sentinels and
  the vendored upstream-ABI miss to labeled tokens; the report is now ?-free
  regardless of node_modules/network, and the test is hermetic.
- Add a VENDORED_NAMES ⊆ GRAMMARS guard + manifest-missing error test.
- Drop now-dead is_vendored_pin/is_vendored/_(vendored)_.
- Compose held + out-of-range vendored blocker reasons instead of overwriting.
- Reword the shared-manifest docs to not over-claim shared upstream coords.

* fix(ci): apply root prettier formatting to mjs + ts test

The quality/format gate runs root `prettier --check .` (printWidth 100, the
gitnexus-local config differs and falsely passed locally).

* test(ci): make both tree-sitter scripts testable offline

The scripts hit live npm/GitHub, which makes the report run flaky and the
monitor's detect/apply logic untestable. Add hermetic seams:

- readiness: --offline flag (+ GITNEXUS_TS_READINESS_OFFLINE env) no-ops the npm
  registry + upstream fetches; the report renders deterministically (vendored
  ABIs from the repo, npm columns marked 'offline', no bare '?'). 3 tests assert
  an offline run touches ZERO network (urlopen patched to raise).
- monitor: detect() and apply() accept injected deps (vendoredVersion/
  resolveUpstream/fetchSource/readAbi) so the newer/ABI/hold gating runs offline
  with fixtures; apply gains --dry-run (validates but writes nothing). 6 tests
  cover newer/same-version/held-c/ABI-15/applicable + a no-mutation dry-run.

* fix(review): keep --assert-current hermetic + harden the no-bare-? invariant

Tri-review findings (PR #2187):
- P2 REGRESSION: --assert-current (documented 'hermetic and offline', run in CI
  without --offline) routed the 5 vendored grammars through vendored_drift_summary,
  which fetches upstream parser.c + commit sha — 10 discarded network calls per run.
  Fix: read the vendored ABI locally via a new vendored_abi_from_repo() helper (also
  used by vendored_drift_summary). Now verifiably network-free.
- Unify the upstream-ABI miss sentinel: prose said 'n/a (generated at build)' while
  the matrix said 'n/a' — and 'generated at build' is a wrong cause (swift HAS a
  committed parser.c). Both now render neutral 'n/a'.
- Fix the stale assert_current docstring claiming swift is prebuilt-only/no parser.c.
- Guard the last latent bare-? path (vendor package.json missing 'version').

Tests: AssertCurrent (network-free guard + out-of-range via the new injection
point), malformed-JSON manifest, detect() error-path, explicit npm/github
undefined assertions. 17 Python + 15 vitest, all hermetic.

* fix(review): use a single unittest import style (CodeQL 753)

CodeQL py/import-and-import-from flagged `import unittest` + `from unittest
import mock`. Collapse to `from unittest import TestCase, main, mock`.

* fix(review): explicit raise in _matrix_row (CodeQL 754)

CodeQL py/mixed-returns flagged the implicit fall-through after self.fail()
(which it doesn't model as NoReturn). End with an explicit raise AssertionError.

* test(review): replace non-null assertions with a must() guard

@typescript-eslint/no-non-null-assertion flagged 4 `!` operators. Add a
narrowing must<T>(value, message) helper (throws on undefined) and a named
baseResolveUpstream, removing every non-null assertion.

* fix(review): unguessable heredoc delimiter for the report output

The report embeds the manifest `hold` field (fork-PR-editable); a fixed
DRIFT_EOF delimiter in a hold value could close the $GITHUB_OUTPUT heredoc
early and inject output keys. Use DRIFT_EOF_$(openssl rand -hex 16) — a value
the report cannot contain. (Randomized delimiter over base64: keeps REPORT raw
markdown, no consumer-side decode.)

* fix(review): scope issues:write to scheduled runs (two-job split)

GitHub Actions has no step-level permissions, so the only way to keep PR runs
(incl. forks) from receiving `issues: write` is to split the job. A `report`
job (contents:read, all events) renders the report + the PR `:⚠️:` and
exposes report/exit_code as job outputs; a schedule-only `upsert-issue` job
(needs: report, issues:write, no checkout) consumes them for the issue upsert +
close. The 'Check upgrade readiness' check name is preserved.

* fix(review): launder npm-version '?' in disposition prose

The disposition bucket prose interpolated r['npm_version'] raw, so a successful
200 npm /latest response lacking a 'version' key would render a bare '?' (the
matrix cell already laundered it). Add npm_version_label ('unknown' for '?') and
use it in all five bucket renderers. Test a version-less npm response.

* refactor(review): load_vendored_manifest returns only the consumed 'hold'

The readiness script reads only the grammar names + 'hold'; the 'key' and
'upstream' fields were phantom data (upstream-drift coords live in the script's
own GRAMMARS map). Narrow the return to {hold}.

* fix(review): unify detect()/apply() 'newer' check for github grammars

detect() compared the bare sha7 while apply() compared up.version (the full
<base>-g<sha7> provenance string apply() also writes). After the bot re-vendored
a github grammar once, detect() reported a perpetual false 'update available'
while apply() correctly saw 'already current' — a noisy job summary + wasted
--apply subprocess (the PR-exists guard absorbed it before any duplicate PR).
Extract a shared isNewer(up, have) helper used by both. Tests cover equal-
provenance (false), first-vendoring plain-version (true, not suppressed), and
sha-advanced (true). Coupled with U12 (the detect⇄apply agreement assertion
lives there once apply()'s not-newer path returns instead of process.exit).

* test(review): cover main()'s out-of-range + prebuilt-only vendored ABI branches

main()'s vendored-ABI classification reads through vendored_abi_from_repo (the
local-read seam --assert-current uses), so patching it drives the
'Vendored (ABI out of range)' blocker branch and the prebuilt-only (vendored_abi
None → 'prebuilt' cell, not '?') branch — neither reachable today since all 5
vendor dirs ship parser.c at ABI 14.

* test(review): monitor-side manifest⇄vendor-dir consistency guard

Mirror the Python consistency guard on the monitor side — the monitor consumes
the same manifest and is the side that WRITES files from manifest `name`, so
manifest/vendor-dir drift must fail CI here too.

* fix(review): validate grammar names at manifest load (path-traversal guard)

The manifest `name` is joined into gitnexus/vendor/<name> paths in both scripts
(and apply() WRITES there), so reject any name not matching tree-sitter-[a-z0-9-]+
at the single load chokepoint — defense-in-depth even though the live trust
boundary already prevents exploitation. loadManifestGrammars gains an injectable
`raw` arg + export for testing; tests reject a '../etc' name in both scripts.

* refactor(review): apply() throws ApplyExit; CLI maps to exit codes

apply()'s 4 process.exit calls killed the vitest worker, blocking in-process
tests of its error branches. Replace them with a thrown ApplyExit{code}; the
not-newer (already-current) path returns `have` instead of exit(0). The isMain
CLI block try/catches and maps ApplyExit.code → process.exit, so the monitor's
subprocess contract (exit 0/2/3) is byte-identical (verified via subprocess
smoke). Tests cover unknown-key=2, held=3, ABI-reject=3, and not-newer (returns
current, no throw, no write).

* refactor(review): extract vendored render helper; trim docstrings (<1000 lines)

Extract the 'Vendored parsers' prose render into _render_vendored_section() so
main() coordinates named phases rather than inlining a ~450-line monolith, and
condense the most verbose docstrings/comments. The script drops from 1092 to 999
lines (under the 1000 bar the maintainability review flagged). Behavior-preserving:
the deterministic --offline render is byte-identical before/after (verified
in-place), --assert-current still passes, and the full unit suite is green.

* fix(review): row-diff regex captures only the Status cell

The change-detection regex captured the whole row tail as group 2, so any
non-status cell drift (e.g. an upstream-ABI bump) emitted a false-positive
'change' line. Capture only the Status cell ([^|]+? before the final |$).
The workflow parseRows regex and the Python _ROW_DIFF_RE stay byte-identical;
the stability test now asserts group 2 is the status string (e.g. c →
'Vendored — held') and contains no pipe.

* fix(ci): hoist intro string out of the list literal (CodeQL 755)

The U13 extraction moved the 'Vendored parsers' intro paragraph (implicitly
concatenated string literals) INTO a list literal, tripping CodeQL
py/implicit-string-concatenation-in-list (reads as a possibly-missing comma
between elements). Hoist it into a parenthesized `intro` variable. Render is
byte-identical.
2026-06-13 16:15:49 +01:00