Commit graph

31 commits

Author SHA1 Message Date
Gergő Magyar
14397dd4aa
feat(taint): intra-procedural taint analysis (#2083) (#2164)
* feat(taint): harvest occurrence-tagged call/member sites on StatementFacts (#2083 U1)

Worker-side site harvest in TsHarvester: call/new/member-read records with
dotted callee paths, receiver slots, per-argument occurrence tagging with
nested-site links, per-declarator resultDefs, spread/template/require-literal
markers. hasTaintSafeSites validation seam. The pdg parse-cache chunk-key
namespace is versioned (pdg:1 -> pdg:2) instead of a global SCHEMA_BUMP so
flag-off users keep warm caches; bench fingerprints re-baselined for the
three call-bearing scenarios (straight-line/dense-bindings byte-unchanged).

* feat(taint): built-in TS/JS source/sink/sanitizer model + site matcher (#2083 U2)

Typed spec (kind taxonomy; sanitizers carry neutralizes-kinds), the canonical
Express/Node model, and matchFunctionSites: ESM alias/namespace + require-
literal callee resolution, bare-name fallback restricted to true globals,
sanitizers module-or-global only (never user-shadowable by name), spread/
template arg-position rules, deterministic taintModelVersion.

* feat(taint): pure intra-procedural taint propagation engine (#2083 U3)

Two-rule model (statement-local + du-fact worklist) with per-taint
neutralized-kind exclusion sets: sanitizers exclude only the sink kinds
they neutralize (escape(req.body) suppresses res.send but still fires
db.query; exec(path.basename(t)) fires), intersection-over-paths so a
bypass occurrence keeps the taint live, kill locality on resultDefs,
propagate-through args+receiver with viaCall hops, one path per finding,
deterministic caps, coverage-gap statuses. Test-first: 38 scenarios on
real harvested CFGs.

* feat(taint): thread taint caps + model version through pdg config/meta (#2083 U5)

resolvePdgConfig gains maxTaintFindingsPerFunction (200), maxTaintHops (32),
and the taintModelVersion digest; RepoMeta.pdg + RunScopeResolutionInput
surfaces added. The key-union comparator trips full writeback on M2->M3
upgrade and on model-version change without --force (mode-flip tested).
No CLI flags or rc keys (programmatic parity with the other caps).

* feat(taint): in-phase taint emit with sparse TAINTED/SANITIZES edges (#2083 U4)

run.ts pdg window: match-first fast path (solver only when a function has
both a matched source and sink) -> computeReachingDefs with the shared RD
fact derivation -> computeTaintFlows -> per-finding TAINTED (versioned
hop-encoded reason via the shared path codec, statement-level occurrence
identity) + per-kill SANITIZES, dedup-before-budget, truncate-and-warn.
All emit counters surfaced (aggregate warn for gaps/drops, debug for
volume); PROF gains taint=. Flag-off golden untouched.

* feat(mcp): explain tool for persisted taint findings (#2083 U6)

Anchorless calls enumerate the sparse TAINTED table (bounded, deterministic,
limit-clamped); anchored calls (file or symbol via resolveSymbolCandidates)
return full decoded hop detail. sinkKind rides a version-1 codec header
(1;<kind>|hops — no other persisted channel exists; U4/U6 ship together).
RepoMeta.pdg probe yields a no-taint-layer note instead of an error.
TAINTED/SANITIZES pinned OUT of VALID_RELATION_TYPES (KTD9a negative-
membership tests); generators + canonical skill docs + mirrors updated.

* test(taint): acceptance fixture battery, snapshots, and bench gates (#2083 U7)

pdg-repo taint-cases fixtures complete the six plan shapes; committed
findings/kills snapshot via a shared pure-path harness that also feeds the
AE2 exact-equality assertion (stored TAINTED == pure-path findings, the
no-explosion gate). New taint-dense bench scenario with four --check gates:
per-function findings pinned AT the cap, absolute reason-byte + site-bytes
disk ceilings (the load-bearing R10 gate), zero-match pass < 0.5x match-
dense, N-linearity. Pre-existing scenario baselines untouched.

* refactor(taint): share one pointKey helper across propagate + emit (#2083 review)

Extract pointKey(ProgramPoint) to cfg/reaching-defs.ts (colon-separated,
matching the codebase block:stmt id convention) and import it in both
propagate.ts and emit.ts, replacing the two divergent locals (':' vs '.').
Edge-id material now uses the colon form; ids are in-memory only and no
test asserts the pointKey segment shape.

* fix(taint): discriminate taint state by source occurrence (#2083 review)

Two distinct sources flowing into one variable at one def point no longer
collapse to a single TAINTED edge: the taint-state key gains a root
source-occurrence discriminator ({point, siteIndex} — the same fields
recordFinding's identity uses, excluding kind). Def->use fact lookup keys
on the source-independent (binding, def-point) portion. Same-source
multi-path flows still share one state so their exclusion sets intersect
(the raw arm soundly wins); termination holds (finite keys, monotone
shrink, no cross-source ping-pong). Restores the KTD6 identity contract.

* fix(mcp): route dotted symbol names in explain to symbol resolution (#2083 review)

The fileish classifier matched any dotted name (UserController.create)
as a file via its extension-like suffix, so symbol resolution never ran
and the tool returned a silent empty file-anchored result. Tighten the
classifier to require a path separator or a real source extension (derived
from the resolver's EXTENSIONS list, multi-language), so dotted/bare names
route to resolveSymbolCandidates (found / ambiguous / not-found).

* fix(mcp): gate explain no-taint-layer note on taintModelVersion (#2083 review)

An M1/M2-era --pdg index has meta.pdg defined (BasicBlock/REACHING_DEF
recorded) but no taintModelVersion and zero TAINTED rows. The probe keyed
on generic meta.pdg presence, so explain returned the generic empty note
instead of the actionable 'no taint layer — run analyze' hint. Gate on
meta.pdg?.taintModelVersion (the field M3 stamps) so an M2-era index gets
the layer hint; a taint-stamped index with no findings still gets the
generic note.

* fix(taint): sequence-expression value flows only the final operand (#2083 review)

A comma expression in value position (exec((log(x), 'safe'))) default-
descended, fanning every operand's occurrences into the enclosing sink
argument — over-tainting exec's arg 0 with x. Add an explicit walkValue
case that records earlier operands' uses with occurrence fan-out suppressed
(new FactAccumulator.suppressOccurrences) and routes only the last operand
through the value path. Sites-layer only; defs/uses/mayDefs byte-identical
(cfg + reaching-defs snapshots unchanged).

* perf(taint): FIFO head-cursor worklist + dedup before chainHops (#2083 review)

Replace queue.shift() (O(N) dequeue) with a strict-FIFO head cursor plus
order-preserving prefix reclamation; FIFO is load-bearing because chainHops
reads the live taints map whose parent/source/viaCall are rewritten
order-sensitively on monotone shrink, so hop determinism is dequeue-order
contingent. Extract findingKey() and dedup-check before chainHops in the
justify branch — already-recorded identities discard their hop chain
(first write wins), so the ancestry walk was pure waste. The else kill
branch is untouched. Findings + hops byte-identical (snapshot unchanged).

* perf(taint): O(1) member-read dedup via composite-key set (#2083 review)

addMemberRead rescanned the whole per-statement sites array per call to
dedup by (object, property, parent) — O(n^2) on member-read-dense
statements. Track a composite-key Set alongside sites for O(1) dedup.
(The require-literal join is already O(sites) with a no-op body on
non-require sites, so no early-exit is needed there.) Behavior identical:
harvest + model-match + taint snapshots unchanged.

* refactor(taint): drop test-only export; source taint caps via emit.ts (#2083 review)

Remove the sanitizerNeutralizes export (its only consumers were two test
assertions — inlined to entry.neutralizes membership). Re-export the
DEFAULT_PDG_MAX_TAINT_* caps from emit.ts and point run.ts at emit.ts, so
the pipeline's taint dependency surface is the single orchestration module
rather than reaching into propagate.ts.

* test(taint): extract the shared TS CFG/taint test harness (#2083 review)

The parse/collectFunctions/cfgOf/cfgsOf/importsFor harness was copied
byte-for-byte across four suites (harvest, model-match, propagate,
taint-emit). Promote it to test/helpers/ts-cfg-harness.ts and import it.
site-safety/reaching-defs carry a structurally different inlined builder
and are left as-is. Pure extraction, no assertion changes.

* test(mcp): harden explain limit-rejection battery (#2083 review)

Add NaN, Infinity, -Infinity, and a numeric string to the out-of-bounds
limit cases — a regression fence over the interpolated LIMIT, confirming
the Number.isInteger guard rejects every non-integer/non-finite/string
input before it reaches the query.
2026-06-12 07:35:09 +01:00
Minidoracat
10d1e47df3
fix(hooks): bound db-lock probe subprocesses and gate probe behind hook slot (#2163) (#2165)
* fix(hooks): bound db-lock probe subprocesses and gate probe behind hook slot (#2163)

The Claude PreToolUse db-lock probe leaks orphaned lsof processes when
the hook process is hard-killed mid-probe (e.g. Claude Code's 10s hook
timeout under load). Orphans accumulate, raise load, slow the next
probe, and snowball to sustained 100% CPU.

- Wrap the unix lsof/ps fallback in coreutils timeout (-k 1 2 / -k 1 1),
  resolved via a lazy self-test, so probe children self-destruct within
  ~3s even if the hook is SIGKILLed. GITNEXUS_HOOK_TIMEOUT_PATH
  overrides the guard binary; the sentinel value 'disabled' turns the
  guard off; hosts without a usable guard keep the previous behavior.
- Acquire the per-repo hook slot before probing (all three adapters),
  bounding concurrent probes to 3 per .gitnexus, with probe and augment
  inside try/finally so the slot is always released.
- Tests: source-order contract, slot-gating behavior, orphan reaping
  with a SIGTERM-immune fake lsof and a SIGKILLed parent (red on base),
  probe-copy byte parity, no-guard equivalence, broken-guard rejection.

Note: pre-commit typecheck skipped; the 62 tsc errors are pre-existing
on main (all in src/core/** and src/server/, none in files touched
here; base==head invariant verified).

* fix(hooks): address tri-review P3 findings (#2165)

- Map guard signal-death (status null + signal, no spawnSync error) to
  fail-closed at both the lsof and ps call sites, closing the freeze
  window (SIGSTOP / laptop sleep > 2s) that previously landed fail-open.
  Rewrite the exit-code comments: coreutils surfaces the -k kill as
  signal death, 124 is budget expiry (live arm), 137 covers only
  exit-code-propagating wrappers or an externally SIGKILLed child.
- Add a debug-gated 'augment skipped: hook slots saturated' stderr line
  on the slot-starved early return in all three adapters, restoring
  observability under GITNEXUS_DEBUG=1.
- GITNEXUS_HOOK_TIMEOUT_PATH now participates in candidate fall-through:
  the env candidate is tried first, then the built-ins, each behind the
  lazy self-test — an existing-but-unusable env path (directory,
  non-executable) can no longer silently disable orphan containment.
- Tests: +6 — guard exit 124 pins the live arm (CJS+Plugin), guard
  signal-death pins the new mapping (CJS+Plugin, red before the fix),
  antigravity behavioral slot-gate, env-dir fall-through still reaps a
  SIGTERM-immune orphan via a built-in guard.

Note: pre-commit typecheck skipped; the 62 tsc errors are pre-existing
on main (none in files touched here).
2026-06-11 15:38:13 +01:00
Gergő Magyar
292f26ece3
fix(hooks): silence MCP-owned-DB augment skip for strict hook runners (#1913) (#2134)
* fix(hooks): silence MCP-owned-DB augment skip for strict hook runners

The PreToolUse augment-skip path wrote `[GitNexus] augment skipped: MCP
server owns DB` to stderr unconditionally on a normal (non-error) skip.
Strict hook runners that validate hook output (e.g. Codex `PreToolUse`)
treat that as noisy / "invalid pre-tool-use JSON output".

Gate the diagnostic behind GITNEXUS_DEBUG via a shared `isDebugEnabled()`
helper, so normal skips are silent by default (empty stdout AND stderr,
exit 0) and the reason stays recoverable with `GITNEXUS_DEBUG=1`. Applied
consistently to all three hand-maintained hook copies (claude,
antigravity, claude-plugin).

Tests:
- Unit (claude CJS + plugin): assert default-silent and debug-on behavior
  for the MCP-owned-DB skip and for the fail-closed (lsof ETIMEDOUT) skip
  that routes through the same gated line; the owner-detection tests run
  with GITNEXUS_DEBUG=1 so the skip discriminator stays observable.
- e2e (antigravity): the antigravity adapter shares the identical gated
  skip but only runs from its install dir, so cover it through the install
  pipeline with a faked DB-owner probe (strict empty-stdout/stderr +
  debug-on). Promote the fake-probe helpers (createHookToolDir / hookEnv,
  plus a module-private writeExecutable) into shared hook-test-helpers so
  unit + e2e reuse them.

Fixes #1913

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

* fix(hooks): unify GITNEXUS_DEBUG gating in main() catch handlers

The main() catch-handler in all three hook copies still gated its crash
log on truthy `if (process.env.GITNEXUS_DEBUG)`, while the skip diagnostic
the #1913 fix added is gated on the strict `isDebugEnabled()` helper
(=== '1' || === 'true'). That split meant GITNEXUS_DEBUG=0 or =false
suppressed the skip line yet still enabled crash logging — two conflicting
contract signals in the same file.

Switch the three catch handlers to isDebugEnabled() so GITNEXUS_DEBUG has
one strict meaning everywhere: exactly '1' or 'true' enables all
diagnostics; everything else (incl. '0', 'false', empty, unset) is silent.

Add boundary tests asserting the MCP-owner skip stays silent with
GITNEXUS_DEBUG='0' and 'false' (CJS + Plugin), pinning the strict contract.

Refs #1913

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

* fix(hooks): gate antigravity stale-index hint stderr behind GITNEXUS_DEBUG

The antigravity AfterTool handler mirrored the stale-index hint to stderr
unconditionally on a normal (non-error) success path — the last ungated
stderr write of the class issue #1913 targets, and a divergence from the
claude hook, which never mirrors this hint to stderr.

Gate the stderr mirror behind isDebugEnabled(). The hint still reaches the
agent via additionalContext (stdout JSON) — parts.push(hint) stays
unconditional — so there is no functional loss; only the by-default
terminal mirror moves behind GITNEXUS_DEBUG=1. This knowingly changes the
#1730 terminal-mirror behavior in favor of strict-runner cleanliness and
parity with the claude adapter.

Split the e2e assertion into a default-silent test (hint in
additionalContext, absent from stderr) and a GITNEXUS_DEBUG=1 test (hint
mirrored to stderr).

Refs #1913

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

* docs(hooks): document GITNEXUS_DEBUG=1 for hook diagnostics

GITNEXUS_DEBUG was documented only in the cursor integration README, so
the diagnostic escape hatch for the Claude Code / Antigravity hooks was
undiscoverable. Operators hitting a silent hook skip (MCP server owns the
DB, fail-closed probe timeout, or an already-current index) had no
documented way to surface the reason.

Add a Troubleshooting subsection explaining that the hooks stay silent on
normal skip paths for strict runners, that GITNEXUS_DEBUG=1 surfaces the
reason on stderr, and that only '1'/'true' enable diagnostics (stdout JSON
the agent consumes is unaffected).

Refs #1913

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

* test(hooks): update setup-antigravity unit test for gated stale-index hint

U2 (7995e921) gated the antigravity stale-index hint stderr mirror behind
GITNEXUS_DEBUG, but a second test — setup-antigravity.test.ts's "AfterTool
emits stale-index hint" — also asserted the hint on stderr by default and
was missed (it lives outside the two files validated locally; the full CI
matrix caught it).

Update it to the U2 contract: assert the hint via additionalContext with
stderr silent by default, plus a GITNEXUS_DEBUG=1 run asserting the
terminal mirror reappears.

Refs #1913

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:09:41 +01:00
Gergő Magyar
1cf65b339c
chore: release v1.6.7 (#2126)
Bump gitnexus 1.6.6 -> 1.6.7, add the 1.6.7 CHANGELOG section (14 PRs since v1.6.6), and sync the Claude plugin manifests (plugin.json + marketplace.json) to 1.6.7.
2026-06-09 21:37:33 +01:00
Gergő Magyar
4682a477d8
feat(mcp): paginate list_repos to avoid client token truncation (#2119) (#2120)
* feat(mcp): paginate list_repos to avoid client token truncation (#2119)

list_repos returned every indexed repository in one unpaginated array,
which large/LLM MCP clients truncate by token limit — so agents with
hundreds of indexed repos could not enumerate them all (the data
transmits fully; the consuming client drops it).

Add bounded limit/offset pagination to the list_repos tool:
- result changes from a bare array to
  { repositories, pagination: { total, limit, offset, returned,
  hasMore, nextOffset } }; default page 50, max 200 (shared constants)
- reject malformed limit/offset; clamp limit above the max
- deterministic order (lower-cased name, then path) over one registry
  snapshot per call, so paging never skips or duplicates an entry
- covers both stdio and remote /api/mcp (shared createMCPServer/callTool)

The internal listRepos() method (5 callers), GET /api/repos, and the
`gitnexus list` CLI are unchanged. The array->object tool-result shape
is a deliberate contract change, documented in CHANGELOG.

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

* fix(mcp): reject list_repos limit above the max instead of clamping (#2119)

parseListReposPagination silently clamped limit>max to the maximum while
throwing on every other out-of-bounds value (limit<1, offset<0, non-integer,
NaN). A client that advanced offset by its requested limit (rather than
pagination.nextOffset) then silently skipped repositories and saw
hasMore:false — defeating the "never skips" guarantee. Reject an over-max
limit too, so validation is symmetric and a caller never gets a smaller page
than it asked for without a clear error. Updates the schema/description, the
helper + ListReposPagination JSDoc, the guide note, and the two clamp tests.

Resolves the cross-engine-corroborated P2 (Codex + adversarial lane) and the
maintainability lane's clamp-vs-throw inconsistency from the PR #2120 review.

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

* refactor(mcp): name the list_repos return type and mark the parser @internal

Extract the inline listRepos() element shape into an exported RepoListing
interface and use it for both listRepos() and listReposPage().repositories,
replacing the opaque Awaited<ReturnType<LocalBackend['listRepos']>> expression
the maintainability review flagged. Tag parseListReposPagination @internal
(it is exported only for unit testing). Pure type/JSDoc change; no behavior.

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

* refactor(eval-server): type formatListReposResult to the paginated shape

Narrow formatListReposResult's parameter from `any` to
{ repositories: RepoListing[]; pagination?: ListReposPagination } and drop the
dead bare-array branch — after #2119 callTool('list_repos') always returns the
paginated object, so the Array.isArray shim was unreachable. Add a list_repos
continuation hint to the eval-server's getNextStepHint (parity with the MCP
server), and cover the previously-untested non-empty + hasMore:false formatter
branch. Migrates the two bare-array formatter tests to the object shape.

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

* test(mcp): harden list_repos pagination coverage

- Exercise the #2054 sibling-clone guarantee through the real callTool tool
  path (in the #2054 describe, which has temp-dir cleanup), proving siblings
  and remoteUrl survive listReposPage's sort+slice — not only listRepos().
- Assert total + limit on the middle-page test (a total miscalculation at a
  non-zero offset would otherwise slip past it).
- Cover the benign boundaries: negative-zero offset (accepted as page 0) and a
  MAX_SAFE_INTEGER offset (empty page).
- Replace the integration test's '\n\n---' split with a string-aware brace
  scan, so a repo path containing braces can never truncate the JSON parse.

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

* docs(skills): sync the list_repos pagination example to the guide mirrors

The .claude and gitnexus-claude-plugin guide mirrors only carried the one-line
table note; add the full "Paginating list_repos" section (shape + multi-page
traversal example + notes) so all three guide copies are byte-consistent with
the canonical gitnexus/skills/gitnexus-guide.md.

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

* chore: drop list_repos CHANGELOG entries from this PR

Restore gitnexus/CHANGELOG.md to match main so this PR contributes no
changelog change; the changelog is curated separately from feature PRs.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 19:59:54 +01:00
Copilot
689e6ef1f8
chore: Sync Claude plugin manifests with the 1.6.6 release (#2090)
* Initial plan

* fix: sync Claude plugin manifest versions

* test: fold manifest sync check into existing node suite

* chore(autofix): apply prettier + eslint fixes via /autofix command

* test: run manifest sync guard in always-on suite

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-08 16:50:09 +01:00
Gergő Magyar
2dc0cc6398
fix(mcp): prevent sibling-clone repo ID collisions and correct generated MCP tool names (#2067) 2026-06-07 10:57:15 +01:00
Gergő Magyar
f01d913eef
fix(hooks): resolve gitnexus on PATH with a pure-Node scan, all-OS (#1938) (#1980) 2026-06-03 03:19:49 +01:00
Gergő Magyar
f885330b34
fix(cli): steer docs, skills, and hooks through a CLI-neutral project-local runner (#1939) (#1945)
* fix(cli): steer npm 11 users away from npx install crash (#1939)

Prefer global gitnexus or pnpm dlx in hooks and generated AI context, warn
when npm 11.x would use the broken npx path, and document workarounds for
the arborist node.target null failure mode.

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

* test(hooks): stage resolve-analyze-cmd.cjs for antigravity adapter; harden load checks

The antigravity adapter gained a top-level require('./resolve-analyze-cmd.cjs')
but stageAdapter() did not copy it, so the spawned adapter crashed with
MODULE_NOT_FOUND. Three load-sensitive tests failed; four silent-path tests
false-passed on empty stdout.

Stage the helper alongside the other sibling helpers, and assert status===0 and
no MODULE_NOT_FOUND on the four silent-path tests so a non-loading hook can never
pass green again. Force a deterministic invocation mode in the stale-index test
so the emitted analyze command no longer varies by CI-runner PATH.

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

* fix(cli): standardize invocation hints on gitnexus@latest; single-source CJS helper

NPX_REF becomes a literal `gitnexus@latest` in resolve-invocation.ts, dropping
the package.json require and the module-load throw (a malformed/absent version
can no longer crash any CLI command at import). The safety this PR delivers is
the install method steered to (global / pnpm dlx), not a pinned gitnexus
version, and the in-repo CJS mirror already degraded to `latest` once copied
outside the package.

Make the two resolve-analyze-cmd.cjs copies byte-identical and add a parity
test that fails on drift. The separate, version-pinned NPX_REF that setup.ts
writes into the MCP server registration is intentional and left unchanged.

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

* perf(cli): move npm-11 npx warning off module load; memoize invocation mode

warnIfNpm11NpxRisk() ran at index.ts module load, so every CLI invocation
(including the `gitnexus mcp` stdio hot path) paid which/where + npm --version
spawns — against the lazy-startup/MCP-stdout discipline (#207, #1383). Move the
call into analyzeCommand, after the ensureHeap() re-exec guard, so it fires once
in the working process and only for `analyze`.

Memoize the PATH-probe-derived invocation mode (the GITNEXUS_INVOCATION override
stays uncached) so repeated callers don't re-probe, and add a test-only reset so
the cache + once-only warning flag don't leak across the unit suite. Covers the
mode!=='npx', npm<11, and npm-absent suppression branches.

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

* fix(cli): detect .exe/extensionless global gitnexus shims on Windows

The winGitnexusWrapper branch only matched .cmd/.bat, so a global gitnexus
installed by Volta or scoop (a .exe or an extensionless shim) was missed and the
hint fell back to pnpm/npx. Accept .exe and treat any non-empty `where` hit as
on-PATH (the emitted hint is `gitnexus analyze` regardless of which shim
resolves it). Mirror the change into both resolve-analyze-cmd.cjs copies so the
TS source and the byte-identical hook mirrors stay in sync.

Add Windows-mocked test cases (.exe-only, extensionless, .cmd preference, CRLF
stripping) and register resolve-invocation.test.ts in cross-platform-tests.ts so
the windows-latest runner exercises the branch.

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

* fix(cli): emit fixed pnpm dlx analyze command in generated AGENTS.md/CLAUDE.md

ai-context baked a machine-resolved command (formatAnalyzeCommand) into
git-tracked AGENTS.md/CLAUDE.md, so the stale-index hint varied per machine and
churned across branches (the #1706 class). Emit the fixed string
`pnpm dlx gitnexus@latest analyze` instead: committed AI-context is the most
authoritative instruction an agent reads, so it must name an install-free,
crash-free method — never `npx`, the npm-11 path #1939 steers away from.

formatAnalyzeCommand stays exported and unit-tested in resolve-invocation.ts
(it still mirrors the two .cjs hook copies); ai-context just no longer calls it.

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

* refactor(cli): unify hook-helper copy into one non-silent routine

installClaudeCodeHooks copied its four hook helpers in separate try/catch blocks
that silently swallowed failures, while installAntigravityHooks recorded an
error per failed copy. Extract one copyHookHelpers(srcDir, destDir, label,
result) with a single canonical helper list (including resolve-analyze-cmd.cjs)
and the antigravity loop's error-reporting policy, and use it from both paths so
a missing helper surfaces as a setup error instead of a silent runtime crash.

Assert both the Claude and Antigravity install paths co-locate
resolve-analyze-cmd.cjs next to the adapter, and that a failed copy records an
error rather than passing silently.

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

* docs(cli): reattach installClaudeCodeHooks JSDoc after helper extraction

The extracted HOOK_HELPERS/copyHookHelpers block landed between the
installClaudeCodeHooks JSDoc and its function, leaving the doc reading as if it
described the helper list. Move the block above the doc so it documents the
function again. No behavior change.

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

* test(cli): enforce TS<->CJS invocation parity and guard CLI startup posture

Tier-2 review found two in-scope gaps in the #1945 follow-up:

- The "mirrors resolve-invocation.ts / test enforces parity" comments overclaimed:
  the parity test only compared the two .cjs copies to each other, so the TS
  source and the CJS hook copies could silently drift (NPX_REF, the per-mode
  command, and the Windows shim regex were hand-edited in all three this PR).
  Add TS<->CJS value parity (NPX_REF + formatAnalyzeCommand for every forced
  mode) and a source-level shim-regex parity check, and make the mirror comments
  accurately describe what is enforced.

- No test locked the R3/R4 startup posture, so re-adding warnIfNpm11NpxRisk()
  (or any resolve-invocation import) at index.ts module scope -- the #207/#1383
  lazy-startup regression -- would pass CI. Add a guard asserting index.ts has
  no module-load invocation probe and the warning is wired into analyzeCommand.

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

* refactor(cli): collapse npx-invocation resolver to one source of truth

PR #1945 carried the gitnexus/pnpm/npx selection in three hand-synced
places — the canonical hook helper, its byte-identical plugin copy, and a
full TypeScript re-implementation in resolve-invocation.ts — kept in lockstep
by per-mode-command and regex-extracted-by-regex parity tests. The TS
formatAnalyzeCommand had no production caller (ai-context emits a fixed
string), and the module memoized + exposed a test-only reset for a "repeated
callers" case that has exactly one caller.

Make hooks/claude/resolve-analyze-cmd.cjs the single source: extract the
Windows-shim line-picking into a pure, exported pickPathMatch() and add an
injectable probe to resolveInvocationMode() so the shipped logic is testable
without spawning or global mocks. resolve-invocation.ts (118 -> 59 lines) now
consumes that cjs via createRequire for resolveInvocationMode/NPX_REF and adds
only the CLI-only npm-version probe and warning; the relative path resolves
identically from src/cli/ (tsx, vitest) and dist/cli/ (shipped, hooks/ is a
published sibling of dist/). Tests exercise the real shipped artifact, the
NPX_REF/mode-command parity scaffolding is dropped (one implementation can't
drift), and parity narrows to the two cjs copies staying byte-identical.

No behavior change: hook stale-index hints and the analyze warning are
byte-identical; the pre-existing setup.ts resolveGitnexusBin is untouched.

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

* fix(cli): bound stale-index hook PATH probe under the hook budget (U1)

The PostToolUse stale-index hint calls formatAnalyzeCommand(), which probes which/where; named PROBE_TIMEOUT_MS=2000 keeps git rev-parse (~3s) + up to two probes well under Claude Code's 10s hook timeout while preserving the machine-correct hint. Byte-identical in the plugin copy.

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

* fix(cli): steer generated cross-repo group commands off npx (#1939) (U2)

The Cross-Repo Groups block in generated AGENTS.md/CLAUDE.md still emitted bare 'npx gitnexus group ...', funneling npm-11 users into the arborist crash; switch to fixed 'pnpm dlx gitnexus@latest group ...'. Export generateGitNexusContent and add a group-branch test asserting no 'npx gitnexus' literal survives.

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

* docs: align steering guidance on pnpm dlx gitnexus@latest (U3)

README troubleshooting uses gitnexus@latest; the repo's own committed CLAUDE.md/AGENTS.md stale-index hint now matches the generated output (pnpm dlx gitnexus@latest analyze) so the repo dogfoods the fix.

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

* test(hooks): assert exact @latest analyze command and pin invocation mode (U4)

Drop dead PKG_VERSION/NPX_REF version-pinned constants; the cjs always emits gitnexus@latest, so assert exact toContain(...) instead of the /@\\S+/ wildcard; pin GITNEXUS_INVOCATION in the --embeddings tests for host-independent determinism.

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

* test(cli): cover resolver warn/edge branches; document probe seam (U5)

Add coverage for the gitnexus-mode warn suppression, getNpmMajorVersion edge inputs (empty/pre-release/non-numeric), and the Windows non-wrapper pickPathMatch branch; widen the InvocationResolver interface to document the optional probe param.

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

* fix(cli): lower hook PATH-probe timeout to 1000ms (U1)

In a linked worktree the stale-index hook runs git rev-parse --git-common-dir (~2s) + rev-parse HEAD (~3s) before up to two PATH probes; PROBE_TIMEOUT_MS=1000 holds the worst case near ~7s under Claude Code's 10s hook budget (was 2000, ~1s headroom). Byte-identical in the plugin copy.

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

* fix(cli): fail closed in gitnexus setup on missing required hook helper/adapter (U2)

copyHookHelpers now returns the failed REQUIRED helpers (the .cjs trio; win-rm-list-json.ps1 stays best-effort since it fails open). Both install paths skip hook registration with an actionable error when a required helper failed; the Claude path also gains the adapter-existence guard the Antigravity path already had. Prevents registering a hook that crashes MODULE_NOT_FOUND on every tool event.

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

* fix(skills): steer committed skill files off npx to pnpm dlx gitnexus@latest (U3)

All 26 committed skill-file copies (gitnexus/skills, .claude, plugin, cursor) used 'npx gitnexus analyze', contradicting the generated freshness line and funneling npm-11 users into the arborist crash. Replace with 'pnpm dlx gitnexus@latest analyze'; add a regression guard (skills-steering.test.ts) that globs all four locations and fails if any reintroduces it. The cli skill's non-analyze npx subcommands (status/clean/list/wiki) are left as-is (out of the analyze-funnel scope).

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

* fix(cli): guard resolver import shape; assert group-impact steering (U4)

Add a load-time guard on the createRequire(resolve-analyze-cmd.cjs) cast so a drifted/renamed cjs export fails loudly at module load instead of as a late TypeError in warnIfNpm11NpxRisk. Add the missing 'group impact' assertion to the ai-context Cross-Repo Groups test, and a resolver-contract test.

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

* fix(cli): auto-select invocation path with pnpm --allow-build (#1939)

Probe npm/pnpm versions and PATH to pick a working analyze command without
user configuration: global gitnexus first, pnpm dlx with --allow-build on
npm 11+ (Ladybug native scripts), npx on npm 10 and earlier. Update docs,
skills, and tests to match the canonical install-free command.

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

* fix(cli): place pnpm --allow-build before dlx, repair version-injection seam (#1939)

The auto-selected install command emitted `pnpm dlx --allow-build=… analyze`,
but pnpm < 10.14 keeps `dlx` in its argv escape list, so flags placed *after*
`dlx` are parsed as package specs and rejected (ERR_PNPM_SPEC_NOT_SUPPORTED) on
pnpm 10.2–10.13.x — strictly worse than the bare command. Move the flags before
`dlx` (the position pnpm has honored since 10.2.0) in both byte-identical hook
copies, the committed AGENTS.md / CLAUDE.md, and every skill tree.

Also repairs the CI-red resolveInvocationMode seam: injecting `{ npmMajor: null }`
to simulate an absent npm fell through `??` to the host's real `npm --version`
(npm 10.x on the CI runners → routed 'npx' instead of 'pnpm'). Use an
`'npmMajor' in deps` sentinel so an injected null is honored, drop the dead
parseMajorVersion guard, and gate the flags on pnpm >= 10.2 via a single
minor-aware probeVersion spawn (skipped for committed docs). Align the TS
getNpmMajorVersion timeout to the 1s hook budget and strengthen the
skills-steering guard with a pre-dlx positive assertion plus a post-dlx
regression check.

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

* docs: add npm-11 pnpm caveat to README Quick Starts (#1939)

The root, package, and cursor-integration README Quick Starts still steered
first-contact users to bare `npx gitnexus analyze` — the exact npm 11.x
arborist install crash issue #1939 names as a funnel. Add a one-line pnpm
`--allow-build … dlx` caveat (keeping the simple npx default for npm<=10 /
pnpm / yarn users); the package README points to its existing npm-11
workaround section.

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

* docs(skills): route every gitnexus-cli command off npx to pnpm dlx (#1939)

The gitnexus-cli skill demonstrated analyze via `pnpm --allow-build … dlx`
but still showed status/clean/wiki/list via bare `npx gitnexus` — the same
package, the same npm-11 crash-prone install path — and its header claimed
"all commands work via npx". Convert every subcommand to the pnpm form across
all three skill copies and reconcile the header. Broaden the skills-steering
guard to forbid any `npx gitnexus` command in the cli-skill copies.

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

* perf(hook): probe pnpm once on the stale-index path (#1939)

The stale-index hook resolved pnpm twice — `which pnpm` for mode selection
then `pnpm --version` for the allow-build gate — two spawns for one tool in a
~9s/10s budget. Capture the version once in formatAnalyzeCommand and thread it
through the existing deps seam (a successful `pnpm --version` proves presence),
sharing a memoized PATH probe with resolveInvocationMode. Add explicit pnpm
10.0-suppress / 10.2-emit boundary tests and relabel the unknown-minor case.
Both byte-identical cjs copies updated together.

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

* fix(setup): single-quote POSIX hook command + assert cliPath patch applied (#1939)

The hook `command` written into editor settings is shell-evaluated; the
double-quoted `node "<path>"` form left `$`, backtick, and other metacharacters
live in an adversarial $HOME. Single-quote the path on POSIX (Windows keeps the
double-quoted form — those chars are illegal in Windows filenames). Also assert
the cliPath source-literal replace() actually matched, recording an actionable
error on drift instead of silently shipping a hook with an unresolved relative
path.

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

* test(setup): normalize expected hook path for the Windows runner (#1939)

The new POSIX-escaping test built its expected hook path with path.join,
which emits backslashes on the Windows runner, while setup.ts forward-slash-
normalizes the path before quoting — so `expect(cmd).toBe(node '<path>')`
mismatched on tests/windows-latest. Normalize the expected path the same way.
Production code was already correct; only the test's expected value was
platform-fragile.

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

* fix(cli): steer docs/skills via a project-local runner, not a pnpm default (#1939)

The prior approach hardcoded `pnpm --allow-build=… dlx gitnexus@latest <cmd>`
into every committed skill + the generated AGENTS.md/CLAUDE.md, which assumes
pnpm is installed. Replace it with a CLI-neutral project-local runner:

- `gitnexus analyze` drops `.gitnexus/run.cjs` (a copy of the canonical
  `resolve-analyze-cmd.cjs`, which gains `buildRunnerArgv` + a `require.main`
  exec tail) next to the index. Docs/skills reference `node .gitnexus/run.cjs
  <cmd>`, which auto-selects the runner (global `gitnexus` → `pnpm dlx` → `npx`)
  at call time — no package-manager assumption. README first-run + an inline
  bootstrap note stay universal `npx gitnexus analyze`.
- The exec tail uses `shell` on Windows so `.cmd`/`.ps1`/`.exe` shims resolve
  (execFileSync can't otherwise; Node blocks `.cmd` without a shell,
  CVE-2024-27980), and prints a diagnostic instead of a silent exit 1.

Tests: runner exec-tail (real spawn, exit-code propagation + ENOENT diagnostic),
copy-failure graceful degradation, and per-subcommand routing + pnpm-fallback
vacuity guards. The generated CLAUDE.md block stays under the #856 token budget.

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

* fix(cli): resolve Windows .cmd version probes so pnpm steering fires (#1939)

probeVersion (and the TS getNpmMajorVersion mirror) spawned npm/pnpm
--version via execFileSync with no shell, so on Windows the .cmd shims
ENOENT'd, the probe reported a present tool as absent, and the stale-index
hook recommended the npx crash path #1939 exists to avoid. Add
shell: process.platform === 'win32' to the version probes (the exec tail
already does this). Parse the first version-shaped line so a Corepack/notice
banner on stdout no longer defeats the parse. Carry pnpm presence separately
from version so a present-but-unparseable pnpm still selects pnpm. Drop the
dead probe ?? resolveOnPath coalesce. Cover resolve-analyze-cmd.cjs (+ plugin
twin) with the shell-injection and windowsHide source-regression guards.

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

* fix(cli): widen pnpm allow-build for the --embeddings=N equals form (#1945)

buildRunnerArgv detected embeddings via gitnexusArgs.includes('--embeddings'),
which missed the equals form (--embeddings=5000) that Commander also accepts,
dropping --allow-build=onnxruntime-node on pnpm 10.2+. Match both forms.

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

* test(cli): cover the runner exec-tail Windows shell branch on CI (#1945)

runner-exec-tail.test.ts was POSIX-only and unregistered in
cross-platform-tests.ts, so the run.cjs Windows shell:true exec branch ran on
no platform despite the file comment claiming windows-latest covered it. Add a
.cmd-shim it.skipIf(onPosix) case and register the file in SPAWN_CLI so the
windows-latest job runs it.

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

* docs: fix broken troubleshooting anchor in gitnexus README (#1945)

The npm-11 quick-start note linked to #npx-gitnexus-crashes-with-nodetarget-is-null-npm-11,
which matches no heading; the actual troubleshooting heading slugifies to
#cannot-destructure-property-package-of-nodetarget-as-it-is-null. Repoint the link.

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

* test(hooks): guard resolve-analyze-cmd.cjs in antigravity e2e sanity check (#1945)

The antigravity adapter top-level require()s resolve-analyze-cmd.cjs, but the
beforeAll helper-presence loop did not check for it — a failed copy would
surface as noisy MODULE_NOT_FOUND in downstream tests instead of the intended
actionable 'Helper not installed' error. Add it to the loop.

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

* docs(skills): tie a missing-runner Cannot-find-module error to recovery (#1945)

Generated CLAUDE.md/AGENTS.md make `node .gitnexus/run.cjs` the primary
command, but the runner is gitignored, so a fresh clone or git clean leaves an
agent facing a raw MODULE_NOT_FOUND. The CLAUDE.md block is token-budget-capped
(#856), so the recovery guidance lives in the cli skill (its documented home):
the bootstrap note now names the `Cannot find module` error and points at
`npx gitnexus analyze` to (re)generate the runner.

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

* refactor(cli): disambiguate the MCP-pinned ref from the @latest hint (#1945)

setup.ts and resolve-analyze-cmd.cjs both exported a constant named NPX_REF
with different values (version-pinned for the persisted MCP entry vs.
gitnexus@latest for hints). Rename setup.ts's module-private constant to
MCP_PINNED_REF (value and behavior unchanged — the MCP pin stays pinned),
leaving the cjs hint ref and its re-export alone. Also route the createRequire
cast through 'unknown' so it reads as an explicit narrowing to the subset this
module uses rather than a claim about the cjs's full export shape.

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

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:00:34 +01:00
ManniX-ITA
39e9b40136
fix(windows): pass windowsHide:true to every child_process spawn-family call (#1794)
* fix(hooks): pass windowsHide:true to every spawnSync to suppress flashing console windows on Windows

On Windows, every PostToolUse and Stop event from Claude Code (and
the Cursor integration variant) cold-spawns ``node`` / ``npx.cmd`` /
``git`` / ``lsof`` through ``child_process.spawnSync``. Without
``windowsHide: true`` in the options, Node's child_process module
asks ``CreateProcess`` to use ``STARTF_USESHOWWINDOW`` with
``SW_SHOWDEFAULT``, and a black console window flashes onto the
user's desktop for the duration of the call. Under active
editor / agent use this means a near-continuous stream of pop-up
windows — unusable in practice (reported live on a Windows 11
workstation running the gitnexus Claude plugin against an active
project; the flashes stack on the taskbar and steal focus from the
editor).

The Node fix is one option flag per spawnSync:

    spawnSync(cmd, args, {
        encoding: 'utf-8',
        timeout,
        cwd,
        stdio: ['pipe', 'pipe', 'pipe'],
        windowsHide: true,            // <-- new
    });

``windowsHide`` is a no-op on macOS/Linux (Node docs: "Hide the
subprocess console window that would normally be created on Windows
systems"), so the patch is platform-neutral and zero-risk on the
other two majors.

This commit touches every ``spawnSync`` call in the three sources
that ship the hook layer:

* gitnexus/hooks/claude/gitnexus-hook.cjs            (4 sites)
* gitnexus/hooks/claude/hook-db-lock-probe.cjs       (3 sites)
* gitnexus-claude-plugin/hooks/gitnexus-hook.js      (6 sites)
* gitnexus-claude-plugin/hooks/hook-db-lock-probe.cjs (3 sites)
* gitnexus-cursor-integration/hooks/gitnexus-hook.cjs (3 sites)

Total: 19 spawn sites guarded. ``hook-lock.cjs`` / ``hook-lock.js``
don't spawn subprocesses; nothing else in the hooks/ dirs touches
``child_process``.

Verified on Windows 10 22H2 / Node 22.21 / gitnexus 1.6.5 by
installing the locally-built tarball and running an active Claude
Code session against a large mixed-language repo — no console
window appears for any hook fire (pre-fix: ~2-3 visible flashes per
edit). No behavioural change on Linux/macOS hosts.

* test(hooks): regression — every hook spawnSync paired with windowsHide:true

Source-level assertion that every ``spawnSync`` invocation in the
hook layer has a matching ``windowsHide: true`` in its options
object. Without the flag, Node's child_process module asks
CreateProcess to use STARTF_USESHOWWINDOW with SW_SHOWDEFAULT and
a black console window flashes onto the user's desktop for the
duration of each call — see the parent fix commit.

The check is source-level rather than behavioural because:

* the flag's effect is observable only on Windows;
* GitHub Actions runs vitest on Linux for the hook tests;
* regressing this is easy (every new spawnSync site has to remember
  to add the flag), and a runtime check on a Windows-only CI leg
  would still let a PR land on the main branch first.

Counts spawnSync occurrences and windowsHide:true occurrences per
file (in code, ignoring comments) and asserts equality. Five files
covered:

* gitnexus/hooks/claude/gitnexus-hook.cjs
* gitnexus/hooks/claude/hook-db-lock-probe.cjs
* gitnexus-claude-plugin/hooks/gitnexus-hook.js
* gitnexus-claude-plugin/hooks/hook-db-lock-probe.cjs
* gitnexus-cursor-integration/hooks/gitnexus-hook.cjs

Adding a new hook file requires updating the HOOK_FILES tuple. A
sanity assertion ``spawnCount > 0`` catches accidental deletion of
all spawn calls in a future refactor (would otherwise silently make
the count-equality assertion trivially true).

Sits next to the existing "no shell: true" and ".cmd extension"
regression tests in test/unit/hooks.test.ts — same shape, same
spirit.

* fix(src): extend windowsHide:true to every spawn-family call in cli/core/mcp/server

Companion to the hook-layer fix in this branch's first commit. The
same Windows console-window flash bug applies to every
``spawn`` / ``spawnSync`` / ``execFile`` / ``execFileSync`` /
``execFileAsync`` / ``execSync`` call in the source tree — not just
the hooks. The MCP local backend
(``src/mcp/local/local-backend.ts``) and the ``gitnexus serve`` git
helpers (``src/server/git-clone.ts``) are particularly bad because
they run from daemonized processes that have no parent console; the
spawned child auto-allocates one and it pops onto the user's
desktop. The CLI sites are less visible (the user is at a terminal
with an existing console; ``stdio: 'inherit'`` shares it) but the
flag is harmless there — windowsHide only suppresses NEW console
allocation, an inherited parent console is untouched. The visible
output of ``gitnexus analyze`` and friends is preserved verbatim.

The pre-existing fix at ``src/core/lbug/extension-loader.ts:96``
established the convention in this codebase. This commit applies it
uniformly.

Sites covered (21 new):

| File | Sites |
|---|---|
| src/cli/analyze.ts           | 1 |
| src/cli/setup.ts             | 2 |
| src/cli/wiki.ts              | 3 |
| src/core/embeddings/embedder.ts | 1 |
| src/core/git-staleness.ts    | 3 |
| src/core/run-analyze.ts      | 1 |
| src/core/wiki/cursor-client.ts | 2 |
| src/core/wiki/generator.ts   | 3 |
| src/mcp/local/local-backend.ts | 2 |
| src/server/git-clone.ts      | 2 |
| src/core/lbug/extension-loader.ts | (already had it, untouched) |

Combined with the 19 hook sites from the first commit + the 1
pre-existing extension-loader site, the codebase now has uniform
``windowsHide: true`` on every spawn-family call.

Behavioural notes:

* ``windowsHide`` is documented by Node as a no-op on POSIX —
  Linux/macOS hosts see byte-identical behaviour.
* ``stdio: 'inherit'`` callers (e.g. ``cli/wiki.ts:522`` opens the
  editor in the user's terminal) keep their interactive UX. The
  child inherits the parent's stdio handles; no new console is
  allocated; the flag has nothing to hide.
* Piped callers (``stdio: ['pipe',…]``) continue to deliver every
  byte of stdout/stderr back to the parent for the parent to log
  / process / re-print. No output is swallowed.
* ``execSync`` / ``execFileSync`` callers that previously had no
  ``stdio`` option (e.g. ``generator.ts:887`` ``execSync('git
  rev-parse HEAD', { cwd })``) keep their default pipe semantics
  (``.toString()`` still works) — windowsHide is added alongside
  the existing ``cwd`` option.

Verified on Windows 10 22H2 / Node 22.21 by installing the locally
built tarball and exercising:

* MCP detect_changes via the local backend → no flash.
* gitnexus serve → no flash on git clone/clone-pull.
* gitnexus analyze interactively → output appears in terminal as
  before, no extra window.

* test(windowsHide): extend regression to every spawn-family call in src/

Companion to the src/ patch. The hooks.test.ts regression now
covers 16 files (5 hooks + 11 source files), and asserts the
invariant for every spawn-family function — not just spawnSync.

Changes:

* Generalise countSpawnCalls() to also count spawn, execFile,
  execFileSync, execFileAsync, execSync (the entire spawn-family
  surface of child_process). Skip method calls (e.g. RegExp.exec)
  via a negative-lookbehind on ``.``.
* Add SRC_FILES table with all 11 source-tree files that import
  spawn-family functions from child_process.
* Loop over [...HOOK_FILES, ...SRC_FILES] so a regression in any
  file fails the same test name.
* Tighten the assertion to ``hideCount >= spawnCount`` rather
  than strict equality, because some sites (e.g. setup.ts:534
  using execFileAsync via shell:true on Windows) may legitimately
  add windowsHide to nested option objects in future refactors.
* Sanity gate ``spawnCount > 0`` per file catches a refactor
  that deletes all spawn calls (would otherwise make the
  assertion trivially true).

Manually exercised against the patched repo:
  16 files, 28 total spawn-family calls, 28 windowsHide:true.
  All pass.

The convention to keep this list in sync: every new file in
gitnexus/src/ that imports from 'child_process' must be added to
the SRC_FILES tuple. The cost is one line per file; the benefit
is the next contributor never has to think about windowsHide
again — the test will catch a miss before merge.

* style: prettier --write on storage/git.ts + hooks.test.ts

CI quality / format job flagged two formatting issues in the
merge-resolution commit: a long single-line options object in
storage/git.ts and similar in hooks.test.ts. prettier --write
fixes both with the project's standard wrap-and-trailing-comma
style. No semantic change.

* test(git): include windowsHide in toHaveBeenCalledWith assertion

The merge-resolution commit added windowsHide:true to the
'git rev-parse --is-inside-work-tree' execSync call in
src/storage/git.ts, but the matching strict-shape assertion in
git.test.ts:31-34 still expected the pre-patch two-key options
object {cwd, stdio}. vitest's toHaveBeenCalledWith does a deep
structural match, so the extra third key flipped the assertion
to fail.

Add windowsHide: true to the expected shape. Only this one
assertion is strict; the two siblings ('passes the correct cwd'
and the no-cwd-arg case) use expect.objectContaining and
expect.any(String) and remain green without modification.

* test(setup-codex): include windowsHide in execFile shape assertions

Same root cause as the git.test.ts fix on this branch: the windowsHide
patch added windowsHide:true to the execFile() options in
src/cli/setup.ts, but three strict-shape toHaveBeenCalledWith
assertions in setup-codex.test.ts still expected the pre-patch
{shell:true} / {shell:false} two-key options. vitest does a deep
structural match, so the extra key flipped the assertions to fail
on every CI matrix leg (ubuntu coverage + macos + windows).

Adding windowsHide:true alongside the existing 'shell' key in
all three sites.

* ci: retrigger checks

go-parity failed on a flaky onnxruntime-node postinstall network timeout
(AggregateError [ETIMEDOUT] in node ./script/install), which cascaded into
the CI Gate. No code change — empty commit to re-run the pipeline.

* fix(test): strengthen windowsHide regression assertions (PR #1794 review)

- Replace toBeGreaterThanOrEqual with exact toBe per DoD §2.7
- Remove unused `m` variable in countSpawnCalls (CodeQL finding)
- Add windowsHide: true to runGit test helper for consistency

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: ManniX-ITA <35522085+ManniX-ITA@users.noreply.github.com>
Co-authored-by: Test <test@example.com>
2026-05-24 09:51:21 +01:00
Shane Thurston Wijaya
105efd0f7c
feat(wiki): added --lang <lang> flags to gitnexus wiki for multilanguage wiki generation support (#1613) 2026-05-17 19:54:02 +01:00
Copilot
ed50a6729f
fix(wiki): Remove the hidden 60s default timeout, validate gitnexus wiki timeout/retry flags, and surface timeout errors (#1651) 2026-05-17 12:03:54 +01:00
Derek Pearson
89c03b2ebb
fix: skip Claude augment hook when GitNexus server owns DB (#1493)
* fix(claude): skip augment hook when server owns db

* chore(autofix): apply prettier + eslint fixes via /autofix command

* fix(hooks): cross-platform DB lock probe for MCP owner guard

Extract hook-db-lock-probe.cjs with a single hasGitNexusDbLockedByGitNexusServer
entry point used by both Claude hooks:

- Linux: scan /proc/<pid>/fd via dev+inode (no lsof required), optional lsof
  fallback; GITNEXUS_HOOK_LINUX_PROC_BUDGET_MS caps scan time
- macOS and other Unix: trusted lsof + ps (absolute paths / env overrides)
- Windows: Restart Manager + Win32_Process via win-rm-list-json.ps1 and
  GITNEXUS_HOOK_POWERSHELL_PATH

Update hooks.test.ts source coverage for the probe module.

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

* Update gitnexus/hooks/claude/win-rm-list-json.ps1

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Apply suggestion from @github-actions[bot]

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(gitnexus): repair package.json JSON after malformed engines edit

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

* Update Node.js engine version requirement to 22.0.0

* Update Node.js engine version to >=22.0.0

* fix(hooks): address ce-code-review findings on PR #1493

P0:
- Replace malformed `RM_UNIQUE_PROCESS` block in
  `gitnexus/hooks/claude/win-rm-list-json.ps1` (duplicate struct decl +
  duplicate `ProcessStartTime` + unbalanced braces) with a single
  well-formed `[StructLayout(LayoutKind.Sequential, Pack = 4)]` struct,
  so PowerShell `Add-Type` actually compiles and the Windows DB-lock
  probe stops fail-open on every machine.
- `gitnexus/src/cli/setup.ts` now copies `hook-db-lock-probe.cjs` and
  `win-rm-list-json.ps1` into the user's `~/.claude/hooks/gitnexus/`
  alongside `hook-lock.cjs`, preventing the `MODULE_NOT_FOUND` thrown
  by `gitnexus-hook.cjs:18`'s top-level require on every fresh install.
  `gitnexus/test/unit/setup.test.ts` extended to assert both new copy
  destinations.
- Four fail-open hook tests (`ENOENT lsof`, `npx parent line`,
  `non-GitNexus ps line`, `ps ENOENT`) now seed `createHookToolDir`
  with a valid `[GitNexus]` stderr line so
  `expect(parseHookOutput).not.toBeNull()` actually holds on CI.

P1:
- Plugin copy of `win-rm-list-json.ps1` gains `Pack = 4` so its CLR
  struct matches the 12-byte native `RM_UNIQUE_PROCESS` layout
  (multi-blocker `RmGetList` no longer reads mangled `dwProcessId`).
- `GITNEXUS_HOOK_CLI_PATH = ''` now falls through to the resolution
  chain in `gitnexus-hook.cjs`, matching the plugin copy and removing
  the twin-file divergence on empty-string envs.
- Lock-warning suppression test seeds `gitnexusMarkerPath` and asserts
  the augment subprocess actually ran, plus `GITNEXUS_DEBUG=1`
  preserves the full discarded prefix.
- MCP-owner skip branch in both hook copies now emits
  `[GitNexus] augment skipped: MCP server owns DB` on stderr, so
  agents can distinguish intentional skip from silent failure.

P2:
- `ps` loop in `hook-db-lock-probe.cjs` fails-closed on `ETIMEDOUT`
  to mirror the `lsof` handling (symmetric subprocess-probe contract).
- `RmStartSession` return value captured in both `.ps1` copies; exits
  early with `[]` on non-zero so subsequent RM API calls don't operate
  on an invalid handle.
- Windows RM-list `.ps1` encoded cache distinguishes uninitialized
  (`undefined`) from load-failed (`null`) with a one-shot
  `GITNEXUS_DEBUG` warning instead of silently caching empty string.
- `createHookToolDir` helper accepts `lsofOutputLines` and
  `psOutputByPid`; the multi-PID test uses them instead of duplicating
  the fake-binary construction inline.
- All five skip-path tests now assert `result.status === 0` and the
  new skip-signal stderr line.
- `AGENTS.md` documents the seven hook configuration env vars
  (`GITNEXUS_HOOK_CLI_PATH`, `_LSOF_PATH`, `_PS_PATH`,
  `_POWERSHELL_PATH`, `_LINUX_PROC_BUDGET_MS`, `_RM_TARGET`,
  `GITNEXUS_DEBUG`).
- `GITNEXUS_DEBUG` path in `gitnexus-hook.cjs`/`.js` writes the full
  discarded stderr prefix instead of a 180-char preview.
- Inline comment in `hook-db-lock-probe.cjs` explains the intentional
  Windows ETIMEDOUT fail-closed semantics.
- Removed the unnecessary `as WriteFileOptions` cast and orphaned
  `import type { WriteFileOptions }` in `hooks.test.ts`.

P3:
- `isGitNexusServerCommand` unexported from
  `hook-db-lock-probe.cjs` (kept as private helper).
- Env-path overrides (`GITNEXUS_HOOK_CLI_PATH`,
  `_POWERSHELL_PATH`, `_LSOF_PATH`, `_PS_PATH`) require
  `fs.existsSync` before being returned, so typos / stale config fall
  through to the standard resolution chain.

Misc:
- `gitnexus/package.json` engines.node back to `>=22.0.0` (matches
  origin/main and the original PR reviewer's earlier request).

Twin-tree parity / CI sync mechanism tracked separately at
abhigyanpatwari/GitNexus#1591.

Test plan: vitest run test/unit/hooks.test.ts → 113 passed,
18 Unix-only skipped; setup.test.ts → 14 passed.

* chore(autofix): apply prettier + eslint fixes via /autofix command

* trigger

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 16:39:30 +01:00
Shane Thurston Wijaya
88d3df77cc
feat:(wiki) added --timeout and --retries flags for large module pages to mitigate timeout aborts (#1543)
* feat:(wiki) added --timeout and --retries flags for large module pages to mitigate timeout aborts

* docs(wiki): document --timeout and --retries options

* docs(wiki): document --timeout and --retries in SKILL.md

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-05-13 18:35:39 +01:00
Abhigyan Patwari
ec4624af87
fix(hooks): cap concurrent augment subprocesses (#1486) (#1510)
* fix(hooks): cap concurrent augment subprocesses to prevent runaway process spawn (#1486)

When Claude Code fires PreToolUse hooks for parallel Grep/Glob/Bash tool
calls, each invocation spawned its own `gitnexus augment` subprocess —
a Node + LadybugDB cold start that holds resources for several seconds.
Under heavy parallel search load (issue #1486: 180+ piled-up processes,
load avg > 100), these accumulated faster than they completed because
nothing capped concurrent in-flight augments.

Add a lockfile-based concurrency guard under `<.gitnexus>/.hook-locks/`:
each running hook claims a `<pid>.lock`, the guard counts live PIDs and
prunes stale entries (>30s mtime or pid no longer alive), and bails
silently when MAX_INFLIGHT (3) is reached. Augment is best-effort
enrichment — missing a few fires under burst load is preferable to
melting the system.

Applied to all three hook variants that spawn augment:
- gitnexus/hooks/claude/gitnexus-hook.cjs (npm-installed Claude hook)
- gitnexus-claude-plugin/hooks/gitnexus-hook.js (plugin Claude hook)
- gitnexus-cursor-integration/hooks/gitnexus-hook.cjs (Cursor hook)

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

* fix(hooks): make augment concurrency cap a hard cap via atomic slot files

Address Claude's review of #1510. The original count-then-claim guard had
a TOCTOU window: N hooks could each read `active < MAX_INFLIGHT` between
readdirSync and the per-pid `wx` write and all proceed, briefly exceeding
the cap. The PR title's "cap" language overstated this.

Replace with fixed-name `slot-0.lock` ... `slot-N.lock` under `.hook-locks/`.
`O_CREAT|O_EXCL` on a fixed path is OS-atomic — exactly one process wins
each slot, so the cap is hard regardless of burst arrival timing. Each
slot file contains the owning PID so stale-takeover still works when a
hook crashes without releasing.

PID liveness is checked before age (Claude's Finding 3): a slow-but-alive
hook is never wrongly evicted. The 30s age window only kicks in to defend
against PID reuse on a long-abandoned slot, well above the 7s augment
timeout so a healthy run never hits it.

Also adds the missing concurrency-guard tests to cursor-hook.test.ts
(Claude's Finding 2): source-level wiring + dead-PID reclaim + 3-slots-full
bail. Previously only the CJS and Plugin variants had test coverage for
the guard; the Cursor variant was validated only by code inspection.

Tests: 5726 passing, +9 from baseline (1 hard-cap burst test + 4 source
regressions in hooks.test.ts; 3 source + 2 integration in cursor-hook.test.ts).

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

* fix(hooks): inspect slot mtime + content via single fd (codeql TOCTOU)

CodeQL flagged the stale-takeover path in acquireHookSlot as a potential
filesystem race (js/file-system-race): statSync(slotPath) followed by
readFileSync(slotPath) gives a TOCTOU window where the file could be
swapped between the metadata check and the content read.

Replace the two separate path-based calls with a single openSync + fstatSync
+ readSync + closeSync sequence. Both mtime and owner PID now come from the
same file descriptor, so the operations are atomic on one inode. No
behavioral change beyond closing the race.

Applied to all three hook variants (CJS, Plugin, Cursor).

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

* fix(hooks): distinguish EPERM from ESRCH in PID liveness check

Cursor Bugbot caught a contradiction with the stated design: the bare
`catch` after `process.kill(owner, 0)` was treating EPERM (process exists
but owned by another user) the same as ESRCH (process gone), which would
evict a live slot whenever the lock dir straddled user boundaries.

Inspect the error code: ESRCH → dead, evict; EPERM → still alive, keep
the slot; anything else → assume alive (be conservative under unexpected
failure rather than over-evict).

Applied to all three hook variants.

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

* fix(hooks): fail closed when lock dir cannot be created

Previously the mkdirSync catch in acquireHookSlot returned `() => {}`
(a truthy no-op). The caller checks `if (!release) return;` to skip
augment when the guard can't be established — but a truthy no-op
slipped through that check and let augment spawn unguarded. On a
cross-user shared `.gitnexus/` or read-only filesystem, N concurrent
hooks would each take that branch and reintroduce the #1486 fan-out
the guard exists to prevent.

Return `null` instead so the caller's `if (!release) return;` skips
augment cleanly. Augment is best-effort enrichment — skipping it when
the guard fails is strictly safer than running unguarded.

Also clarify the stale-slot comment: PID-liveness wins for slots
younger than HOOK_LOCK_STALE_MS, but age is the final arbiter beyond
30s (PID-reuse defense). The previous wording said "PID-liveness wins
over age" without qualifying it, which contradicted the >30s branch.

Add source-level regression tests in hooks.test.ts and
cursor-hook.test.ts asserting acquireHookSlot returns null (not
() => {}) on lock-dir failure. Note in the Cursor test file that the
10-spawner burst test is not duplicated because the algorithm is
byte-for-byte identical to the CJS hook and already covered there.

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

* refactor(hooks): extract lock guard into helper modules

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/04dd20c5-28fd-433a-83cf-ad83fd03fb32

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-13 08:56:27 +01:00
sburdges-eng
b79278705a
fix(hook): resolve canonical repo root + guard read-only FTS ensure (#1226)
* fix(hook): resolve canonical repo root + guard read-only FTS ensure (#1224)

Two bugs in the Claude Code hook + query layer integration:

1. `findGitNexusDir` (in `gitnexus/hooks/claude/gitnexus-hook.cjs` and
   `gitnexus-claude-plugin/hooks/gitnexus-hook.js`) walked upward from
   cwd looking for a non-registry `.gitnexus/`. In linked git worktrees
   created via `git worktree add`, the canonical repo's `.gitnexus/`
   never sits above the worktree path, so the walk silently fails and
   neither augmentation nor staleness notifications fire.

   Fix: keep the cwd-walk as the fast path, then fall back to
   `git rev-parse --git-common-dir` to resolve the shared `.git/`
   directory (which lives inside the canonical repo across all linked
   worktrees) and walk up from its parent. Returns null cleanly when
   `git` isn't on PATH or cwd isn't inside any working tree.

2. `ensureFTSIndex` in the LadybugDB adapter rethrew when the active
   connection is read-only (e.g. the MCP query pool, which opens DBs
   read-only by design). Defensive callers used to surface five
   "Cannot execute write operations in a read-only database" warnings
   per query.

   Fix: extract `isReadOnlyDbError` (mirroring the existing
   `isDbBusyError` discriminator) and have `ensureFTSIndex` catch the
   read-only error, cache the key, and return silently. Index creation
   is owned by `gitnexus analyze` on a writable connection — the
   ensure call is safely a no-op on the read pool. Lock / busy /
   "already exists" / schema errors continue to propagate.

Tests:
- `test/unit/hooks.test.ts`: new "Linked git worktree resolution"
  block exercises both hooks against a real linked worktree to confirm
  PostToolUse stale notifications fire, plus a negative case when the
  canonical repo has no `.gitnexus/`.
- `test/unit/lbug-readonly-error.test.ts`: new file unit-tests the
  `isReadOnlyDbError` discriminator (positive matches, case
  insensitivity, non-Error inputs, and unrelated errors that must
  still surface — lock contention, "already exists", schema misses).
- `test/integration/lbug-core-adapter.test.ts`: extends the existing
  FTS coverage with an idempotency assertion for `ensureFTSIndex` to
  pin the read-only guard's success-path contract.

Verified with `npx tsc --noEmit` and `vitest run` on the affected
files (hooks + readonly + lbug-core-adapter + bm25-search +
lbug-extension-loader + lbug-embedding-hashes — 136 tests pass).
Build: `npm run build` succeeds.

Closes #1224

* fix(local-backend): cover supported vector path

Add the supported-platform regression assertion for QUERY_VECTOR_INDEX and align the unsupported VECTOR diagnostic wording with platform policy.

Made-with: Cursor

---------

Co-authored-by: Gergo Magyar <gergomagyar@icloud.com>
2026-04-30 18:12:03 +01:00
CauchYoung
86abc01445
fix(hooks): ignore global registry during staleness checks (#1141)
* fix(hooks): ignore global registry during staleness checks

* test(hooks): cover indexed repos under global registry

---------

Co-authored-by: laplace young <yangqk12@whu.edu.cn>
2026-04-28 09:39:18 +01:00
Copilot
2b0392cd83
feat(analyze): preserve existing embeddings by default; --force regenerates them; add --drop-embeddings opt-out (CLI + HTTP API) (#1055)
* Initial plan

* fix(analyze): preserve existing embeddings by default; add --drop-embeddings opt-out

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/da1da041-afcd-4d38-8a2f-39ca52a462ff

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

* analyze: --force on embedded repo now regenerates embeddings (preserve+top-up)

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/e2759765-b8f6-453a-8c28-595439d23cb4

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

* analyze: wire dropEmbeddings into HTTP API; log cache-load failures; extract pure deriveEmbeddingMode + behavioral tests; sync GUARDRAILS.md

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/7d88e595-cbd8-47b2-ba4f-fb5b9a60cda4

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
2026-04-24 13:07:40 +01:00
Gergő Magyar
bf09eab95b
feat: configure prettier with pre-commit hook (#563)
* feat: configure prettier with pre-commit hook integration

Add prettier, lint-staged, and prettier-plugin-tailwindcss at the repo
root with husky pre-commit hook integration. Moves husky from
gitnexus/ to root package.json for reliable hook installation.

- Root package.json with prepare/format/format:check scripts
- .prettierrc with endOfLine:lf and tailwindStylesheet for TW v4
- .prettierignore excluding fixtures, vendor, generated, *.d.ts, *.md
- .gitattributes enforcing LF line endings for Windows consistency
- Pre-commit hook uses direct node_modules/.bin/ paths (no npx)

* style: apply prettier formatting to entire codebase

One-time bulk format. No logic changes.
Use .git-blame-ignore-revs to skip this commit in git blame.

* chore: add .git-blame-ignore-revs for prettier format commit

* perf: pre-commit hook runs only tests related to staged files

Use vitest --related to scope test execution to tests that import
the changed files, instead of running the full suite on every commit.

* perf: remove vitest from pre-commit hook, keep in CI only

Pre-commit now runs lint-staged + tsc only. Tests run in CI
(ci-tests.yml) where they belong — keeps commits fast.

* ci: add prettier format check to quality workflow

PRs will now fail if code isn't formatted with prettier.
2026-03-28 14:58:04 +00:00
Candido Sales Gomes
5a5850832c
refactor: migrate from KuzuDB to LadybugDB v0.15 (#275)
* refactor: migrate from KuzuDB to LadybugDB v0.15

KuzuDB was archived (Apple acquisition, Oct 2025). LadybugDB is the
community fork with full API compatibility.

- Package swap: kuzu → @ladybugdb/core, kuzu-wasm → @ladybugdb/wasm-core
- Rename all internal paths: kuzu → lbug (adapters, schema, storage)
- Storage path: .gitnexus/kuzu → .gitnexus/lbug (with auto-cleanup)
- Add explicit VECTOR extension loading (required in v0.15)
- Update CI workflow, documentation, and all tests
- 1151 unit + 27 integration tests passing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address code review findings (P1-P3)

P1: Fix WASM adapter to use getAll() API, wire cleanupOldKuzuFiles
into analyze command, add symlink path traversal protection.
P2: Cache VECTOR extension load state, batch augmentation engine
queries (20→4), fix web getCopyQuery for multi-language tables,
fix stale KuzuDB references, correct brainstorm package names.
P3: Complete lbug-wasm.d.ts type declarations, batch semantic
search per-label, update stale BM25 comment.

* chore: remove outdated KuzuDB migration brainstorming document

* fix: load FTS extension in MCP pool adapter on init

The read-only pool adapter never loaded the FTS extension, so all
QUERY_FTS_INDEX calls failed silently. This broke search-pool and
augmentation integration tests, and caused empty results in the
web UI server mode.

* feat: implement shared Database caching and connection reference counting

* feat: enhance KuzuDB migration handling and status reporting

* fix: mock cleanupOldKuzuFiles in local backend callTool tests

* fix: update mock for cleanupOldKuzuFiles and adjust imports in callTool tests

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 15:53:01 +00:00
Linus Beckhaus
c4eaf45ab1
feat(hooks): auto-reindex notification with cross-platform hardening (#205)
Adds PostToolUse hook that detects stale GitNexus index after git mutations (commit, merge, rebase, cherry-pick, pull) and notifies the agent to reindex. Uses lightweight staleness check (git rev-parse HEAD vs meta.json) instead of running gitnexus analyze synchronously, avoiding KuzuDB corruption and 120s blocks. Security and cross-platform hardening: remove shell:true from all spawnSync calls, use .cmd extensions on Windows, add path.isAbsolute(cwd) guards, fix setup.ts path escaping with JSON.stringify, use sendHookResponse() consistently. Includes 73 regression tests.
2026-03-07 08:59:54 +00:00
abhigyanpatwari
20ebd6b781 feat: security hardening, MCP improvements, skills, hooks, and CLI updates
- Export security primitives (CYPHER_WRITE_RE, isWriteQuery, isTestFilePath,
  VALID_NODE_LABELS, VALID_RELATION_TYPES) from local-backend
- Improve MCP kuzu-adapter with better query handling
- Add PR review skill for Claude, Cursor, and npm package
- Add CLI guide and CLI skills
- Update hooks for Claude plugin and Cursor integration
- Remove deprecated claude-hooks.ts CLI module
- Update eval-server, setup, and analyze CLI commands
- Improve CSV generator and ingestion processors
- Update CLAUDE.md and AGENTS.md configs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:13:42 +05:30
abhigyanpatwari
39b01f101e feat(skills): rewrite skill descriptions for better auto-invocation
Skill descriptions were too tool-centric ("using knowledge graph", "blast
radius") which prevented Claude Code from matching them to user intent.
Rewritten to user-intent-driven format with "Use when..." phrasing and
example trigger phrases so Claude can semantically match user requests.

Updated across all 3 sources: gitnexus/skills/, gitnexus-claude-plugin/skills/,
.claude/skills/, and the ai-context.ts fallback generator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:24:41 +05:30
abhigyanpatwari
2a444acf1d fix(plugin): hook logic bug and version mismatch
- Fix hook exit status check: || → && so failed augment errors
  don't get injected into Claude's context as graph results
- Add exit status check to npx fallback (same bug)
- Update plugin version from 1.2.11 to 1.3.3 in both
  marketplace.json and plugin.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:08:32 +05:30
Linus Beckhaus
238abbd947 refactor(skills): prefix all skill names with gitnexus- for disambiguation
Skill folder names determine invocation paths in Claude Code plugins
(e.g. plugin:gitnexus:gitnexus-cli). Generic names like "cli" or
"debugging" could collide with other plugins, so prefix them all with
gitnexus- for clarity.

Updated across plugin dirs, main package source files, ai-context.ts
generator, setup.ts installer, and all CLAUDE.md/AGENTS.md routing tables.
2026-02-25 14:15:42 +01:00
Linus Beckhaus
4f4fe9e587 update version 2026-02-25 13:40:49 +01:00
Linus Beckhaus
dbf3495713 fix(skills): remove gitnexus- prefix from skill frontmatter names
Skill names should match folder names since the plugin namespace
(gitnexus:) already provides context. Avoids redundant display like
gitnexus:gitnexus-cli → now gitnexus:cli.
2026-02-25 13:39:25 +01:00
Linus Beckhaus
5b8ce44537 format: format skills 2026-02-25 13:36:14 +01:00
Linus Beckhaus
ffc4b69004 feat(plugin): add marketplace, fix manifest, and add CLI commands skill
Add .claude-plugin/marketplace.json at repo root so users can permanently
install via `/plugin marketplace add nicosxt/gitnexus`. Remove invalid
hooks/mcpServers fields from plugin.json (auto-discovered at default
locations). Add cli skill covering all agent-relevant CLI commands
(analyze, status, clean, wiki, list) with correct flags. Update guide
skill and CLAUDE.md generator routing tables.
2026-02-25 13:30:47 +01:00
Linus Beckhaus
397dad8ec4 feat(plugin): transform Claude Code plugin into self-contained installable package
Bundle MCP server config (.mcp.json), fix hook to use spawnSync with npx
fallback and read stderr (KuzuDB stdout workaround), wire plugin.json with
hooks/mcpServers paths, add guide skill with tools/resources/schema reference,
add AMP-compatible mcp.json to all skill dirs, and slim CLAUDE.md generator
by moving reference content into the guide skill.
2026-02-25 11:47:16 +01:00
abhigyanpatwari
eca55aacd7 fixed resource count multiplying issue ( using resource templates now ) 2026-02-13 21:28:36 +05:30