* fix(deps): pin tree-sitter-c/cpp to fix Windows segfault (#1242)
`tree-sitter-c@0.23.2` ships native prebuilds compiled against tree-sitter
ABI 14 (tree-sitter-cli >=0.24), while GitNexus is pinned to the
tree-sitter@0.21.1 JS runtime. On Windows the JS runtime hits
`Cannot read properties of undefined (reading '161')` inside
`unmarshalNode` and a native segfault in the parse-worker pipeline on
real C codebases (e.g. STM32 headers from the issue reporter).
Two coordinated registry pins fix the root cause without any override
gymnastics or vendoring:
- `tree-sitter-c` -> `0.21.4` (last release built against the
tree-sitter@0.21 ABI; declared peer `^0.21.0`).
- `tree-sitter-cpp` -> `0.23.2` (last 0.23.x release before
tree-sitter-cpp added a runtime dep on the broken-ABI
`tree-sitter-c@^0.23.1`; pinning here lets us drop the previous
global override entirely).
`npm ls tree-sitter-c` is now clean: single deduped 0.21.4, no
`overridden` annotations, no nested copy.
Parser loader collapsed to one declarative table:
- One `SOURCES` map with `{ load, unavailableNote, optional? }` rows
for every grammar including TSX. Adding/removing a grammar is one
entry; `unavailableNote` is mandatory and the type checker enforces
it, so failures are never silent and never generic.
- Single `loadGrammar(key)` does lazy require + cache + per-failure
classification. Required failures `console.error` the note and
rethrow the original (preserves stack); optional failures
`console.warn` and report the language as Unsupported. One
warn-once `Set` deduplicates per language key.
- The previous bespoke `warnCUnavailable` + `cWarningEmitted` state
and 4 conditional spreads in the language map are gone.
Per-grammar `unavailableNote` strings name the package, list the most
likely failure mode for that grammar, and link the relevant tracking
issue (#1013, #1125, #1130, #1242) where applicable.
Tests: new `C parser ABI compatibility (#1242)` block under
parser-loader.test.ts exercises the actual failure paths
(non-trivial parse + tree walk + Query.captures + TreeCursor
descent). The original report's `unmarshalNode` crash sits on
exactly the traversal hot path these tests now cover.
Validation:
- npx tsc --noEmit: clean
- npx vitest run test/unit: 4808 passed, 10 skipped
- npx vitest run test/integration/resolvers/cpp.test.ts: 133/133
- minimal C parse + walk + query + cursor verified manually under
tree-sitter@0.21.1 + tree-sitter-c@0.21.4 on Win11 x64 / Node 22
Closes#1242. Does not unblock the broader tree-sitter@0.25 upgrade
tracked in #858.
Made-with: Cursor
* chore(ci): redesign tree-sitter upgrade-readiness report (#858)
The daily script that owns the body of #858 used to dump one giant
matrix and leave a human to figure out which grammars are actually
ready to bump. After pinning `tree-sitter-c@0.21.4` and
`tree-sitter-cpp@0.23.2` for #1242, several rows in that matrix now
look like regressions when in fact they are deliberate. The report
now classifies each grammar instead of just listing them.
What changed in `check-tree-sitter-upgrade-readiness.py`:
- New `INTENTIONAL_PINS` table documents grammars deliberately held
below `npm latest`, with a one-line rationale and a tracking issue
per row (#1242 for C and C++, #1013 for C#). The script reads pins
straight from `gitnexus/package.json` so a future bump cannot
drift away from this report.
- New `_classify_grammar(...)` produces one primary disposition per
grammar: Ready for 0.25 / Intentionally pinned / Waiting on
upstream npm release / Blocked on upstream / Could not check.
The dispositions drive the report layout.
- New `vendored_drift_summary(...)` covers all three vendored
parsers (`tree-sitter-proto`, `tree-sitter-dart`,
`tree-sitter-swift`) uniformly: ABI from `parser.c` when present,
upstream npm + GitHub status, and the rationale extracted from
each vendor's `_vendoredBy` field. Prebuilt-only vendors
(Swift today) report `ABI 'prebuilt'` instead of `None`.
- Report layout: top-of-page TL;DR + counts, an actionable
"What you can do today" section, then one section per
disposition bucket, then a dedicated "Vendored parsers"
section. The original raw matrix is preserved inside a
collapsible `<details>` block so the row-diff bot that watches
this issue still has stable input.
- `sys.stdout.reconfigure(encoding="utf-8")` so the workflow no
longer crashes on Windows when the report contains arrows or
em-dashes.
No workflow / cron changes; the daily job posts the new body the
next time it runs. #858 itself was updated by hand in the meantime
to keep the tracker readable.
Made-with: Cursor
* fix(parser-loader): log C grammar load failures at error severity (#1242)
Addresses review feedback on #1243.
`tree-sitter-c` is in `dependencies` (not `optionalDependencies`) so a
load failure on a supported platform always indicates a real install
problem the user needs to see — corrupted node_modules, unsupported
Node version, or an ABI mismatch with the bundled runtime. Previously
the optional-grammar machinery downgraded that to `console.warn`,
which can be missed in long log streams and silently drops C analysis
for an entire repo.
Decouples log severity from throw behavior:
- `GrammarSource.severity?: 'warn' | 'error'` is a new optional field
that overrides the default log level for a load failure. Default is
`error` for required grammars and `warn` for optional ones, matching
the prior behavior for every existing row.
- `LoadResult` carries the resolved severity through `loadGrammar` so
`logFailure` no longer derives it from `fatal`.
- `tree-sitter-c` row sets `optional: true, severity: 'error'`. The
pipeline still degrades gracefully (callers see Unsupported instead
of a thrown error), but the diagnostic is loud and the
`unavailableNote` now spells out what to try first
(`npm rebuild tree-sitter-c`, reinstall) and links the tracker.
No test changes needed: `parser-loader.test.ts` exercises behavior on
the success path and on optional-failure dispatch; severity is a
display-only concern routed through `console.error` vs `console.warn`,
which the existing tests don't assert on.
Made-with: Cursor
* fix(ci): treat intentional pins as 0.25 blockers in readiness report
Addresses review feedback on #1243.
`_classify_grammar` returned bucket `intentional` before checking
`target_compat`, and the per-grammar status loop only added a row to
`blockers` when npm-latest was incompatible with the target runtime.
The combination meant: if every other grammar resolved tomorrow but we
were still holding `tree-sitter-c@0.21.4` and `tree-sitter-cpp@0.23.2`
(both incompatible with `tree-sitter@0.25.x`), the script would emit
"**Ready** — all grammars are 0.25-compatible" and mislead maintainers
into thinking the runtime upgrade was unblocked.
Fix:
- The status loop now adds an entry to `blockers` whenever a grammar
is in `INTENTIONAL_PINS`, regardless of npm-latest's peer dep. The
blocker message names the pinned spec, embeds the rationale from
`INTENTIONAL_PINS`, and tells the reader the pin must be lifted
before the target runtime upgrade. When the pin is removed (entry
deleted from `INTENTIONAL_PINS`), the grammar resumes standard
classification on the next run.
- `bump_now` now excludes intentional pins so they never show up in
the "What you can do today" section. Bumping an intentional pin
requires a deliberate edit to both `INTENTIONAL_PINS` and
`package.json`, not a one-line dependency bump.
Verified locally: TL;DR now reports 8 blockers (6 upstream + 2
intentional) where it previously reported 6, and the verdict
correctly remains **Blocked** even in the hypothetical future where
all upstream blockers clear.
Made-with: Cursor
* chore(deps): add tree-sitter aware Dependabot config and drift monitoring
Two things Dependabot cannot see on its own:
1. ABI consistency. The tree-sitter runtime supports a known range of
grammar ABIs. When a grammar bumps past that range, require() silently
fails and fallback paths mask the regression in test coverage.
2. Vendored upstream drift. vendor/tree-sitter-proto is a snapshot of
coder3101/tree-sitter-proto regenerated against a pinned cli version.
Upstream keeps moving. Nothing notices until a maintainer remembers to
look.
Dependabot configuration
- Added npm ecosystems for gitnexus, gitnexus-web, gitnexus-shared.
- Grouped all tree-sitter-* grammar bumps into one PR (ecosystem moves in
lockstep, one PR per grammar is noise).
- Pinned the tree-sitter runtime itself. Bumping 0.21 to 0.22+ changes
which grammar ABIs load and requires coordinated updates to the
vendored proto grammar. That stays a deliberate human decision.
- Pinned tree-sitter-cli for the same reason (it controls which ABI
vendor/tree-sitter-proto/src/parser.c emits when regenerated).
Drift check (.github/scripts/check-tree-sitter-drift.py)
- Reads the tree-sitter runtime version from gitnexus/package.json.
- Walks every installed tree-sitter-* grammar plus the vendored proto
and reports its LANGUAGE_VERSION against the runtime's supported ABI
range (table maintained in the script; extend when bumping runtime).
- Fetches coder3101/tree-sitter-proto main parser.c and compares byte
for byte to the vendored copy. Reports the upstream HEAD short SHA
and the upstream ABI so a maintainer can act.
- Prints a Markdown report; exits 0 when everything is in range and
matches upstream, 1 otherwise.
- Stdlib only, no external deps.
Drift workflow (.github/workflows/tree-sitter-drift-check.yml)
- Runs weekly (Mondays 09:00 UTC) to match Dependabot's cadence.
- Also runs on PRs that touch the script or workflow itself, where it
fails the PR check on drift so the drift gate cannot land broken.
- On scheduled runs with drift, opens or updates a single tracking
issue labeled tree-sitter-drift. On scheduled runs that come back
clean, closes the open tracking issue (if any) with a comment.
* refactor(deps): rewrite drift check as tree-sitter 0.25 upgrade readiness monitor
Replace the ABI drift pass/fail gate with a daily upgrade readiness
dashboard that tracks peer-dep compatibility of all 14 grammars with
tree-sitter@0.25.0 and reports which are ready, unreleased, or blocking.
Key changes:
- Rename drift-check → upgrade-readiness (script, workflow, job id)
- Fix P0: pass report via env var, not ${{ }} template interpolation
- Fix P1: npm fetch failure now adds a blocker instead of false-green
- Fix P1: pass GITHUB_TOKEN for authenticated GitHub API calls
- Switch Dependabot to daily for tree-sitter grammars
- Use dict for blockers (no prefix collision), derive TARGET_RUNTIME
constant, reuse GRAMMARS parser_path, normalize CRLF in comparisons
- Reduce per-call HTTP timeout from 15s to 8s for workflow budget
- PR runs warn on blockers instead of hard-failing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(ci): remove global-upgrade smoke test workflow
The ci-global-upgrade.yml workflow tested npm global install upgrades
over a specific release candidate (1.6.2-rc.8). That RC has shipped
and the workflow is no longer needed. Remove it and all references
from ci.yml (needs, env vars, gate check).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(ci): add changelog comments to upgrade readiness tracking issue
Each daily run now posts a comment summarizing what changed before
updating the issue body. Comments include the ready/blocker counts
and a diff of grammar status changes (e.g. tree-sitter-cpp:
Unreleased -> Ready). Gives a timeline of how the upgrade unblocks.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: standardize workflow concurrency and automate release-note labeling
Concurrency — prevent racing CI jobs
- Every top-level workflow now declares an explicit concurrency block.
- PR runs cancel-in-progress on supersede; main/push/workflow_call/publish
runs queue instead of cancelling so every commit and every release is
validated end-to-end.
- ci.yml uses a literal `CI-` prefix (not `${{ github.workflow }}`) and a
per-run nested group for workflow_call invocations, avoiding a potential
deadlock with publish.yml and release-candidate.yml callers whose own
concurrency groups could otherwise collide with the called workflow.
- ci-report.yml falls back to `<head-repo>/<head-branch>` for fork PRs
(stable across reruns) instead of the per-run-unique workflow_run.id
which did not actually serialize anything.
- ci-quality.yml enforces the convention: fails CI if any non-reusable
workflow lacks a concurrency block or a reusable workflow declares one.
Release-note automation
- New pr-labeler.yml: amannn/action-semantic-pull-request enforces
conventional-commit PR titles on pull_request (fork-safe, read-only);
release-drafter/release-drafter with disable-releaser: true applies the
matching label under pull_request_target (write-scoped). sync-labels in
.github/release-drafter.yml removes managed autolabels that no longer
match (e.g. when `!` or `BREAKING CHANGE:` is dropped from a PR).
- .github/release.yml (unchanged) continues to map labels to categorized
release-notes sections.
- dependabot.yml added for the github-actions ecosystem so pinned SHAs
auto-refresh on a weekly cadence.
Docs
- CONTRIBUTING.md documents the concurrency convention, the
conventional-commit PR-title rules, and the reusable-workflow exception.
Follow-up to verify before relying on the labeler in anger
- gh api repos/amannn/action-semantic-pull-request/git/refs/tags/v5.5.3
- gh api repos/release-drafter/release-drafter/git/refs/tags/v6.0.0
- Confirm release-drafter reads its config from the base ref (not fork
head) when invoked via pull_request_target.
* ci: address PR review feedback on concurrency and labeler workflows
Two blocking fixes
- pr-labeler.yml: separate concurrency slots for pull_request and
pull_request_target. Previously both triggers shared a single group
with cancel-in-progress: true, so the privileged autolabel run could
cancel the title-validation check mid-run and leave a required status
in a permanent cancelled state.
- pr-labeler.yml autolabel job: add contents: read. release-drafter's
context.config() reads .github/release-drafter.yml from the default
branch via the repo-contents API and 403s without the scope. Job-level
permissions nullify all unlisted scopes so an explicit grant is needed.
Two non-blocking improvements
- Replace the hardcoded reusable-workflow allowlist in ci-quality.yml
with dynamic on:-block parsing. New workflow_call-only workflows no
longer produce false-positive convention failures.
- Implement actual group-key validation. The check now also asserts that
every concurrency.group expression references either ${{ github.workflow }}
or the literal CI- prefix (the documented ci.yml exception).
- Script extracted to .github/scripts/check-workflow-concurrency.py so
it is runnable locally and independently testable.