Collapse release-candidate.yml into publish.yml so there is exactly one workflow that publishes gitnexus to npm, creates GitHub Releases, and triggers Docker builds — for both release candidates and stable releases. Closes#1609 architecturally.
A first-stage `route` job classifies push-to-main / push-tag / workflow_dispatch into `rc` / `stable` modes and fails closed on malformed shapes. RC path runs rc-guard → ci.yml → publish (mint GitHub App token → checkout with persist-credentials:false → resolve next rc version → atomic v-tag + rc/<SHA> marker push → vtag integrity gate → npm publish via OIDC → GitHub prerelease → if: failure() cleanup) → docker.yml. Stable path verifies package.json matches the tag and publishes to `latest` via OIDC (no docker).
Hardening:
• Self-trigger prevention via negative-glob `tags: ['v*', '!v*-rc.*']` — the bug class behind #1609 cannot recur.
• Two distinct actions/checkout steps per mode (no conditional `token:` expression footgun).
• Workflow-level `permissions: {}` deny-all + per-job grants; `id-token: write` only where OIDC is used.
• npm Trusted Publishing replaces NPM_TOKEN (delete the secret after the first successful publish).
• GitHub App installation token (actions/create-github-app-token@v3.2.0) replaces the long-lived RELEASE_PUSH_TOKEN PAT (delete after first successful RC).
• vtag integrity gate fails closed on empty / mode-mismatched output (prevents Release named `main` from a github.ref fallback).
• Annotation-injection sanitization on every logged ref.
• Explicit `secrets:` passthrough on docker.yml (DOCKERHUB_USERNAME, DOCKERHUB_TOKEN); ci.yml no longer inherits anything.
• `if: failure()` cleanup auto-deletes v-tag + rc-marker on partial failure (eliminates the external-consumer phantom-version ingestion window).
• ACTIONS_STEP_DEBUG window closed via `set +x` wrap on the inline auth-header compute.
• Curated retry-loud error handling on `gh api` bot-user-id lookup and `npx semver`.
Pre-merge validation:
• 10-reviewer multi-agent code-review pass; 14 findings fixed inline (commit 820cefae), 6 deferred to follow-ups.
• End-to-end dry-run rehearsal via workflow_dispatch (run 25919563064) validated route classification, rc-guard, App token mint, RC checkout, version resolver, vtag synthetic-regex check, and faithful tarball pack at the bumped version.
• All zizmor findings on the unification commits closed.
• Branch-protection required checks all green.
Post-merge actions:
• After the first successful RC, delete the `NPM_TOKEN` and `RELEASE_PUSH_TOKEN` secrets — they are no longer used.
• The first real RC after merge is the live-fire test for steps dry-run could not exercise (atomic tag push, real npm OIDC handshake, GitHub Release creation, docker.yml under explicit secrets passthrough). The if: failure() cleanup step handles the partial-failure recovery automatically; the Rollback Runbook in CONTRIBUTING.md covers the rare cases auto-cleanup can't reach.
* 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>
* feat(cursor): upgrade hooks to Cursor 2.4 postToolUse for Read/Grep/Shell coverage
Cursor 2.4 (released 2026-01-22) shipped generic preToolUse/postToolUse hooks
matching `Shell|Read|Write|Grep|Delete|Task|MCP:<tool>`, replacing the
2.3-era beforeShellExecution hook that only fired on shell commands. The
existing integration only intercepted the shell path, so Cursor users got
graph augmentation roughly 10% as often as Claude Code users — only when
the agent dropped to rg/grep instead of using its native Read/Grep tools.
This swaps the integration over to postToolUse and ports the bash+jq
hook script to cross-platform Node:
- gitnexus-cursor-integration/hooks/hooks.json: registers a single
postToolUse hook matching Shell|Read|Grep that invokes the new
gitnexus-hook.cjs.
- gitnexus-cursor-integration/hooks/gitnexus-hook.cjs: new Node hook
mirroring the safety patterns from the Claude hook (absolute-cwd
validation, .gitnexus discovery with linked-worktree fallback,
npx.cmd on Windows, end-of-options `--` marker, debug truncation,
graceful failure). Extracts the search pattern per tool kind:
Grep -> toolInput.query; Read -> file basename stripped to identifier
chars; Shell -> existing rg/grep arg parser. Emits Cursor-shape
`{ "additional_context": "..." }` on stdout — no shell, no jq.
- gitnexus-cursor-integration/hooks/augment-shell.sh: removed (Windows
incompatible, narrower coverage).
- gitnexus/test/unit/cursor-hook.test.ts: 33 regression tests covering
manifest wiring, source-level invariants (no shell:true, npx.cmd,
isAbsolute, additional_context output shape, end-of-options marker),
extractPattern coverage per tool, and behavioral early-exit paths
(empty/invalid stdin, relative cwd, no .gitnexus, unknown tool name,
short patterns, non-search shell commands, case-insensitive matching).
- README.md / gitnexus/README.md: editor-support table now lists Cursor
as Full / hooks=Yes (postToolUse), matching reality.
- gitnexus/src/cli/augment.ts and gitnexus/src/core/augmentation/engine.ts:
doc-strings updated from `Cursor beforeShellExecution` to
`Cursor postToolUse`.
Closes#1466.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cursor): hook timeout is in seconds, not milliseconds
Cursor's `timeout` field in hooks.json is in seconds (per
https://cursor.com/docs/agent/hooks and the original integration's
`"timeout": 5`). I'd written `10000` after blindly copying the issue
body's example — that resolves to ~2.8 hours, not 10 seconds. If the
script ever hangs before reaching its inner spawnSync timeouts (e.g.
during stdin read), Cursor would have waited that long before killing
it.
Drop to `10` (seconds), matching the Claude plugin's hooks.json and
giving plenty of headroom over the inner 7s augment-CLI timeout.
Add a regression-guard assertion in cursor-hook.test.ts so a future
ms/s mixup fails fast.
Reported by Cursor Bugbot on PR #1467.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cursor): address Claude review findings — payload aliases, debug, install docs
Resolves three findings from Claude reviewer on PR #1467:
1. Cursor payload field-name uncertainty (SIGNIFICANT)
Claude flagged that the Grep `query` field is an unverified assumption
per Cursor 2.4 docs (https://cursor.com/docs/agent/hooks). Mitigated:
- Expanded Grep aliases: query | pattern | regex | q | search | searchQuery
- Added pickLongestStringValue() last-resort fallback so the hook
extracts *something* even if Cursor renames every documented field
- Added GITNEXUS_DEBUG=1 stderr logging of the raw stdin payload so
users can capture Cursor's actual contract when diagnosing silent
no-ops, and report it back if aliases drift
- Added Read alias `filePath` (camelCase variant alongside `file_path`)
- Inline comment block citing the docs URL and the uncertainty
2. Hook command path resolution + install docs (SIGNIFICANT)
Claude flagged `node ./hooks/gitnexus-hook.cjs` as relative without
documented install path. Added gitnexus-cursor-integration/README.md
with explicit install steps:
- .cursor/hooks.json + hooks/gitnexus-hook.cjs at project root
- Confirms Cursor's project-root CWD convention with doc link
- Verify steps including GITNEXUS_DEBUG capture
- Pattern-extraction contract table per tool
- Troubleshooting: not-firing, npx fallback, wrong-pattern diagnosis
3. README "Full" overclaim for Cursor (MODERATE)
Both README rows now read `Yes (postToolUse, manual install)` linking
to the new install README, accurately signaling that hooks aren't
automated by `gitnexus setup` like they are for Claude Code.
4. Shell quoted-pattern parser limitation (MINOR, documented)
Added inline comment in gitnexus-hook.cjs documenting the known
`rg "User Service"` -> `User` truncation, plus regression tests in
cursor-hook.test.ts pinning the behavior so a future change is
visible.
Test additions (33 -> 41):
- Wide-alias source coverage for Grep (query / pattern / regex / q /
search / searchQuery) plus pickLongestStringValue fallback
- Read alias coverage including camelCase filePath
- GITNEXUS_DEBUG behavioral test: stderr quiet by default, payload
echoed when env var set, stdout output contract preserved either way
- Shell quoted-pattern documented behavior tests
- Install README presence + content (.cursor/hooks.json, hooks/, debug
diagnostics)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
* feat(cli): add `gitnexus publish` for opt-in understand-quickly registry
Adds a small, opt-in command that fires a single `repository_dispatch`
event at `looptech-ai/understand-quickly` to ask the registry for an
instant resync of the current repo's entry. No graph file is uploaded;
the registry pulls from raw.githubusercontent.com per the protocol at
https://github.com/looptech-ai/understand-quickly/blob/main/docs/integrations/protocol.md.
- Pure helpers (id parsing, payload construction, validation) live in
`gitnexus-shared/src/integrations/understand-quickly.ts` so the
package stays Node-free and the same logic is testable in isolation.
- The CLI command lives in `gitnexus/src/cli/publish.ts`. Without
`UNDERSTAND_QUICKLY_TOKEN` it is a no-op (exits 0 with one
informational line); with the token it POSTs the dispatch and
surfaces 204 / 401 / 404 / 5xx distinctly.
- The id defaults to `<owner>/<repo>` parsed from the `origin` remote
and can be overridden with `--id`.
- Refuses to publish when no `.gitnexus/` index exists, with a
`gitnexus analyze` hint.
Tests: a new vitest unit covers the pure helpers (8 + 8 + 2 cases) and
the no-token no-op path with a `fetch` spy that fails the test if the
network is touched. README gets a one-paragraph "Publishing to
understand-quickly" section near the existing CLI docs.
* fix(uq-publish): address review blockers + high-severity items
Addresses CodeQL polynomial-regex (HIGH), token-gate ordering, distinct
401/403/404/422 response branches, fetch timeout, expanded test coverage,
tightened owner/repo validation, and non-GitHub remote rejection.
See response thread on PR #1425 for the per-finding rationale.
Signed-off-by: amacsmith <alex.mac@looptech.ai>
* fix(publish): address Claude review on PR #1425
- AbortError → TimeoutError: AbortSignal.timeout() throws a
DOMException with name 'TimeoutError', not Error{name:'AbortError'}.
Match the pattern used in core/embeddings/http-client.ts so the
user-facing "timed out after 15000ms" message actually fires. Update
the regression test to throw a real DOMException — the previous fake
was a false-green.
- isValidOwnerRepo: forbid trailing hyphen in the owner segment.
GitHub rejects this at account-creation time; allowing it here meant
hand-typed --id values like 'my-org-/repo' would pass our regex and
422 from GitHub.
- Add publish-command coverage to cli-index-help.test.ts (asserts on
--id, --skip-git, the registry name, and the token env var) and
cli-commands.test.ts (asserts publishCommand is exported as a
function). Catches accidental command-registration deletion.
---------
Signed-off-by: amacsmith <alex.mac@looptech.ai>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
* fix(lbug): route diagnostic logs to stderr to avoid MCP stdio corruption
Replace console.log/console.warn with console.error in core/lbug so
diagnostic messages reach stderr and never corrupt the JSON-RPC stream
on MCP stdio. Per spec, the server MUST NOT write anything to stdout
that is not a valid MCP message.
- lbug-adapter.ts:367 - schema creation warning (MCP-reachable via lazy
DB init from tool handlers)
- lbug-adapter.ts:1047,1054 - legacy embedding fallback diagnostics
(currently HTTP-only, but covered by upcoming no-console lint rule)
- extension-loader.ts:191 - default warn handler fallback used during
DuckDB extension loading
* feat(mcp): add stdout sentinel via AsyncLocalStorage transport-write tagging
Untagged process.stdout.write calls now redirect to stderr with a
[mcp:stdout-redirect] prefix instead of corrupting the JSON-RPC frame
stream. Identification is correctness-by-construction: the transport
wraps every send() in withMcpWrite() (AsyncLocalStorage) and the
sentinel checks isMcpWrite() per call. A byte-shape heuristic would
have falsely rejected Content-Length frames (start with C, end with })
and misclassified multi-chunk writes.
- gitnexus/src/mcp/stdio-context.ts: AsyncLocalStorage helpers + factory
- gitnexus/src/mcp/server.ts: install sentinel in safeStdout Proxy,
flush summary at process exit
- gitnexus/src/mcp/compatible-stdio-transport.ts: wrap send() write in
withMcpWrite so transport frames pass through cleanly
- gitnexus/test/unit/mcp-stdout-sentinel.test.ts: 17 cases covering
pass-through, redirect, prefix, truncation (default 200 / custom),
rate limit (default 10), one-shot warning, summary, mixed sequences
* feat(eslint): forbid console.log/warn and process.stdout.write in MCP-reachable code
Add a narrow ESLint override for gitnexus/src/mcp/**, gitnexus/src/core/lbug/**,
gitnexus/src/core/embeddings/**, and gitnexus/src/cli/mcp.ts that:
- sets no-console: ['error', { allow: ['error'] }] — only console.error
survives, since stderr is the only spec-safe channel for diagnostics
while the MCP stdio transport owns stdout for JSON-RPC frames
- adds no-restricted-syntax matching MemberExpression and CallExpression
forms of process.stdout.write to close the bypass path that the
AsyncLocalStorage sentinel cannot guarantee
Migrates 18 pre-existing console.log/warn call sites in core/embeddings/
(embedder.ts, embedding-pipeline.ts) to console.error; these are reached
from gitnexus_query semantic search and would have polluted MCP stdio
once a query triggered the embedding pipeline.
Adds eslint-disable-next-line comments in pool-adapter.ts at the four
legitimate process.stdout.write sites — they ARE the captured-real-write
infrastructure used by the sentinel and the silenceStdout/restoreStdout
mechanism.
The override is forward-compatible with feat/pino-logger (PR #1336)
which adds a broader no-console rule for gitnexus/src/; the narrow rule
here is a strict subset and rebases trivially when #1336 lands.
* feat(setup): pin setup-generated MCP config to installed version, keep static configs on @latest
The user-facing MCP config that 'gitnexus setup' writes into editor configs
now references gitnexus@<installed-version> instead of gitnexus@latest, read
dynamically from gitnexus/package.json#version at module load. This skips
the npm-registry metadata roundtrip on every MCP connect and stays
reproducible until the user explicitly upgrades.
Static example configs and quickstart docs intentionally keep @latest:
- .mcp.json, gitnexus-claude-plugin/.mcp.json
- gitnexus-claude-plugin/skills/*/mcp.json (6 files)
- README.md / gitnexus/README.md MCP examples
Pinning these would create per-release version-bump churn for marginal
(~100-500ms) savings. The dominant cold-cache cost is the native rebuild
addressed separately by the GITNEXUS_SKIP_OPTIONAL_GRAMMARS env var.
README adds a one-line steer above the @latest quickstart pointing
repeated users at 'gitnexus setup' for the absolute-path config that
bypasses npx entirely.
Tests refactored to assert against the dynamic version (createRequire of
package.json) so they don't break on every release bump:
- gitnexus/test/unit/setup.test.ts
- gitnexus/test/unit/setup-jsonc.test.ts
- gitnexus/test/unit/setup-codex.test.ts
- gitnexus/test/integration/setup-skills.test.ts (regex match)
* feat(install,mcp): GITNEXUS_SKIP_OPTIONAL_GRAMMARS opt-out + missing-grammar warnings
Postinstall scripts (build-tree-sitter-dart.cjs, build-tree-sitter-proto.cjs)
gain a strict 'process.env.GITNEXUS_SKIP_OPTIONAL_GRAMMARS === "1"'
early-exit so users without a C++ toolchain (or anyone wanting fast
'npm install gitnexus') can skip the native rebuild. Strict '=1' only —
'true', 'yes', '0' and any other value fall through to the rebuild.
Add gitnexus/src/cli/optional-grammars.ts: cheap require.resolve probe for
each optional grammar, with a stderr warning helper. The warning surfaces:
- At MCP server start (cli/mcp.ts) — unconditional, since the server
serves any indexed repo and we cannot pre-filter by language.
- At 'gitnexus analyze' start (cli/analyze.ts) — conditional on the
target repo containing .dart/.proto files (cheap glob), so users with
no relevant code don't see noise.
README documents the env var with the strict '=1' value and the trade-off
(faster install, no Dart/Proto parsing until reinstalled).
* test(mcp): child-process integration test asserts end-to-end stdout discipline
Spawns 'node dist/cli/index.js mcp' as a child, drives the MCP stdio
handshake (initialize -> initialized -> tools/list), reassembles every
stdout chunk into Content-Length-framed JSON-RPC messages, and asserts
zero stray bytes. Any byte outside a valid header-then-body window is
captured and surfaced in the failure message alongside the server's
stderr — this is the regression gate for U1 (no console.log/warn in
MCP-reachable code) and U3 (AsyncLocalStorage stdout sentinel).
Time budget: 5s local / 15s CI for first frame; 10s/30s total. Asserts
the published GitNexus tool surface (list_repos, query, context, impact,
detect_changes, rename) is reported by tools/list.
Adds 'pretest:integration': 'node scripts/build.js' so 'npm run
test:integration' rebuilds dist before the spawn — closes the
'stale dist masks regression' DX gap.
* fix(mcp): address PR #1383 review — sentinel scope, grammar detection, lint, contract
Blockers:
- B2: detectMissingOptionalGrammars now actually require()s each grammar
instead of require.resolve(). For 'file:' optional dependencies the
package directory is always installed regardless of postinstall outcome,
so resolve() never threw and the missing-grammar warning never fired
for the exact target users (those who set GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1
or whose native rebuild soft-failed). require() loads the entry, which
triggers node-gyp-build and throws if .node is absent. Result memoized.
Should-fix:
- S1: Removed duplicate uncaughtException/unhandledRejection handlers from
cli/mcp.ts. server.ts:startMCPServer already registers handlers with
full stack traces; cli/mcp.ts handlers fired first with worse output and
never got a chance to exit because server.ts shuts down immediately.
- S2: Sentinel is now actually global. New setActiveStdoutWrite() in
pool-adapter so silenceStdout/restoreStdout cycles preserve a
registered wrapper instead of unwinding to raw realStdoutWrite. At
startMCPServer: install sentinel.write as process.stdout.write AND
register it as the active handler. Direct process.stdout.write calls
from anywhere (console.log, dependency banners, etc.) now route through
the sentinel instead of bypassing it. The transport's _safeStdout Proxy
remains as belt-and-suspenders.
- S3: ESLint no-restricted-syntax now also forbids destructuring of
process.stdout (covers both 'const { write } = process.stdout' shapes
and rest patterns).
Minor:
- M1: chunkToBuffer now handles plain Uint8Array (Buffer.from(u8)) instead
of falling through to String(chunk) which produced '1,2,3,...' garbage.
- M2: Untagged-write callbacks are now invoked on next tick per the
Node Writable.write contract — both within and beyond the rate-limit cap.
extractCallback handles the (chunk, cb) and (chunk, encoding, cb) overloads.
- M3: setup.ts throws early if package.json#version is missing/non-string
instead of emitting 'gitnexus@undefined'.
- M4: parser-loader.ts console.warn → console.error; ESLint scope extended
to gitnexus/src/core/tree-sitter/** so future violations are caught.
New tests cover:
- Plain Uint8Array redirect (asserts no String(chunk) garbage).
- Writable callback fired async (next-tick) for both normal and
past-rate-limit redirects.
Validation: cd gitnexus && npx tsc --noEmit clean; vitest run 7863 passed,
11 skipped; eslint clean on MCP-reachable scope; integration test green
against rebuilt dist/.
* fix(mcp): close pre-sentinel stdout window + tighten contracts
Address ce-code-review findings on PR #1383:
P1 — Sentinel install order (was: stdout corruption window during
mcpCommand pre-startup):
- Add idempotent installGlobalStdoutSentinel() to mcp/stdio-context.ts.
It captures realStdoutWrite/realStderrWrite, replaces process.stdout.write,
and registers with pool-adapter's setActiveStdoutWrite — exactly once.
- cli/mcp.ts now installs the sentinel as the FIRST line of mcpCommand,
before warnMissingOptionalGrammars (which after the B2 fix actually
require()s each native grammar binding and could emit node-gyp-build
banners to raw stdout in the pre-sentinel window).
- mcp/server.ts startMCPServer keeps a safety-net call to the same helper;
the second invocation is a no-op.
P1 — WriteFn type erasure:
- WriteFn now declared as instead of
, so the assignment
and the
setActiveStdoutWrite(sentinel.write) call don't silently cross a
type boundary.
P1 — extractCallback fragility:
- Replaced backward-scan-with-undefined-break heuristic with a strict
'last arg if function' check matching the documented Writable.write
contract. No longer breaks on a future (chunk, options, cb) overload.
P2 — _detectionCache premature memoization:
- Removed the explicit cache. Node's module cache already memoizes
require() — calling detectMissingOptionalGrammars multiple times is
cheap. Removing the module-level mutable state makes the helper
trivially testable (no need for a reset hatch).
P2 — Misleading 'reinstall' message on broken (not missing) grammars:
- detectMissingOptionalGrammars now distinguishes MODULE_NOT_FOUND /
node-gyp-build 'no native build' patterns from other errors
(SyntaxError, EACCES, native crash). Broken bindings get an
actionable stderr line naming the real failure instead of the
misleading 'reinstall to enable' hint.
Other:
- mcp/core/lbug-adapter.ts updated with a KEEP-THIS-FILE note. Tests
use the path as a vi.mock seam (calltool-dispatch.test.ts and 7
others); new non-test code may import core/lbug/pool-adapter.js
directly. The maintainability finding flagging the shim as
self-contradictory was incorrect — the shim has a real test purpose.
Validation: tsc clean, vitest 7863 passed (no regressions), eslint
clean on MCP-reachable scope, integration test green against rebuilt
dist/.
* fix(mcp): close import-time stdout corruption window
Codex's adversarial review on PR #1383 found that even though cli/mcp.ts
is loaded lazily by Commander, ITS static imports (startMCPServer,
LocalBackend, installGlobalStdoutSentinel, warnMissingOptionalGrammars)
evaluate synchronously when the module loads — well before mcpCommand's
function body runs. Three of those four imports transitively pulled in
core/lbug/pool-adapter.ts, which imports @ladybugdb/core at module top
level. The native binding's init can write to raw stdout in that
pre-sentinel window and corrupt the JSON-RPC frame stream.
Fix: shrink cli/mcp.ts's static-import closure to a single zero-dep
chain (mcp/stdio-context.js -> mcp/stdio-capture.js, both leaf-clean),
install the sentinel as the first executable statement of mcpCommand,
then dynamically import the heavy backend modules in parallel via
await Promise.all.
Per the plan at docs/plans/2026-05-06-002-fix-import-time-stdout-window-plan.md:
- U1: New leaf module gitnexus/src/mcp/stdio-capture.ts owns the
stdout-capture singleton state (realStdoutWrite, realStderrWrite,
activeStdoutWrite + setActiveStdoutWrite/getActiveStdoutWrite).
Zero non-node: imports — adding any would re-introduce the hazard.
- U2: pool-adapter.ts re-exports the relocated symbols under the
existing names so the test mock seam (8+ files use vi.mock on
mcp/core/lbug-adapter.ts which re-exports * from pool-adapter)
keeps working without churn. restoreStdout and the watchdog now
read the active handler via getActiveStdoutWrite(). stdio-context.ts
imports from stdio-capture directly.
- U3: cli/mcp.ts's static imports collapse to one
(installGlobalStdoutSentinel). startMCPServer / LocalBackend /
warnMissingOptionalGrammars become parallel await import()
inside mcpCommand, after the sentinel install.
- U4: New regression test gitnexus/test/integration/mcp/import-closure.test.ts
spawns a child Node process that imports dist/cli/mcp.js (without
invoking mcpCommand), inspects the CJS module cache via createRequire,
and asserts @ladybugdb/core (and tree-sitter native bindings) are
NOT in the static-import closure. Characterization-first: this test
was authored to fail against the pre-fix code and confirmed to do so
before U1-U3 landed.
Validation: tsc clean; vitest 7865 passed / 11 skipped (2 new U4 cases);
eslint clean on MCP-reachable scope; integration server-startup test
green against rebuilt dist/.
* fix(mcp): drop dead ESLint selector + suppress redundant grammar warning
Two minor PR #1383 review findings:
1. eslint.config.mjs: removed Selector 3 (`Property[key.name='write'].properties:has(...)`).
`.properties` is not a valid attribute on a Property node in the ESTree
AST, so the :has clause never matched — dead code. Selector 4 covers
the canonical `const { write } = process.stdout` shape; tightened its
comment to make that explicit.
2. cli/mcp.ts: removed the unconditional warnMissingOptionalGrammars call
at MCP startup. The analyze path already emits this warning at index
time with relevantExtensions filtered to the repo's actual file types,
and a repo can only be served by MCP after analyze has run. Repeating
the warning unconditionally on every MCP session was pure noise on
machines whose indexed repos don't use .dart/.proto.
* chore(mcp): address PR #1383 review nits
Three minor hygiene findings from the production-readiness review:
- cli/mcp.ts: rewrite stale comment that described
warnMissingOptionalGrammars as living inside mcpCommand. The call was
removed in ca617552 — this path no longer invokes it at all.
- test/integration/mcp/import-closure.test.ts: same comment drift fixed.
Test assertion is unchanged and still passes for the right reason
(cli/mcp.js's static-import closure is leaf-only).
- mcp/server.ts: rename _safeStdout to safeStdout. The leading underscore
conventionally signals "intentionally unused" but the Proxy is passed
to CompatibleStdioServerTransport on the next line.
No behavior change. Typecheck clean; ESLint MCP-reachable scope still 0
errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci(security): add CodeQL SAST workflow for JS/TS and Python
CodeQL analyzes both languages on PR, main push, and weekly schedule.
Findings upload to the Security tab as SARIF. Advisory only on
introduction; promote to required check after baseline triage.
Plan: docs/plans/2026-05-03-001-feat-automated-security-scans-plan.md (U1)
* ci(security): add Dependency Review PR gate
Blocks PRs introducing high+ severity dependency vulnerabilities.
Posts inline summary comment on failure. Required-check candidate
after one week of clean runs.
Plan: docs/plans/2026-05-03-001-feat-automated-security-scans-plan.md (U2)
* ci(security): add Gitleaks secret scanning
PR runs scan the diff; main pushes scan full history.
Defense-in-depth on top of GitHub native push protection
(documented as a recommended Settings toggle in SECURITY.md).
Plan: docs/plans/2026-05-03-001-feat-automated-security-scans-plan.md (U3)
* ci(security): add OpenSSF Scorecard workflow
Weekly + on main push. SARIF uploads to Security tab; public
badge URL resolves after first scheduled run lands.
Plan: docs/plans/2026-05-03-001-feat-automated-security-scans-plan.md (U4)
* ci(security): add zizmor workflow lint
Lints .github/workflows/** for known Actions security misconfigurations
(unpinned actions, dangerous interpolation, missing permissions).
Triggered only on PRs touching .github/**.
Plan: docs/plans/2026-05-03-001-feat-automated-security-scans-plan.md (U5)
* ci(security): add Trivy container image scanning
Builds Dockerfile.cli and Dockerfile.web, then scans images for
HIGH/CRITICAL CVEs. Findings record-only on Security tab; not
PR-blocking. Weekly schedule + main push for freshness.
Plan: docs/plans/2026-05-03-001-feat-automated-security-scans-plan.md (U6)
* docs(security): add SECURITY.md policy and Scorecard badge
Vulnerability disclosure policy points to GitHub Private Vulnerability
Reporting. Documents in-CI scans landed in this branch and recommended
admin actions for forks.
Plan: docs/plans/2026-05-03-001-feat-automated-security-scans-plan.md (U7)
* fix(review): apply autofix feedback
- CodeQL paths-ignore: replace brace expansion (parser.{c,js}) with two
explicit entries — CodeQL uses .gitignore-style globs that do NOT support
brace expansion, so the original pattern matched no files.
- Trivy: pin aquasecurity/trivy-action from @master to @0.28.0 — mutable
refs are a supply-chain risk and are exactly what zizmor (added in this
same plan) is meant to flag.
ce-code-review run: /tmp/compound-engineering/ce-code-review/20260503-104259-279c3bc4/
* docs(review): record residual review findings
ce-code-review autofix run flagged three downstream-resolver items
that are not blockers but should land before promoting any of the new
security workflows to required PR checks.
Source: /tmp/compound-engineering/ce-code-review/20260503-104259-279c3bc4/
* fix(ci-security): address all zizmor + dependency-review violations
Resolves all GitHub Advanced Security findings on PR #1297:
- Add 'persist-credentials: false' to actions/checkout in 5 workflows
(codeql, dependency-review, gitleaks, trivy, workflow-lint). Prevents
the GITHUB_TOKEN from persisting in .git/config for downstream steps
to read. Scorecard already had it.
- Pin every net-new third-party Action to a commit SHA (was: major-tag
refs flagged by zizmor as 'unpinned action reference'):
github/codeql-action -> v3.35.3 (0daab03)
actions/dependency-review-action -> v4.9.0 (2031cfc)
gitleaks/gitleaks-action -> v2.3.9 (ff98106)
ossf/scorecard-action -> v2.4.3 (4eaacf0)
docker/build-push-action -> v6.19.2 (10e90e3)
- Bump aquasecurity/trivy-action 0.28.0 -> 0.36.0 (ed142fd). Versions
< 0.35.0 are flagged by GHSA-69fq-xp46-6x23 (briefly compromised
supply chain). Caught by Dependency Review on the introducing PR.
- Pin pipx-installed zizmor to 1.24.1 (was unpinned 'pipx install
zizmor' resolving to latest at run time).
Removes the now-stale residual-findings doc since every item it
recorded is resolved on this branch.
* fix(ci-security): clear remaining zizmor findings
After landing the new security workflows, zizmor reported 5 high+
findings against pre-existing workflows (none introduced by this PR's
new files, all introduced by zizmor's wider scope). Resolved per
research at docs.zizmor.sh and PyO3/maturin issue #2425:
Real fixes (cache-poisoning):
- publish.yml + release-candidate.yml: add 'package-manager-cache:
false' to actions/setup-node. setup-node v5+ enables caching by
default when a packageManager field is present in package.json;
explicit opt-out keeps release installs hermetic and clears the
audit. Cost: ~30s slower per release run.
Documented exemptions (dangerous-triggers, .github/zizmor.yml):
- ci-report.yml: workflow_run is REQUIRED to post sticky comments
on fork PRs (forks have read-only GITHUB_TOKEN on pull_request).
- claude.yml: pull_request_target is required by claude-code-action
to access secrets and post fork-PR review comments. PR checkouts
pin fork HEAD SHA to mitigate TOCTOU.
- pr-labeler.yml: pull_request_target on the autolabel job needs
pull-requests:write. release-drafter runs with dry-run:true and
reads config from the BASE ref only.
Each exemption carries the documented mitigation in zizmor.yml.
workflow-lint.yml now passes --config to both the SARIF and the
gate invocations.
Local 'zizmor --config .github/zizmor.yml --min-severity high .'
reports: No findings to report. Good job!
The "Web UI (browser-based)" section described an old client-side
architecture. Today gitnexus.vercel.app is a thin frontend that
auto-connects to a local `gitnexus serve` backend — there is no
ZIP drag-and-drop and no fully self-contained mode.
- Drop "No server, no install" claim
- Replace "drag & drop a ZIP" tagline with the actual onboarding step
- Add the missing `gitnexus serve` step to the local-dev block
Closes#1110
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci(docker): mirror signed images to Docker Hub alongside GHCR
docker.yml now publishes to docker.io/abhigyanpatwari/gitnexus{,-web} in
the same build step as the existing GHCR push, so both registries receive
the same digest, the same Cosign keyless signature, and the same SBOM /
build-provenance attestations. The Docker Hub login uses new repo secrets
DOCKERHUB_USERNAME / DOCKERHUB_TOKEN (scoped PAT, not account password).
Supply-chain guarantees carry over unchanged: the signing loop iterates
metadata-action's full tag set, so Docker Hub tags get signed at the
identical digest under the same docker.yml@refs/tags/v* identity. The
ClusterImagePolicy is extended with docker.io / index.docker.io / bare-
namespace globs so admission cannot be sidestepped by registry-prefix
choice. README and .env.example document both registries; RC section in
CONTRIBUTING.md notes the Docker Hub mirror tag.
Closes#1027
* ci(docker): publish to akonlabs Docker Hub namespace; add PR dry-run CI
- Hardcode `akonlabs` as the Docker Hub namespace in metadata-action and
both attestation subject-names (Docker Hub org differs from GitHub org
`abhigyanpatwari`, so `github.repository_owner` would produce the wrong ref)
- Update docs (.env.example, README, CONTRIBUTING) and the Kubernetes
ClusterImagePolicy globs to reference `akonlabs/gitnexus{,-web}`
- Add `pull_request` trigger so the image build runs as CI on every PR
(build only — no push, sign, or attestation)
- Add `workflow_dispatch` with `dry_run: boolean` (default true) for
manual build-only runs; all publish steps gated on
`github.event_name != 'pull_request' && !inputs.dry_run`
The top-level and CLI READMEs advertised `gitnexus group add <name> <repo>`
(two args) and `gitnexus group remove <name> <repo>`, but the CLI
(`gitnexus/src/cli/group.ts`) actually requires three args for `add`
(`<group> <groupPath> <registryName>`) and uses `<groupPath>` — not a
repo path — for `remove`. Reusing the same second argument across two
`group add` invocations silently overwrote the previous mapping because
the hierarchy path is the key in `group.yaml`'s `repos` map.
Update both READMEs to match the real CLI contract. Node_modules not
installed locally for this docs-only change, so pre-commit (prettier +
typecheck) was skipped.
Made-with: Cursor
Co-authored-by: TuanPM1 <tuanpm1@kaopiz.com>
* feat: add docker support
* feat: move docker files to root
* feat: add docker build and push workflow
* fix: pin docker action SHAs to verified commits
Made-with: Cursor
* fix: remove redundant --platform=$TARGETPLATFORM from runtime stage
Made-with: Cursor
* fix: upgrade docker actions to Node.js 24-compatible versions
Made-with: Cursor
* docs: updated readme
* fix: update docker references
* fix(docker-server): reject null bytes in resolvePath
Defensively harden the path traversal guard by returning null
early when the URL contains a null byte, before normalization runs.
Made-with: Cursor
* fix(docker-server): handle createReadStream errors
Attach an error listener before piping so mid-flight read errors
(truncated file, permission change) cleanly destroy the response
instead of being silently swallowed.
Made-with: Cursor
* fix(docker-server): replace existsSync with async stat
Eliminates the TOCTOU race between the initial stat call and the
subsequent existsSync check. Reuses the async stat pattern already
in place and removes the now-unused existsSync import.
Made-with: Cursor
* test(docker-server): add integration tests; fix %00 null-byte bypass
Decode the URL before the null-byte check so percent-encoded null
bytes (%00) are also rejected with 400 instead of falling through
to the SPA fallback. Adds 5 node:test integration tests covering
valid assets, SPA fallback, path traversal, null bytes, and 404.
Made-with: Cursor
* style: fix prettier formatting in docker-server files
Made-with: Cursor
* fix(docker): wire tests into CI, fix resolvePath separator, correct image namespace
- Add `node --test docker-server.test.mjs` step to ci-tests.yml so the
path-traversal guard tests run in every CI pass instead of being silently skipped.
- Fix resolvePath containment check: `startsWith(root)` would allow sibling
directories like `/app/dist-evil/`; now guards with `root + sep` or exact match.
- Update docker-compose.yaml default image from `abhigyanpatwari` namespace to
`brainifii` to match what docker.yml publishes to GHCR.
* fix(docker): update apt-get commands and set user permissions
- Modify Dockerfile and Dockerfile.test to include options for apt-get to bypass validity checks during updates.
- Set ownership of the /app directory to the 'node' user in the runtime stage for improved security and proper permission handling.
* fix(docker): switch to Alpine base images for smaller footprint
- Update Dockerfile to use Alpine-based Node.js images for both builder and runtime stages, reducing image size and improving performance.
- Replace apt-get commands with apk for package installation in the runtime stage.
* fix(docker): update Node.js version in Dockerfile
- Change base image from node:20-alpine to node:22-alpine
* fix(docker): update Node.js version in Dockerfile to 22-alpine for runtime
---------
Co-authored-by: kritik.b <kritik.b@media.net>
Wire extractors into the sync pipeline with service boundary detection.
GroupService provides high-level API for all group operations.
- Sync pipeline: orchestrates extraction (HTTP, gRPC, topics) with
service boundary assignment and exact matching
- GroupService: groupList, groupSync, groupContracts, groupQuery,
groupStatus (groupImpact deferred to cross-repo follow-up PR)
- CLI: group create/add/remove/list/sync/contracts/query/status
- MCP tools: group_list, group_sync, group_contracts, group_query,
group_status
- Monorepo fixture: 3 services (auth/orders/gateway) connected via
gRPC + Kafka + HTTP — all intra-repo cross-links discovered
- Documentation: CLI commands and MCP tools added to both READMEs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gitnexus-web imports from gitnexus-shared, which requires npm run build
to generate dist/. Without this step, npm run dev fails with module
resolution errors.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Dart was added as the 14th supported language in PR #204 but the README
was not updated. Adds Dart row to the supported languages table and
updates the language count from 13 to 14.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: E2E workflow, web typecheck job, pre-commit hook, test suite
CI:
- ci.yml consolidated to reference ci-tests.yml
- ci-quality.yml: add typecheck-web job for gitnexus-web/
- ci-e2e.yml: E2E workflow with dorny/paths-filter (web changes only)
- ci-report.yml: remove dead integration-reports references
- CI gate allows skipped E2E status
- .gitignore: playwright artifacts, eval test artifacts
Pre-commit hook:
- .githooks/pre-commit: typecheck + unit tests for both packages
- Activated via git config core.hooksPath in prepare script
Test infrastructure:
- Vitest + React Testing Library: 58 unit tests
(graph, server-connection, mermaid, settings, constants, utils, paths)
- Playwright E2E: 5 tests + manual recording harness
- vitest.config from vitest/config, engines.node >= 20
- Playwright artifacts retain-on-failure
- wait-on in devDependencies
- vitest/coverage-v8 aligned with vitest 4.x
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update gitnexus-web package-lock.json
Reflects devDependency additions (vitest, playwright, wait-on,
@testing-library, etc.) from package.json changes in this PR.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): add missing process-list-loaded testid, increase CI timeouts
- Add data-testid="process-list-loaded" to ProcessesPanel (E2E tests
were waiting for an element that didn't exist)
- Increase server connect timeouts from 5s to 10s for slower CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ci): run gitnexus-web unit tests in CI, remove unused variable
- Add gitnexus-web npm ci + vitest run to ci-tests.yml so web unit
tests are gated by the CI status check (were only running locally)
- Remove unused IS_PLAYWRIGHT_AUTOMATION variable from E2E spec
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): add process-row testid, wait for networkidle on page load
- Add data-testid="process-row" to ProcessItem component (E2E tests
referenced it but it didn't exist in the source)
- Use waitUntil: 'networkidle' on page.goto to ensure Vite dev server
is fully ready before interacting (fixes first-test timeout in CI)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): add process-view-button and process-highlight-button testids
E2E tests referenced these data-testid attributes but they didn't
exist in ProcessItem. All 6 E2E testids now have matching source
elements: status-ready, process-list-loaded, process-row,
process-view-button, process-highlight-button, server-url-input.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): remove networkidle — Vite HMR WebSocket prevents it from resolving
networkidle waits for zero network activity for 500ms, but Vite's HMR
WebSocket stays open permanently, causing page.goto to timeout at 60s
on all tests after the first. The explicit toBeVisible waits on UI
elements are sufficient and deterministic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(e2e): wait for Server button visibility, add CI retry, all 5 tests pass locally
Root cause: test 1 clicked the Server button before React hydrated,
so the tab content never rendered and the input wasn't found.
Fixes:
- Wait for Server button toBeVisible before clicking
- Increase input wait to 15s
- Remove networkidle (Vite HMR WebSocket prevents it from resolving)
- Add retries: 1 in CI for transient cold-start flakiness
Verified locally: all 5 E2E tests pass, 198 unit tests pass, typecheck clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ci): tolerate LadybugDB native crash during analyze step
gitnexus analyze can crash with "double free or corruption" (known
issue #273) during the LadybugDB native addon shutdown. The index is
usually written successfully before the crash. The workflow now:
1. Allows analyze to exit non-zero with a warning
2. Verifies .gitnexus index was actually created
3. Only fails if no index exists (real failure)
All tests verified locally: 198 unit, 5 E2E pass, typecheck clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ci): fix shell quoting in analyze step, simplify to || true
The previous echo string had special characters that broke bash
quoting in GitHub Actions. Simplified to: analyze || true, then
check if .gitnexus exists.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add agent development framework, GitHub templates, eval refactor
Agent framework (layered docs for AI-assisted contributions):
- AGENTS.md: canonical instructions, impact analysis, MCP tools
- CLAUDE.md: Claude Code-specific deltas and hooks
- GUARDRAILS.md: safety boundaries, non-negotiables, escalation
- ARCHITECTURE.md: monorepo layout, data flow map
- TESTING.md: test structure, commands, categories
- RUNBOOK.md: copy-paste operations for dev/CI/MCP
- llms.txt: minimal LLM context pointer
Editor integration:
- .cursor/index.mdc + rules/100-monorepo.mdc
GitHub templates:
- PR template with areas-touched checkboxes
- Bug report + feature request issue forms
Eval harness:
- Refactored mcp_bridge, tool_registry, constants
- Error sanitization utilities
- Property-based tests via Hypothesis
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(eval): use format_exception instead of format_exc in sanitize_exception
format_exc() returns the currently handled exception traceback, which
may be unrelated if called outside an active except block. Using
format_exception(type(exc), exc, exc.__traceback__) reliably captures
the passed exception's traceback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: update CONTRIBUTING.md and TESTING.md for current CI/hook setup
- CONTRIBUTING.md: add gitnexus-web typecheck command, pre-commit hook
checklist item
- TESTING.md: add gitnexus-web typecheck command, pre-commit hook
section (husky), update CI integration to list actual workflow files
(ci-quality, ci-tests, ci-e2e)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: update testing docs to reflect CI/E2E changes from PR #486
- AGENTS.md: update test counts (CLI ~2000 unit, ~1850 integration),
add gitnexus-web testing section (198 unit, 5 E2E with commands)
- RUNBOOK.md: fix Node requirement to >=20, fix E2E local repro command
- TESTING.md: E2E uses data-testid selectors + real servers, not mocks
- .cursor/rules/100-monorepo.mdc: add web test/E2E commands
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: address context engineering review — deduplicate tokens, expand Cursor rules
- Remove ~100-line gitnexus:start block from CLAUDE.md (was duplicated from AGENTS.md)
- Fix gitnexus:start block inlined inside AGENTS.md Reference Docs bullet (doubled)
- Replace CLAUDE.md scope table with pointer to AGENTS.md (single source of truth)
- Expand .cursor/index.mdc with 5 non-negotiable safety rules for always-on context
- Add .cursor/rules/200-eval.mdc with Python/eval commands (glob-scoped to eval/**)
- Improve llms.txt with priority annotations and descriptions
- Bump version headers to 1.2.0, last-reviewed to 2026-03-24
Saves ~1,400 tokens/session with zero information loss.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
- Add Codex to Editor Support table
- Add Codex manual config example (~/.codex/config.toml)
- Update editor list in usage table
Fixes#131
Made-with: Cursor
* 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>
* feat(type-env): constructor-call type inference for TypeEnv (Phase 1)
Add extractInitializer as a Tier 1 fallback in buildTypeEnv: when a
declaration node has no explicit type annotation, infer the type from
constructor-call patterns (new X(), X::new(), X::default(), $x = new X()).
Languages covered: TypeScript/JS, Java (var), Rust, PHP, C++ (auto).
Python/Kotlin/Swift deferred — need symbol-table access to distinguish
class constructors from function calls.
Adds 20 new unit tests covering constructor inference, annotation
precedence, and known limitations across all supported languages.
* fix(type-env): class-aware constructor resolution, multi-declarator fix
- Add collectClassNames pre-scan: walks AST to build Set<string> of
class/struct names defined in the file
- C++ extractInitializer uses classNames.has() to verify identifier is
a known class before inferring (auto x = User() resolves, auto x =
getUser() does not — no false positives)
- Add InitializerExtractor type that receives classNames parameter
- Fix env.size gating: always call extractInitializer when available,
so mixed declarators like const a: A = x, b = new B() resolve both
- Add env.has() guard in Java extractInitializer to skip already-bound vars
- Document Rust new/default whitelist rationale
- Pin all test assertions, add mixed multi-declarator test case
* fix(type-env): resolve Self/self/static/parent to actual type names
- Rust: Self::new()/Self::default() resolves to enclosing impl type
- PHP: new self()/static() resolves to enclosing class, parent() to superclass
- Rust: Tier 0 annotation guard prevents overwrite by constructor inference
- Rust: mut_pattern handling in extractVarName for let mut bindings
- TS: fix misleading comment in extractInitializer
- 58 tests passing (3 new Self/self resolution tests)
* perf(type-env): single-pass AST walk with closure-scoped state
Refactors buildTypeEnv to use closures instead of passing mutable state
as parameters. classNames, env, and config are captured by the inner
walk and extractTypeBinding functions — no parameter mutation.
- Eliminates separate collectClassNames pre-scan (O(2n) → O(n))
- config looked up once per file instead of per-node
- 29 fewer lines
* feat(type-env): constructor-inferred type resolution for all languages
Add cross-file constructor type inference to the ingestion pipeline,
enabling receiver-type disambiguation for member calls like
`user.save()` when the variable is assigned from a constructor without
explicit type annotations.
Pipeline changes:
- Add extractInitializer to Python and Swift type extractors
- Add CONSTRUCTOR_BINDING_SCANNERS for Python, Swift, C/C++ in type-env
- Wire constructorBindings through parse-worker → parsing-processor →
pipeline → processCallsFromExtracted
- Rewrite resolveCallTarget receiver-type filtering (step D) to use
tiered import resolution (same-file → import-scoped → global) before
falling back to fuzzy ownerId matching
- Use collectTieredCandidates for constructor binding verification
instead of raw lookupFuzzy
Bug fixes:
- Fix C++ inline method query: @definition.method was captured on
field_declaration_list instead of function_definition, causing wrong
parameterCount for all inline class methods
- Fix parse-worker accumulated/flush results missing constructorBindings
CI changes:
- Add swift.test.ts to ci-integration pipeline group and coverage job
- Update ci-report to fetch base branch (main) coverage for delta
reporting instead of showing config thresholds
- Add per-suite timing breakdown table (unit/integration/total)
- Add expandable skipped test details section
Tests: 288 passed, 4 skipped (swift — macOS only) across 10 languages
- 36 new constructor-inferred integration tests (4 per language)
- 10 fixture directories with cross-file constructor patterns
- TypeScript, JavaScript, Java, Kotlin, Python, PHP, Rust, Go, C++, Swift
* fix(type-extractors): add type assertion for LanguageTypeConfig
* feat(ruby): constructor-inferred type resolution and self-receiver mapping
Add Ruby User.new constructor binding scanner to type-env, enabling
receiver-type disambiguation for member calls like user.save vs repo.save.
Add self/this → enclosing class resolution in lookupTypeEnv so self.method()
calls resolve to the correct class even when the method name is ambiguous.
* docs: update README with constructor inference and self/this resolution details
* refactor(ingestion): unified ResolutionContext replaces fragmented map passing
Introduce createResolutionContext() as the single resolution API for all
processors. Eliminates duplicated tier-selection logic, fixes heritage
namedImportMap bug, and adds per-file resolution caching.
- NEW resolution-context.ts: closure-factory with resolve(), per-file cache,
TIER_CONFIDENCE constant, and shared ResolutionTier type
- DELETE symbol-resolver.ts: zero production importers, logic now in
resolution-context.ts
- call-processor: all functions take ctx instead of 6 separate maps,
collectTieredCandidates removed (ctx.resolve replaces it),
D4 redundant re-resolve eliminated
- heritage-processor: takes ctx, resolveHeritageId helper extracts
repeated 14-line fallback pattern, namedImportMap now included
- import-processor: takes ctx, dead createImportMap/createPackageMap/
createNamedImportMap factories removed
- pipeline: creates single ctx, wires onProgress to all processors,
logs cache hit rate in dev mode
- Tier renamed: unique-global → global (honest about returning all candidates)
- Tests migrated: 1178 unit + 84 integration passing
* feat(type-env): self/this/super resolution, TypeEnvironment API, and review fixes
Add cross-language receiver keyword resolution:
- self/this/$this → enclosing class name via AST walk
- super/base/parent → parent class name via heritage AST extraction
(8 grammar variants: TS/JS, Java, Python, Ruby, C#, PHP, Kotlin, C++, Swift)
- D-phase widening in resolveCallTarget for super→parent method dispatch
Introduce TypeEnvironment API replacing loose TypeEnvResult + lookupTypeEnv:
- buildTypeEnv() returns TypeEnvironment with .lookup() method
- Single-pass AST walk merges constructor binding scan (was separate traversal)
- ClassNameLookup type replaces over-broad ReadonlySet<string> facade
- Memoized class name lookups to avoid redundant SymbolTable scans
Code review fixes (6 agents, 11 findings):
- Replace ctx.resolve(name, '') hack with direct symbols.lookupFuzzy()
- Extract scope key helpers (extractFuncNameFromScope, receiverKey)
- Simplify D-phase from 5 steps to 4 with deduped typeNodeIds
- Remove C from CONSTRUCTOR_BINDING_SCANNERS (YAGNI — C has no constructors)
- Cache Map reuse in ResolutionContext to reduce GC pressure
- Remove unused TieredCandidates import
Integration tests for self/this, parent, and super resolution across all
12 supported languages with per-language fixture directories.
* fix(type-env): generic parent resolution, TS cast inference, C++ brace-init
Fix generic parent class breaking super resolution:
- extractParentClassFromNode now uses extractSimpleTypeName to strip
generic params (Base<T> → Base) and qualified names (models.Model → Model)
- Affects TS, Java, Python, C# heritage extraction
Fix TypeScript new X() as T / new X()! missed inference:
- Unwrap as_expression and non_null_expression before checking for
new_expression in extractInitializer
Fix C++ brace-init User{} missed inference:
- Handle compound_literal_expression with type_identifier child
in extractInitializer
Clean up deprecated lookupTypeEnv:
- Remove standalone lookupTypeEnv export, migrate all callers to
TypeEnvironment.lookup() method
- Update all 80+ test assertions to use the new API
Integration test fixtures added:
- typescript-cast-constructor-inference (new X() as T, new X()!)
- typescript/java/csharp/kotlin-generic-parent-resolution
- cpp-brace-init-inference (auto x = User{})
* fix(type-extractors): Go &User{}, TS double-cast, Swift .init inference
Fix Go pointer-to-struct literal not inferred:
- Unwrap unary_expression (address-of &) before composite_literal check
- user := &User{} now correctly infers type User
Fix TypeScript double-cast only unwrapping one level:
- Change if to while loop for nested as_expression/non_null_expression
- new User() as unknown as Admin now correctly infers type User
Fix Swift User.init(name:) explicit init call missed:
- Handle navigation_expression callee with .init suffix in extractInitializer
Integration test fixtures:
- go-pointer-constructor-inference (&User{}, &Repo{})
- typescript-double-cast-inference (as unknown as T)
* feat: Rust struct literal, Python qualified ctor, Go new(), Swift .init scanner
- Rust: handle struct_expression in extractInitializer (User { name: "alice" })
- Python: support attribute nodes in extractInitializer (models.User("alice"))
and the cross-file scanner — extractSimpleTypeName handles qualified names
- Go: handle new(User) built-in in extractGoShortVarDeclaration
- Swift: extend CONSTRUCTOR_BINDING_SCANNERS to handle navigation_expression
callee for User.init(name:) cross-file resolution
Unit tests: 87 → 96 (Rust struct literal, Go new(), Python qualified ctor,
Python scanner qualified, plus edge cases)
Integration tests: 4 new describe blocks with fixtures
* fix: Rust Self{} resolution, C++ scoped brace-init, PHP promotion params, Ruby constants
- Rust: resolve Self {} struct literal to enclosing impl type (was stored as "Self")
- C++: replace type_identifier guard with extractSimpleTypeName for compound_literal_expression,
enabling ns::User{} scoped brace-init (closes previously deferred gap)
- PHP: add property_promotion_parameter to TYPED_PARAMETER_TYPES for PHP 8.0+
constructor property promotion (__construct(private Foo $x))
- Ruby: extend extractRubyConstructorBinding to accept constant left-hand side
(REPO = Repo.new)
Unit tests: 96 → 101 (+5: Rust Self{} ×2, C++ ns::User{} ×1, PHP promotion ×1,
Ruby constant ×1)
Integration tests: 4 new describe blocks with fixtures
* feat: Phase 1 type resolution gaps — walrus, PHP properties, nullable, Go make/assert
Phase 1 quick wins from the type resolution gap analysis:
1. Python walrus operator := (named_expression) — extractInitializer + scanner
2. PHP 7.4+ typed class properties — property_declaration in extractDeclaration
3. Nullable union unwrapping — User | null → User in extractSimpleTypeName
4. Go make() builtin — slice/map element type extraction
5. Go type assertions — iface.(User) type extraction
Also: PHP primitive_type handling in extractSimpleTypeName (string, int, etc.)
Unit tests: 101 → 114 (+13)
Integration tests: 8 new describe blocks with fixtures
* feat: Phase 2 type resolution gaps — C++ range-for, Rust if-let, C# pattern matching, Python class annotations
Phase 2 medium-effort improvements:
1. C++ range-for with explicit type — for (User& u : vec) binds u: User
2. Rust if-let/while-let captured_pattern — user @ User { .. } binds user: User
3. C# is-pattern matching — if (obj is User user) binds user: User
4. Python class-level annotations — confirmed already working, added tests
Unit tests: 114 → 127 (+13)
Integration tests: 11 new test cases with fixtures
* calm fix 4 adding skills to repo [ISSUE #140]
* inspect
* unit and integration tests
* fixed hardcoded cohesion miss
* e2e tests for --skills flag for langauge/repo support
* Cohesion test e2e tests
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.
Merged PR #133 which adds AST-based Laravel Route::* extraction.
Reverted AGENTS.md, CLAUDE.md, and README.md to preserve current config,
crypto warning, Discord link, and correct language support count (12,
including Kotlin/Swift).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Resolved FUNCTION_NODE_TYPES: keep 'anonymous_function' for PHP (php_only grammar),
add Kotlin 'lambda_literal' and Swift 'init_declaration'/'deinit_declaration'
- Resolved pipeline.ts: adopt chunked pipeline structure, integrate
processRoutesFromExtracted into per-chunk worker data processing
- Resolved framework-detection.ts: use upstream AST-BASED FRAMEWORK DETECTION heading
- Fixed accumulated/mergeResult in parse-worker to include routes field
- Impact tool now returns risk score, affected processes/modules, and summary
- Cypher tool formats results as markdown tables for LLM readability
- Context tool includes module (functional area) field
- Semantic search skips model init when embeddings are disabled
- Setup: wrap npx in cmd /c on Windows for .cmd script compatibility
- Embedder: silence stderr during ONNX model load to protect MCP stdio
- API: use executeCypher directly to avoid double formatting
- Add community integrations section to READMEs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add HTTP wrapper functions (executeQuery, textSearch) to the ingestion
worker and a new initializeBackendAgent method that creates the agent
with HTTP-backed tool closures instead of local KuzuDB. useAppState
detects backend mode and routes agent initialization accordingly.