The oracle-mask overlay covered the same path review setup reads, so every historical cell died with can't-open-patch. Leave the staged copy visible for apply, then fail closed if it is still there when the model starts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: release v1.6.11
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(test): read /api/info version from package.json
server-info unit tests hardcoded 1.6.10 while buildServerInfo reads
package.json, so the 1.6.11 bump failed ubuntu coverage 3/3.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: read the published version from one helper
Release bumps kept breaking tests that each re-required package.json.
packageVersion() is now the single read for CLI, MCP, serve, and those tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Seed the current gitnexus-review skill into older PR checkouts, force-add
historically gitignored skill paths, accept plugin-qualified Skill ids,
and lock host-unsafe workspaces to review-output.json so a generation can
finish and score. Sandbox cleanup restores owner write bits before delete
because a session that copytrees the locked clone otherwise leaves 0555
trees that rmtree cannot remove.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(core): add cache-first npm update-check service
Shared fail-open checker: validated 24h cache under GITNEXUS_HOME,
acquireFileLock-guarded refresh, monotonic publication, hardened
registry fetch (no credentials, private-address redirects refused,
body-capped), strict x.y.z comparator, install-eligibility
classification, and an unref'd refresh scheduler for long-lived
processes. Extracts getGlobalDir into storage/global-dir.ts with a
repo-manager re-export (no caller changes).
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli): notify on available updates via stderr and doctor
One i18n'd stderr line on interactive invocations when the validated
cache holds a newer version (TTY-gated, CI/opt-out/eligibility-gated,
hook and help/version command identities excluded). Stale cache spawns
a detached hidden __update-check refresh child so command exit latency
is unchanged. doctor prints the cached latest version when known.
Dockerfile.cli sets GITNEXUS_NO_UPDATE_NOTIFIER=1.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(mcp): emit one stderr update notice per process per version
Process-scoped adapter in mcpCommand (stdio and --http), dynamically
imported after the stdout sentinel, started only after connect, fully
catch-isolated. Arms the shared refresh scheduler with cleanup on
process exit. Protocol payloads stay free of update state (R15).
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(serve): expose update state on /api/info
Serve-scoped controller owns an in-memory update snapshot: one
staleness evaluation after listen, then the shared unref'd scheduler,
stopped on close/shutdown. /api/info reads only the snapshot and gains
optional latestVersion/updateAvailable fields for eligible installs;
the existing three fields are byte-compatible.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(web): dismissible update-available banner from /api/info
Fetches server info after backend connect and on reconnect, renders a
fixed banner in the exploring view only when updateAvailable is true
and the version is undismissed, hides while the reconnect banner is
active, and fails open on fetch errors. role=status + aria-live with a
keyboard-focusable dismiss; dismissal persists per version in
localStorage. Copy in en/zh-CN common.json with version interpolation.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs(cli): document update notifications and opt-outs
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(review): apply review findings and simplify pass
Review: gate the detached refresh spawn on a live lock-owner probe so
parallel CLI invocations coalesce to one refresh child (validated P2,
three-reviewer agreement); poll /api/info on a slow cadence while
exploring so post-load server-side discoveries surface (validated P1);
add a monotonic sequence guard so overlapping server-info fetches
commit in order.
Simplify (behavior-preserving): shared truthy-env/opt-out/freshness
helpers in update-cache.ts, shared cachedUpdateNoticeLine for CLI and
doctor, extracted install-eligibility core with per-process memo,
memoized registry parsing, single evaluation per scheduler cycle,
cache-only startup evaluate in serve, flattened MCP exit handler,
shared bottom-banner shell, storage keys in ui-constants.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(update-notifier): address residual review tickets on this PR
Stop the lock-busy 1ms scheduler spin, replace clock-skewed cache
entries, move the outbound URL guard into core, and extract the serve
update controller. Pin the startup/guard/single-flight/MCP/CLI contracts
those tickets called out.
Fixes#3167#3168#3169#3170#3171#3172#3173#3174
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3175)
Fetch the npm /latest document instead of the full packument so the 64KiB cap can succeed, and treat reused lock PIDs as stale so refresh is not suppressed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3175)
Register the CLI spawn suite on the OS matrix, pin MCP opt-out env, and compare versions without IEEE-754 rounding.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli): add gitnexus update install and versioned command banners
Give an explicit Claude/Codex-style upgrade (`npm i -g gitnexus@version`) and print `GitNexus <Name> (version)` on every command so the running build is obvious without silent self-update.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3175)
- Document the pinned install as npm i -g gitnexus@<x.y.z>, not a copyable @version tag
- Wait for wall-clock-future cache repair to publish before asserting
- Restore the stdout spy if the TTY notice assertions fail
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(update-notifier): keep last known latestVersion on a failed refresh
A later offline check was wiping the pin and hiding a known update for 24h.
gitnexus update still treats a failed live fetch as checkFailed.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3175)
- Word update.current so a newer-than-latest install is not called the latest stable version.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ci): hide the detached update-check spawn on Windows
The refresh child was spawned without windowsHide, so Windows CI could stall
before writing the cache and then fail cleanup with EBUSY.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(build): build the web UI from prepack, not from every npm ci
gitnexus-web is a separate ~650-package tree (React, Vite, LangChain,
Mermaid). Because `prepare` built it, every `npm ci` in gitnexus/ also
installed and Vite-built a second product. On CI that install ran
uncached inside an execSync timeout, so a healthy-but-slow install was
SIGTERM'd mid-flight and surfaced as `spawnSync /bin/sh ETIMEDOUT` --
repeatedly killing node floor compat, a job that only import-links the
CLI dist and never needs the UI.
The UI is only needed inside the published tarball, so build it from
prepack instead. `npm run build` and `prepare` are now CLI-only; pass
--web (or npm run build:web) to include it. Jobs that pack or publish
install gitnexus-web in their own visible step, and the in-script
fallback install is untimed so a slow install can no longer be killed
halfway and reported as a build failure. The tsc/vite timeout default
goes 300s -> 600s so the remaining bounded steps have headroom.
Default build on this machine: 30s, no gitnexus-web work.
* fix(build): enforce web package artifact integrity
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(build): clarify web packaging helpers without changing behavior
Keep the same opt-in, fail-closed, and pack/publish preserve rules while
trimming comments, sharing the test harness, and reading index.html
directly instead of probing it first.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: skip prepare on typecheck so a cold shared install cannot cancel the job
quality/typecheck's 10-minute budget was spent on an uncached gitnexus-shared
npm install plus a full prepare tsc that tsc --noEmit does not need.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: stop typecheck-web from canceling before the npm cache can save
Hashing gitnexus-shared into the web cache key forced a cold 650-package
install; the 10-minute job then canceled and never wrote a warm cache.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: give format the same 10-minute budget as lint
A cold root npm ci already took 4m19s and canceled prettier at the 5-minute
cap. Lint does the same install and needed 7m41s on that run.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: stop installing TypeScript 7 just to compile gitnexus-shared
A dedicated npm ci in gitnexus-shared took 7 minutes to add two packages
(TypeScript 7's optional per-platform binaries) and cancelled typecheck,
Windows pack, and coverage shard 1. Compile shared with gitnexus's tsc.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3166)
- Run tsc via execFileSync so the compiler path is never interpolated into a shell.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Address PR review feedback (#3166)
- Run tsc as node typescript/bin/tsc so Windows never has to execFile a .cmd shim.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Launch tsc via node and lib/tsc.js on every OS.
The npm .bin/tsc shim is tsc.cmd on Windows, which execFileSync cannot spawn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ci): lock eval containment against a dedicated shared npm ci
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Seeding is unconditional today, so the next run would inherit the rejected
proposal from a generation whose proposer could still read the hidden
oracles. That taint propagates: each generation stages the previous
proposal, so one contaminated proposal survives until the artifact expires.
Scheduled runs still always seed — memoryless weekly runs would re-propose
the same rejected candidate forever.
Co-authored-by: Cursor <cursoragent@cursor.com>
The proposer authors the overlay the benchmark arms are scored with, but its
clone was never sanitized: it could read eval/workflow_bench, i.e. the task
prompts and the hidden oracles it was about to be graded against. The last
diagnostic run did exactly that, reading
inv-feature-list-repos-filter.oracle.test.ts directly, so a proposal could
win the gate by encoding expected behavior into a skill instead of being a
better skill. Sanitize the proposer clone exactly as run_cell already does.
Also remove the PreToolUse tool-input normalizer. It never ran: headless
`claude -p` (2.1.247) dispatches no hooks from inline --settings, a settings
file, project/user/local --setting-sources, or a trusted ~/.claude.json
project entry. Keeping it would read as a control in review while enforcing
nothing, and it was the sole reason the proposer stopped using --bare —
which stays off on its own merits, since bare ignores --tools and would cost
the proposer Grep and Glob.
Blank optional arguments from the OpenAI adapter remain handled where the
code is ours: MCP aliases in local-backend normalizeToolParams. Built-in
Read still rejects pages:"" and the model self-corrects on the next turn.
Co-authored-by: Cursor <cursoragent@cursor.com>
The first fully-logged skill-evolution run failed for five deterministic
reasons that had nothing to do with the candidate under test. Each is fixed
at the layer that actually owns the contract:
- Strict provider adapters materialize omitted optional string arguments as
"". The MCP alias normalizer now treats a blank optional alias as absent
(a blank REQUIRED target is still rejected), and a trusted PreToolUse hook
strips blank strings before Read/GitNexus tool calls.
- MCP semantic errors rode home in a successful envelope and logged as
result=ok. SessionProgress now inspects the payload and reports them as
semantic-error.
- Claude Code's nested sandbox overlays absent root dotfiles with device
nodes, which the provenance snapshot read as unauthorized workspace
changes. Those names are excluded at the workspace root and hidden from
git via an immutable excludes file.
- The proposer could not read /evidence from Bash (missing allowRead entry)
and had no offline gitnexus runner, so it fell back to npx and hit the
network. Both are now mounted; ripgrep is installed in CI.
- selected-rows.json advertised host artifact names that do not exist in the
mount. Rows now name their staged patch_file/transcript_files, the prompt
describes the real layout, and oversized bundles compact artifacts before
dropping evidence rows so no row is silently lost.
Also replaces two benchmark scenarios that main already satisfies
(trivial-version-alias, inv-bug-pdg-note) with non-vacuous ones, verified to
fail against a pristine checkout.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(zig): static-gating analysis module + fixture (ported from feat/zig-static-gated-edges-v2)
Squashes c6fe922c, 2f3c8e9e, fab088f4, 9b58af74, aef2ae83, 86b892ef, d5657861,
f3780b3a: file-local comptime bool constants, cross-file flag resolution via the
@import alias map, re-aliased const chains, == / != against known bools,
else / else-if branch awareness. The module is self-contained; the hooks that
call it land in the next commit.
* feat(zig): stamp static-gated call sites through the scope pipeline
Wires the ported gating module into the scope-resolution pipeline that
now emits every Zig CALLS edge (PR #1432), replacing the parse-worker /
call-processor hooks of the original branch, which targeted the legacy
DAG path the merged provider no longer uses.
Data flow, one new fact carried end to end:
emitZigScopeCaptures stamps `@reference.static-gated` on a call
capture whose anchor lies in a statically dead
range (body of `if (CONST_FALSE)`, else of
`if (CONST_TRUE)`), via the module's new
`collectZigStaticGatedRanges` (line/col ranges,
because a Capture keeps no node)
scope-extractor marker -> `ReferenceSite.staticGated`
buildReference -> `Reference.staticGated`
references-to-edges, -> `GraphRelationship.staticGated` on the
free-call-fallback, emitted CALLS edge (both emit paths, plus
edges.ts (tryEmitEdge*) the generic bridge)
local-backend impact -> `staticGated` on impact frontier edges
Same marker idiom as Go's `@reference.callee-position` / `embedded-pointer`:
zero-range, present or absent, so every ungated site's capture set is
byte-identical and no other language changes.
SCHEMA_BUMP 92 -> 93: parse-time captures changed.
Cross-file constants (`if (cfg.FOO)` with `cfg = @import("cfg.zig")`) are
NOT stamped yet: the module resolves them through `lookupBoolsForPath`, but
the capture emitter runs per file in the parse worker with only
`{ path, content }`, so it cannot see the sibling source. The two positive
cross-file cases in zig-static-gating.test.ts are `it.skip` with that
reason; the negative ones pass unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ciQ3MTjpQXkCoNntZR9zG
* feat(graph): add staticGated edge property
Adds an optional `staticGated?: boolean` field to `GraphRelationship`
that flags edges originating in code branches known at index time to
be unreachable in production — e.g. `if (CONST_FALSE)` blocks where
the condition reduces to a comptime-known `false`.
Schema + persistence wiring:
- `gitnexus-shared/src/graph/types.ts` — additive optional field on
`GraphRelationship`; absent edges read identically to live ones.
- `gitnexus/src/core/lbug/schema.ts` — `staticGated BOOLEAN` column
on the `CodeRelation` REL table.
- `gitnexus/src/core/lbug/csv-generator.ts` — appends a
`staticGated` column (0/1) to the `relations.csv` written for
bulk COPY ingest.
- `gitnexus/src/core/lbug/lbug-adapter.ts` — fallback per-row
`MATCH ... CREATE` insert reads the optional column and threads
it into the relationship properties.
No language has populated this field yet — the Zig hookup lands in
the next commit. Existing DBs need a re-index for the new column to
appear; existing readers are unchanged because the field is
optional and absent on every other language's edges.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
(cherry picked from commit c8cd5efe27a77a5d1b3f05e3f4e89069b5c6b19e)
* fix(zig): gate bare literal branches; AND the flag over deduplicated free-call sites
PR #3161 review, two findings:
1. `stampZigStaticGating` returned early when the file declared no boolean
constants, but `collectZigStaticGatedRanges` also folds bare literals, so
`if (false) { foo(); }` in a constant-free file went unstamped. The early
return is gone; the range walk runs for every file.
2. `emitFreeCallFallback` deduplicates CALLS edges per (caller, callee) and
wrote `staticGated` from whichever site it met first, so a callee reached
from one live site and one dead site was gated or not by traversal order.
Emission is now deferred to the end of each file's sites and the flag is
the AND over every site that collapsed into the edge: one live site keeps
the edge live. The other emit path keys its dedup on the site range and
was not affected; `collapseByCallerTarget` in the generic bridge would
have the same shape but no language that sets the marker opts into it.
Fixture + tests: `gated_bare_literal`, `live_and_gated_same_callee` (live
site first) and `gated_then_live_same_callee` (dead site first) in
zig-static-gating.test.ts. All 70 resolver suites (3,603 tests) pass with
the shared emitter change.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ciQ3MTjpQXkCoNntZR9zG
* test(zig): move the SCHEMA_BUMP pin to 93; rebaseline emit fingerprints for the staticGated column
Three CI failures on f4954963, all consequences of this PR:
- test/unit/incremental-parse-cache.test.ts pins SCHEMA_BUMP so concurrent
bumps cannot collide; 92 -> 93 for #3161 (parse-time call captures gain
`@reference.static-gated`), 92 added to the taken list.
- bench/emit-persistence `measure.mjs --check` and `measure-streaming.mjs
--check`: byte-identity fingerprints drift because every relationship row
now ends in a `staticGated` cell. Regenerated with the inverse-operation
evidence recorded under `_rebaselined_3161_static_gated_column` in both
baseline files: stripping ONLY the new column from the emitted CSVs
reproduces the prior fingerprints exactly (36 files, 3 rel_* files differ,
33 byte-identical; 36,000 PDG rows each +2 bytes), so no row moved between
pair files or reordered. Timing and retention gates passed throughout.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ciQ3MTjpQXkCoNntZR9zG
* docs(graph): state the CALLS contract on staticGated; say "provably unreachable at compile time"
Review on #3161 (magyargergo): the flag must not redefine what a CALLS
edge means. The field's doc now says so explicitly: CALLS still means
"there is a resolved call site from A to B", never "B is reachable from
A"; `staticGated` is additional, statically provable path-feasibility
metadata, an opt-in analysis layer that no core pass acts on. The edge is
emitted, persisted, traversed and counted exactly as before.
Wording: "unreachable in production" -> "provably unreachable from the
indexed source at compile time" on GraphRelationship, ReferenceSite and
Reference. The index has no production build configuration and should not
claim one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ciQ3MTjpQXkCoNntZR9zG
* feat(zig): surface staticGated on impact byDepth items; gate if-expressions and negated/parenthesized conditions
Addresses the tri-review on #3161.
- impact: the depth traversal already selected r.staticGated but dropped it
when building the byDepth item. Forward it (present only when true) and
document the field on the impact tool's byDepth contract. Traversal and
ranking still do not act on it; that stays opt-in for consumers.
- zig-static-gating: walk `if_expression` (`const x = if (c) a() else b();`)
in addition to `if_statement`. The expression form has no field names and
no else_clause wrapper, so the arms are located positionally
(`ifExpressionArms`). Labeled-block arms are covered.
- evalCond: `parenthesized_expression` is transparent, so `!(A and B)` and
`((FLAG))` fold. Prefix `!` has no unary node in tree-sitter-zig; the
header now says exactly which shapes fold instead of "simple negation".
- fixture + tests: nine new cases (negation x2, parentheses x2,
if-expression then/else/labeled-block x5). Cross-file `@import` constants
remain skipped and now cite the tracking issue #3162.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ciQ3MTjpQXkCoNntZR9zG
* refactor(zig): drop the unreachable cross-file gating builders; document the real wiring
gitnexus-check on b77cb4ed: `buildZigImportAliasMap` / `buildZigRawImportAliasMap`
and the per-call ancestor walk (`isCallStaticGated`, `ifBranchDirection`,
`nodesEqual`) had no caller anywhere. They were ported from the legacy
call-processor design; the scope-resolution provider stamps ranges via
`collectZigStaticGatedRanges` instead, and nothing populates the cross-file
seam yet (#3162). Remove them so the module exports only what runs.
The evaluator keeps `importAliases` + `lookupBoolsForPath` (the seam #3162
will fill); the header now says so explicitly and points at the actual
wire-up (`stampZigStaticGating` in languages/zig/captures.ts) instead of the
retired `configs/zig.ts` hook.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ciQ3MTjpQXkCoNntZR9zG
* refactor(zig): reuse descendantsOfType and tighten static-gated capture matching
Walk if-nodes through tree-sitter instead of a hand-rolled stack, return the original capture array when nothing is gated, and register the marker as a known sub-tag so it cannot be mistaken for an anchor.
Co-authored-by: Cursor <cursoragent@cursor.com>
* style(zig): wrap a long else-clause assignment to satisfy prettier
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The sweep printed error_kind=plan-evidence-invalid and nothing else, so
the reason a cell failed stayed in results.jsonl — an artifact uploaded
after the run, not something a watcher can read while it is still going.
Print the cell's error_detail next to its result line, redacted through
the same credential list as the artifact and bounded, since a
session-error detail carries stdout/stderr tails.
Co-authored-by: Cursor <cursoragent@cursor.com>
A proposer or benchmark session could run for an hour with nothing in the
log between "proposing…" and its final result, so a wedged run looked
exactly like a working one. The last CI failure spent 66 minutes silently
retrying a dead endpoint before saying so.
A session's stdout is evidence and is only written out after redaction,
so it can never be echoed. Add a stdout_observer hook to run_managed that
sees the stream without copying it anywhere, and a SessionProgress
reporter that prints only what can be derived safely: turn counts, tool
names, API retries, and a heartbeat while the session is quiet. API
retries are called out by name because that is the signature of the
gateway wedging.
Progress goes to stdout so the benchmark sweep's lines reach the log
live through the existing echo_stdout passthrough, rather than as a
bounded stderr tail after the fact.
Co-authored-by: Cursor <cursoragent@cursor.com>
The loopback LiteLLM proxy was started with stderr=PIPE, but nothing
drained that pipe after the readiness probe. Once the proxy's request
logs filled the 64 KiB pipe buffer it blocked on write, so every later
session request hung with no HTTP status. The last CI evolution run
burned 66 minutes and $3.98 before dying on ten "Request timed out"
retries with error_status=null.
Send proxy stdout+stderr to a 0600 log file in the gateway work dir
instead, and read startup failure detail from that file.
Also give both ends of the loopback hop a 30 minute budget: high
reasoning effort on a full context window can leave a request without a
first token for longer than Claude Code's default client timeout, so
sessions failed on the clock rather than on real errors.