mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-30 01:51:20 +00:00
1115 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ed03ae3ea8 |
refactor(devcontainer): split workspace-deps to updateContentCommand
post-create.sh was doing two unrelated jobs: workspace dependency install (four `npm install` runs in topological order) and AI CLI credential sync. They have different lifecycle needs — deps should re-run when lockfiles change, AI sync should run once per container — but both were gated on container-create. Per Dev Container spec lifecycle, `updateContentCommand` is the right hook for workspace deps: runs at container-create AND on content changes (lockfile updates). `postCreateCommand` is right for AI CLI sync: container-create only. Move steps 3-7 (husky cleanup + four `npm install` runs) into install-deps.sh wired as `updateContentCommand`. Split the chown step too — install-deps owns workspace-side dirs (node_modules volumes, ~/.npm), post-create owns AI-side dirs (~/.claude, ~/.codex, ~/.cursor, /commandhistory, ~/.local). Each script now has one concern. post-create.sh drops from ~187 lines to 148; install-deps.sh is 56 lines new. Faster rebuilds when nothing about deps changed (the credential sync + path translation work still runs every container-create, but the npm install dance no longer does). Research backing (no other simplification applies): - Anthropic's reference devcontainer uses pure named volumes; no host-state inheritance pattern is published. - Path translation has no upstream fix (issues #21916, #10379 closed without resolution). Our Node rewrite is the workaround. - pnpm workspaces (`pnpm -r install`) would replace the four installs with one command, but that's a real refactor (touches gitnexus/scripts/build.js + 4 package.json files); deferred. - `HUSKY=0` in containerEnv would drop the `rm -rf .husky/_` hack, but would also stop pre-commit hooks from firing inside the container; deferred. |
||
|
|
6bcc2fe207 |
fix(devcontainer): clean stale plugin/skill symlinks from prior design before writes
A user upgrading from Option B (read-only host stage + symlinks) to the current hybrid RW-bind topology hit EROFS in post-create.sh when the plugin registry path-translator tried to write `/home/node/.claude/plugins/known_marketplaces.json`. The named volume still carried `/home/node/.claude/plugins -> /host/.claude/plugins` (Option B's symlink). The new design's sub-path bind mounts at `plugins/marketplaces` and `plugins/cache` overlay through the symlink, but writes to the parent dir itself resolve via the symlink to the RO host stage and fail. Drop any leftover symlinks at known target paths early in step 2 so the mkdir/writes that follow land in the volume. |
||
|
|
79e763ea25
|
Merge branch 'main' into feat/multi-cli-devcontainer | ||
|
|
1f8c32e2f6 |
fix(devcontainer): translate host plugin registry paths to Linux on rebuild
The previous topology bind-mounted the entire `~/.claude/plugins/` directory from host. That brought through plugins, marketplaces, and extracted cache content correctly — but ALSO brought through the registry JSONs (`known_marketplaces.json`, `installed_plugins.json`, `plugin-catalog-cache.json`) which carry absolute OS-native paths: "installLocation": "C:\Users\gergo\.claude\plugins\marketplaces\X" "installPath": "C:\Users\gergo\.claude\plugins\cache\Y\Z" Claude in the Linux container fails to resolve these Windows paths and reports `Marketplace X failed to load: cache-miss`. Split the topology: - `plugins/marketplaces/` (git clones) and `plugins/cache/` (extracted plugin files) stay bidirectional RW binds — content is path-independent. - Registry JSONs move into the per-container named volume. post-create.sh reads host's versions, rewrites any absolute path ending in `/.claude/plugins/<rest>` (Windows `C:\Users\...` and POSIX `/Users/...` / `/home/...` patterns) to `/home/node/.claude/plugins/<rest>`, and writes the translated result to the volume. What this gets you: - Plugin installed on host → next container rebuild has it (translated). - Plugin installed inside container → lives in volume registry; lost on rebuild (consistent with credentials model). Re-install on host for persistence. ensure-host-config-dirs.cjs now also creates `plugins/marketplaces/` and `plugins/cache/` on host if absent (Docker rejects bind mounts whose source doesn't exist). |
||
|
|
b43fc21c98 |
refactor(devcontainer): hybrid RW bind + per-container creds — fixes EROFS on in-container plugin install
The previous Option B topology (RO host stage + named volume + symlinks
into the volume) made `/plugin marketplace add` inside the container fail
with EROFS — the symlinks pointed at a read-only mount, so Claude
couldn't create new marketplace dirs. Switch to a hybrid: shareable
content (plugins/skills/agents/memory/commands/settings.json/$HOME/.claude.json
for Claude; config.toml/memories/skills for Codex) gets a direct RW bind
from host so reads and writes go bidirectionally; credentials + the
small identity file stay in per-container named volumes so logout in
container doesn't log out host.
Mount precedence does the heavy lifting: the named volume mounts at
/home/node/.<cli> first, then sub-path bind mounts overlay specific
sub-paths. Container's view at /home/node/.claude/plugins/ is the host
dir; container's view at /home/node/.claude/.credentials.json is the
named volume's file.
What this gives you:
- /plugin marketplace add in container = installed on host
- New skill on host = visible in container immediately (no rebuild)
- claude logout in container = host stays logged in
- compound-engineering plugin enabled on host = enabled in container
- Theme picker fires once (or never if host has theme set)
What it costs:
- Write-through: a compromised npm dep in workspace deps can write to
host ~/.claude/{plugins,skills,agents,memory,commands}/. Documented
trade-off; for personal dev, accepted. Credentials still per-container.
post-create.sh becomes much simpler — only syncs the four credential
files from host into the named volumes. No more symlink dance, no more
state-file merging.
ensure-host-config-dirs.cjs gains the new bind sources: the shareable
subdirs and settings.json/config.toml files get mkdir/touched on host
so Docker doesn't reject the mount when a CLI has never been used.
|
||
|
|
26470d5580 |
fix(devcontainer): full plugin/config parity by dropping CLAUDE_CONFIG_DIR + syncing settings.json
Two changes that together give the container the same plugins and configs as the host for all three AI CLIs (login stays per-container): 1. Drop CLAUDE_CONFIG_DIR from containerEnv. The named-volume mount target `/home/node/.claude` already matches Claude's default `~/.claude`, so the env var added no behavior — but setting it changed which file Claude reads `hasCompletedOnboarding` from. With it set, Claude reads `$CLAUDE_CONFIG_DIR/.claude.json` (the small identity-only file that does NOT carry `hasCompletedOnboarding`); without it, Claude reads `$HOME/.claude.json` (the big onboarding-state file that does). The wizard fires every container-create when set, skips when unset. 2. Sync `settings.json` from host (Claude) + symlink `memories/` and `skills/` from host (Codex). Theme + `enabledPlugins` + `extraKnownMarketplaces` live in `settings.json` — without syncing it, the theme picker fires and host-installed plugins stay disabled even though their files are symlinked in. Codex's `memories/` and `skills/` are the symmetric Codex user-installed surface, now shared the same way Claude's plugins/skills/agents/memory/commands are. Cursor stays as-is — `cli-config.json` conflates auth+settings (already synced), and there's no separate plugin surface to mirror. Login details remain per-container by design (acceptable to re-login on rebuild). Everything else — plugins, skills, agents, memory, MCP user- scope config, project trust, theme, plugin enablement — now matches host on every container-create. |
||
|
|
11fc43b425
|
feat(impact): per-symbol processes field on byDepth items (#1867)
* feat(impact): per-symbol processes field on byDepth items
Today `impact` returns aggregated `affected_processes` at the top level
but the per-symbol `byDepth` items don't say which processes each caller
participates in. Consumers planning a deploy want to know if a given
caller is hit by a daily cron, a webhook, or a user-facing route - each
is a different deploy-risk profile - and that information requires a
follow-up cypher query per symbol today.
This change attaches `processes: [...]` to every `byDepth[depth][i]`
item, listing the processes that symbol participates in:
byDepth: {
"1": [
{
depth: 1,
id: "Function:src/foo.ts:doStuff",
name: "doStuff",
...
processes: [
{ id: "proc:cron_daily", label: "Daily cron",
processType: "cron", step: 12 }
]
}
]
}
The list is empty for symbols not in any process. Additive change, no
breaking modifications to existing fields.
Implementation:
- A second chunked Cypher pass runs after the existing per-process
aggregation pass, returning per-(symbol, process) rows. Same chunk
size and MAX_CHUNKS as the aggregation pass, so worst-case adds 10
extra round-trips bounded by the same env var.
- The enrichment pass is skipped entirely when `affectedProcesses.length
=== 0` (nothing to enrich) or `summaryOnly === true` (byDepth not
returned anyway).
- The aggregation query is unchanged - the new query has a distinct
RETURN shape (`RETURN s.id AS sid, ...`) so an existing unit test that
counts STEP_IN_PROCESS chunks was narrowed to match only the
aggregation pattern.
Tests:
- New: byDepth items always have a `processes` field (default empty
when no STEP_IN_PROCESS edges exist).
- New: when STEP_IN_PROCESS rows exist, the matching byDepth item
carries the right `{id, label, processType, step}` entry.
- Updated: impact-batching-grouping test mock narrowed to count only
aggregation chunks (the new per-symbol pass is covered separately).
* style: apply prettier to gitnexus/src/mcp/local/local-backend.ts
Pure line-wrap fix flagged by quality / format CI on PR #1867. Zero
semantic change: prettier broke a chained .slice().map() across three
lines instead of one. No test changes, no logic changes.
* fix(impact): address PR review findings on per-symbol process enrichment
- byDepth.processes doc now states each item carries processes (Finding 1)
- move per-symbol STEP_IN_PROCESS enrichment post-pagination so symbols
beyond the pre-pagination cap no longer get false-empty processes:[]
(Finding 2); hoist CHUNK_SIZE/MAX_CHUNKS to function scope so the
post-pagination pass can reference them
- dedup per-symbol query with DISTINCT + MIN(r.step) per (symbol,process)
pair (Finding 3)
- suppress the per-symbol pass under summaryOnly, incl. impactByUid group
fan-out, plus a test asserting the query never fires (Findings 4, 6)
* fix(impact): address second-round review findings A-E
Finding A (blocker): impactByUid passed summaryOnly:true, which drops the
entire byDepth field. cross-impact.ts reads fan.byDepth to build the group
by_depth output, so cross-repo by_depth was always {}. Replace with a new
skipPerSymbolEnrichment option on _runImpactBFS that suppresses only the
per-symbol STEP_IN_PROCESS pass while preserving byDepth.
Finding B+D (blocker): rewrite the byDepth.processes tool description. Drop
the stale "enrichment cap" wording (no longer true post-pagination), document
the {id,label,processType,step} entry shape, and tell agents to cross-check
affected_processes when partial:true.
Finding C: bound the post-pagination per-symbol enrichment loop to
MAX_CHUNKS*CHUNK_SIZE page IDs and surface partial:true when capped, so a
large page cannot trigger unbounded DB round-trips (DoD 2.6).
Finding E: add a test exercising the real impactByUid -> _runImpactBFS path
asserting byDepth survives and the per-symbol query never fires.
---------
Co-authored-by: scotjelinski <58397194+scotjelinski@users.noreply.github.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
|
||
|
|
a6df9ca182 |
docs(devcontainer): document sync-from-host design + dual-source auth flow
README still described the old "first-run copy" behavior. After the post-create.sh change to always-sync-from-host, the design works either direction: - Log in on host → next container-create syncs the credentials into the named volume. - Log in inside the container → the named volume persists the login across rebuilds; the host has no source to overwrite from, so it stays alone. Also documents the two-Claude-state-files trap (`$HOME/.claude.json` AND `$CLAUDE_CONFIG_DIR/.claude.json`, both with the same userID required), and the volume-deletion recovery path for stale named volumes carried over from earlier rebuilds. |
||
|
|
f4749cf1e4 |
fix(devcontainer): resync AI CLI state from host on every container-create
Two bugs were causing Claude Code to fire the onboarding wizard inside the container even with valid host credentials: 1. Missing the second state file. Claude Code 2.1.x writes a small `.claude.json` INSIDE `CLAUDE_CONFIG_DIR` (carrying migration tracking + userID), not just the one at `$HOME/.claude.json`. If the userIDs in the two files disagree, Claude treats the session as inconsistent and re-onboards. The previous post-create.sh only copied the `$HOME` one. 2. First-run guards (`[ ! -e $dst ]`) skipped the copy when stale named volumes from earlier rebuilds still had the prior session's state in them, leaving the container desynced from the host. Replace `copy_on_first_run` with `sync_from_host` that always overwrites from host on container-create. `link_readonly_share` now clears stale non-symlink dst entries before linking. Copies both `$HOME/.claude.json` and `$CLAUDE_CONFIG_DIR/.claude.json` so userIDs stay aligned. Container can still mutate its own state between rebuilds; resync only happens on rebuild (postCreate boundary). |
||
|
|
abbcacebca |
refactor(devcontainer): hybrid AI CLI config — read-only host share + per-container credentials
Restructure the Claude Code / Codex / Cursor mount topology to fix the silent first-run-UI bug surfaced in PR testing, and to harden against the host-write-through escape class the previous bind-mount design exposed. The actual root cause of the first-run wizard firing on the user's screenshot — confirmed via three parallel research agents (best practices, framework docs deep dive of the OpenAI Codex Rust source, adversarial design review) — was NOT a credential permission check. Claude Code splits state across `~/.claude/.credentials.json` AND `~/.claude.json` (a FILE at $HOME, sibling of the `.claude/` dir). The latter holds `hasCompletedOnboarding`, `userID`, `oauthAccount` metadata, MCP user-scope config, and per-project trust state — and Claude Code reads it at literal `$HOME/.claude.json`, not via `CLAUDE_CONFIG_DIR`. The previous design mounted `~/.claude/` but left `~/.claude.json` outside the topology entirely, so every container started with a missing onboarding-state file and re-ran the wizard. Confirmed by tfvchow/field-notes-public#10: "Persisting .credentials.json alone is NOT sufficient. Without .claude.json, Claude Code treats the session as a fresh install and prompts for login regardless of valid credentials being present." The new topology: **Mounts** - `${localEnv:HOME}/.claude` → `/host/.claude` (read-only bind) - `${localEnv:HOME}/.codex` → `/host/.codex` (read-only bind) - `${localEnv:HOME}/.cursor` → `/host/.cursor` (read-only bind) - `${localEnv:HOME}/.claude.json` → `/host/.claude.json` (read-only bind) - `claude-config-${devcontainerId}` → `/home/node/.claude` (named volume) - `codex-config-${devcontainerId}` → `/home/node/.codex` (named volume) - `cursor-config-${devcontainerId}` → `/home/node/.cursor` (named volume) **containerEnv** gains `CODEX_HOME=/home/node/.codex` (Codex's own env override, per its public Rust source). `CLAUDE_CONFIG_DIR=/home/node/ .claude` was already set. **`post-create.sh`** stages the named volumes on first run: - Symlinks shareable subdirs from `/host/.claude` into the named volume: `plugins/`, `skills/`, `agents/`, `memory/`, `commands/`. Codex gets `config.toml` symlinked. Cursor has no shareable subdirs (cli-config .json conflates auth and settings). - Copies `.credentials.json`, `auth.json`, `cli-config.json` on first run with `chmod 600`. After first run, container manages its own refresh; host's credentials untouched. - Copies `~/.claude.json` on first run (with stub `{"hasCompletedOnboarding":true,"installMethod":"global"}` fallback for hosts that haven't run Claude Code). This is the fix for the observed onboarding-wizard loop. `ensure-host-config-dirs.cjs` now also touches `~/.claude.json` on the host if missing, so the bind mount has a valid source on hosts that have never run Claude Code. **Why read-only + named volume vs. the previous full bidirectional bind mount:** 1. **Host filesystem write-through escape, eliminated.** Previous design symlinked `plugins/`, `agents/`, `skills/` write-through into the host's `~/.claude/` — a malicious npm package in the workspace dep tree could drop `agents/evil.md` into the host's config, which the next host Claude session would auto-load. The read-only `/host` mount blocks this; container compromise no longer persists across teardown via host-side autoload. 2. **Windows bind-mount perm-flattening, sidestepped.** Files surfaced through a Docker Desktop Windows bind mount appear as `root:root` mode `777`. Credentials in the named volume come with proper Linux ownership and `chmod 600` — what each CLI expects on write (none enforces on read, but write-side hygiene matters for the host's understanding of "where credentials live"). 3. **No `ide/` lock-file collisions.** Previous design symlinked `~/.claude/ide/` write-through, including per-PID lock files. Host PID and container PID namespaces are unrelated → lock-file PIDs misclassify dead processes as alive. Skipping `ide/` keeps lock files container-local. 4. **No `projects/` ghost dirs.** Host encodes the workspace path as `D--development-coding-GitNexus`, container as `-workspace`. Bidirectional `projects/` symlinks would split memory and session state across two ghost project dirs for what is conceptually the same project. Skipping `projects/` keeps per-project state container-local; host's projects/ stays untouched. 5. **No `settings.json` version drift.** Container is pinned to a specific Claude Code version (`CLAUDE_CODE_VERSION` build arg); host floats with auto-update. Bidirectional `settings.json` writes produced silent schema rollback. Skipping settings.json keeps each side authoritative for its own version. **README** rewritten in the same section to describe the new topology honestly: what's shared, what isn't, the OAuth refresh-token divergence between host and container, per-CLI quirks (macOS Keychain storage, Cursor's known upstream in-container auth bug, Codex keyring storage). Trust-boundary section updated to name the threat model accurately — same read surface as before (malicious dep can still READ all credentials), but write-through into host plugin/agent dirs is now blocked. Verified locally: `@devcontainers/cli read-configuration` resolves all 19 mounts correctly on Windows, `post-create.sh` parses, and `ensure-host-config-dirs.cjs` idempotently touches `~/.claude.json`. Research backing this design: - Anthropic Claude Code devcontainer docs (named-volume pattern): https://code.claude.com/docs/en/devcontainer - tfvchow/field-notes-public#10 (both files required): https://github.com/tfvchow/field-notes-public/issues/10 - anthropics/claude-code#29029 (VS Code extension strips hasCompletedOnboarding): https://github.com/anthropics/claude-code/issues/29029 - OpenAI Codex Rust source (no read-side perm check): https://github.com/openai/codex/blob/main/codex-rs/login/src/auth/storage.rs - Cursor CLI in-Docker auth issue: https://forum.cursor.com/t/cursor-agent-authentication-issue-inside-docker/143995 |
||
|
|
6a569e401e |
feat(devcontainer): bind-mount ~/.docker, ~/.aws, ~/.azure for agent workflows
Extend the host bind-mount surface so coding agents inside the container inherit cloud + container-registry auth from the host without any per-container setup: - ~/.docker (read-write) — Docker registry auth (config.json) + buildx config. Container-registry pushes (ghcr.io, docker.io) from inside the container pick up host `docker login` state. Read-write because the Docker CLI refreshes credential-helper tokens. - ~/.aws (read-only) — AWS CLI / SDK credentials. Read-only because rotating creds typically happens via the host. Empty on this dev box, so forward-compatible: the moment you `aws configure` on the host the container picks it up on the next rebuild. - ~/.azure (read-only) — Azure CLI credentials. Same pattern as ~/.aws. `ensure-host-config-dirs.cjs` extends to mkdir these three on init so the bind mounts always have a valid source even if a CLI has never been used on this host. The Docker CLI itself isn't installed in the container by default — the ~/.docker/ mount is inert until you add `docker-outside-of-docker:1` or similar Feature. README now calls this out under "What you still don't have inside the container" so it's obvious which CLIs are agent-ready and which need a feature add to become useful. README updates: - Bind-mount table gains a "Why" column and rows for the three new mounts, making it clear at a glance what each one enables. - Trust-boundary section lists Docker registry tokens, AWS, and Azure creds in the read-side exfil path so the threat model stays honest as the credential surface grows. - New subsection lists not-included CLIs (Docker, AWS, Azure, gcloud, kubectl, private-npm) with the exact Feature ID or mount snippet needed to enable each — turns "I want my agent to do X" into a one-line config change. Verified locally: `npx @devcontainers/cli read-configuration` resolves all 9 host bind mounts to valid C:\Users\<name>/* paths on Windows. |
||
|
|
213390a4b5 |
fix(devcontainer): drop ~/.gitconfig bind mount; defer to VS Code auto-copy
VS Code's Dev Containers extension auto-copies the host's gitconfig into the container at attach time using `(dd ...) >> /home/node/.gitconfig`. A read-only bind mount of ~/.gitconfig blocks that write, so attach failed with `cannot create /home/node/.gitconfig: Read-only file system`. Making it read-write would let the append succeed, but the bind mount means the host file and the container file are the same file — VS Code's append would double the host gitconfig contents on every container start. Drop the ~/.gitconfig bind mount entirely. VS Code's auto-copy is the purpose-built mechanism for this, gives the container the host's user.name / user.email transparently, and avoids both the read-only write failure and the append-duplication trap. The container ends up with a writable /home/node/.gitconfig that's a copy of the host's, not a mount. The remaining six bind mounts (.claude, .codex, .cursor, .ssh, .config/git, .config/gh) keep their existing modes — XDG-style git config under ~/.config/git is unaffected by VS Code's auto-copy (which only targets ~/.gitconfig), so its read-only bind mount stays. Also remove the `.gitconfig` touch from ensure-host-config-dirs.cjs (now unnecessary) and update the README CLI-state table, sharing explanation, and troubleshooting row to reflect that gitconfig flows in via VS Code auto-copy rather than the bind mount. |
||
|
|
a29a1cfb9f |
feat(devcontainer): support Windows-native via auto setx HOME on first run
Reverses the "WSL2 required on Windows" posture. Windows-native now
works after a one-time auto-handled setup.
The root cause of the bind-mount failure: VS Code resolves
`${localEnv:HOME}` by reading its own process env, and Windows doesn't
set `HOME` by default — Windows uses `USERPROFILE`. So the bind sources
were collapsing to `/.claude`, `/.codex`, etc., and Docker rejected them.
`ensure-host-config-dirs.cjs` now handles this automatically on Windows
hosts where `HOME` is unset:
1. Runs `setx HOME "%USERPROFILE%"`, which writes to the user-level
Windows environment (HKCU\Environment) — no admin required. Every
future user process inherits HOME from there.
2. Prints a clear one-time setup banner explaining the user needs to
fully restart VS Code (File > Exit, not just close the window) for
VS Code to pick up the new env at its next startup.
3. Exits 1 so VS Code surfaces this as a clean container-create failure
instead of letting Docker error opaquely later.
On the second Reopen-in-Container attempt, `HOME` is now set in VS
Code's env, the script skips the setup block, creates the bind-mount
source dirs, and the container builds normally. Subsequent rebuilds
have no extra steps.
Mac, Linux, and WSL2 hosts have `HOME` set by the shell, so the new
block is a no-op there. Same `devcontainer.json` works across all
supported hosts.
README rewritten to reflect the new posture:
- Header lists Windows 11 (native) as a supported host alongside macOS,
Linux, and WSL2, with a note that Windows-native gets a one-time
HOME setup handled by the initializeCommand.
- New "Windows 11 setup" section walks through the auto-handled setup
flow + a manual `setx HOME "%USERPROFILE%"` fallback for users who
want to do it themselves.
- "Known trade-offs of Windows-native vs WSL2" subsection lays out the
Docker Desktop Windows bind-mount edge cases (file watchers, npm
install perf, husky/_ EPERM) so users opting into Windows-native do
so eyes-open. WSL2 remains documented as the faster path for users
who want it, but it's no longer the only supported one.
- Troubleshooting table gets two new rows: the one-time setup banner
(with "what to do" instructions) and the residual `bind source path
does not exist` case (run setx manually + fully exit VS Code).
|
||
|
|
f4dc20289a |
fix(devcontainer): fail-fast on Windows-native with HOME-not-set diagnostic
The previous commit's "Windows-native works" softening was wrong. VS Code
on Windows-native resolves `${localEnv:HOME}` by reading the host shell's
HOME env var, and cmd.exe has no HOME set — the bind sources collapse to
`/.claude`, `/.codex`, etc., and Docker errors:
Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /.claude
The @devcontainers/cli output that prompted the softening was misleading
because I ran it from a Bash session with HOME already set, not from VS
Code's cmd.exe call context. The original Finding-1 P0 — that Windows-
native silently breaks the bind-mount feature — was correct.
Three changes:
1. `ensure-host-config-dirs.cjs` detects the failure mode early:
`if (process.platform === 'win32' && !process.env.HOME)` prints a
targeted error message naming the root cause (cmd.exe has no HOME →
${localEnv:HOME} resolves empty → bind sources fail) and a step-by-step
pointer to set up WSL2. Exits 1 so VS Code surfaces it as a clean
container-creation failure, not the cryptic Docker bind-mount error.
2. README header reverted to "Windows 11 via WSL2" only (not "and
Windows-native"). The "Windows 11 — WSL2 is required" section names
the specific HOME-resolution mismatch concretely so future readers
understand why the constraint exists.
3. Troubleshooting table gets a new row for the `ERROR: GitNexus
devcontainer requires WSL2` message pointing at the setup section.
|
||
|
|
f592c804ed |
fix(devcontainer): Node-based initializeCommand; bind-mount .ssh + .config/git
Two fixes bundled:
1. The previous commit's OS-keyed `initializeCommand` object was based
on a misread of the Dev Containers spec. The object form on command
properties is **named parallel tasks**, not OS dispatch — VS Code ran
all three keys in parallel via cmd.exe on Windows, the POSIX branches
failed, and container creation aborted before Docker was invoked.
Restore the single-string Node-based form:
`node .devcontainer/ensure-host-config-dirs.cjs`. Node works
identically in cmd.exe on Windows and bash/zsh on Linux/macOS/WSL,
and `os.homedir()` respects $HOME on POSIX and %USERPROFILE% on
Windows. The script is idempotent (mkdirSync recursive is a no-op
for existing dirs; touch is gated on .gitconfig existence).
Document Node ≥18 on the host as the only host-side prerequisite
beyond Docker Desktop and the VS Code Dev Containers extension.
Anyone running Claude Code on the host already has it.
2. Extend the host-bind mount surface with `~/.ssh` and `~/.config/git`,
both read-only:
- `~/.ssh` lets commit signing + push over SSH remotes work inside
the container without copying private keys. Read-only mount means
container code can read keys but can't modify or delete them.
(Threat: a malicious dep can still read private keys from inside
the container; the read-only mount narrows write-side blast
radius, not read-side. Documented in the trust-boundary section.)
- `~/.config/git` covers XDG-style git config (`~/.config/git/config`,
`~/.config/git/ignore`, `~/.config/git/attributes`) for users who
keep settings there instead of `~/.gitconfig`. Read-only, same as
`~/.gitconfig`.
Update the CLI-state-sharing table and trust-boundary paragraph to
reflect the expanded surface.
Re-adds .devcontainer/ensure-host-config-dirs.cjs (deleted before the
OS-keyed attempt).
|
||
|
|
32cb7dc28a |
fix(devcontainer): cross-platform initializeCommand + soften Windows-native posture
The previous commit's `initializeCommand` was POSIX-only (`mkdir -p $HOME/...`).
VS Code on Windows runs the host shell as `cmd.exe /c ...`, which can't
parse POSIX syntax — `$HOME` doesn't expand, `mkdir -p` errors, the init
fails with `The syntax of the command is incorrect`, and container
creation aborts before Docker is invoked.
Switch `initializeCommand` to the spec's OS-keyed object form:
- linux/darwin (covers WSL2 because VS Code runs initializeCommand in
the WSL shell when attached via the WSL extension): POSIX mkdir+touch,
as before
- win32: PowerShell snippet that creates the same directories under
$USERPROFILE and touches the gitconfig if missing
Soften the README's hard "WSL2 required" framing from the previous
commit. Reality per `@devcontainers/cli read-configuration` output:
`${localEnv:HOME}` on Windows-native resolves to `C:\Users\<name>`
(VS Code falls back to USERPROFILE), so the bind mount sources are
valid Windows paths and Docker Desktop handles the translation. The
earlier `accessing specified distro mount service` failure was a
separate Docker Desktop WSL-integration issue, not a HOME-resolution
issue. Windows-native works; it's just slower with more bind-mount
permission edge cases (the husky/_/h EPERM class). The README now
explains the tradeoff and steers toward WSL2 for performance + file
watchers + permission reliability, rather than blocking Windows-native
checkouts outright.
Update the troubleshooting row to reflect the new posture.
|
||
|
|
cd8ac5f6d1 |
refactor(devcontainer): address ce-code-review findings (P0 + 4 × P1 + 8 × P2 + 2 × P3)
Walkthrough resolution of the 16-finding ce-code-review on PR #1875. 15 of 16 findings applied; one (F12, Anthropic Feature floating tag) was superseded by F6's Feature removal. P0 - F1: WSL2 is now REQUIRED for Windows hosts, not just recommended. ${localEnv:HOME} resolves to empty string on Windows-native (no HOME env var) — bind mounts then point at /.claude, /.codex etc. and silently break. ensure-host-config-dirs.cjs wrote to USERPROFILE-derived paths via os.homedir(), so the two surfaces disagreed about which env var was "home" on Windows. README header reframed; "Windows 11 — WSL2 is required" section explains the mismatch concretely. P1 - F2: Workspace `node_modules` volume names now include `-${devcontainerId}` so two GitNexus checkouts on the same host (~/work/GitNexus and ~/projects/GitNexus) don't share volumes and corrupt each other's installs. - F3 + F5: `postCreateCommand` extracted to `.devcontainer/post-create.sh` with `set -euo pipefail` and six labeled echo steps so failure logs name the step instead of an opaque &&-chain index. Chown step extended to cover /home/node/.npm, /commandhistory, and /home/node/.local — these named-volume mount points were owned by build-time UID 1000 but the container's `node` is re-IDed at runtime by updateRemoteUserUID on non-1000 Linux hosts, leaving them unwritable until now. - F4: Cursor installer downloaded to a temp file with curl --retry + --max-time; sha256 logged to build output before execution so drift across rebuilds is visible in CI logs. Full hard-pin (to a versioned downloads.cursor.com tarball with verified sha256) tracked as a follow-up in README "What's not included". P2 - F6: Anthropic Feature replaced with a direct `npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}` so CLAUDE_CODE_VERSION actually pins the installed binary (the Feature ignored the ARG and pulled latest at install time). Honors the earlier "pin known-good versions" decision and resolves F12's floating-tag concern for this Feature. - F7: Dockerfile ARG defaults dropped for the three version vars; `devcontainer.json` `build.args` is now the single source of truth. Standalone `docker build .devcontainer/` must pass --build-arg. - F8: ensure-host-config-dirs.cjs deleted; `initializeCommand` now uses POSIX `mkdir -p` + `touch ~/.gitconfig` directly, dropping the host-Node-on-PATH prerequisite that broke on fresh Windows+Docker Desktop installs without Node. - F9: ~/.gitconfig bind-mounted read-only so `git commit` inside the container uses the host's user.name / user.email. Read-only so container-side `git config --global` doesn't leak to host. - F10: ~/.config/gh bind-mounted (read-write) so `gh pr create` / `gh pr checks` / `gh issue create` work inside the container without re-auth. AGENTS.md's commit + PR workflow now fully functional for agents inside the container. - F11: CLAUDE_CONFIG_DIR removed from Dockerfile ENV; canonical value lives only in devcontainer.json containerEnv. Eliminates the two-file edit risk. - F13: Mounts comment now documents per-instance vs per-workspace-name scoping rationale so future contributors don't guess. - F14: README "Trust boundary, concretely" paragraph names the exfil path explicitly (malicious npm postinstall → OAuth tokens → ~/.claude/projects/<workspace>/memory/MEMORY.md secrets) and lists vendor-side rotation runbook entries. P3 - F15: Dockerfile pre-create + chown of /home/node/.claude, .codex, .cursor dropped — those paths are bind-mounted, which fully shadows any image-side ownership. Only .npm, .local, /commandhistory still benefit from the pre-create. - F16: README "Bumping CLI versions" section rewritten against the post-F6 reality: CLAUDE_CODE_VERSION and CODEX_VERSION are real pins; CURSOR_VERSION is informational only. Verified locally: `docker build .devcontainer/ --build-arg ...` succeeds. Smoke-tested image: `claude --version` (2.1.153), `codex --version` (0.134.0), `cursor-agent --version` all resolve as the non-root `node` user; named-volume mount points (/home/node/.npm, /commandhistory) are node-owned at build time so non-1000 host UIDs get the post-create.sh chown fix instead of EACCES. |
||
|
|
35e61a9e66 |
feat(devcontainer): bind-mount host CLI config dirs for plugin/skill/memory sync
Switch the credential/config mounts from per-devcontainer named volumes
to bind mounts of `${localEnv:HOME}/.claude`, `~/.codex`, and
`~/.cursor`. Effect inside the container:
- Authentication is shared with the host. If you've already run
`claude login` / `codex login --device-auth` / `cursor-agent login`
on the host, you're already authenticated in the container.
- Plugins, skills, agents, memory, and settings sync both ways. Install
a plugin in the container, it shows up on the host; add a custom
agent on the host, the container sees it immediately.
- All devcontainers on the host share the same CLI state, mirroring
how host shells already share it. (Per-workspace isolation of plugins
was never a stated requirement; the previous per-devcontainer named
volumes leaked nothing useful.)
Add `.devcontainer/ensure-host-config-dirs.cjs` and wire it as
`initializeCommand`. It runs on the host before container create and
guarantees `~/.claude`, `~/.codex`, `~/.cursor` exist, so Docker doesn't
reject the bind mount when a CLI has never been used on this host.
Cross-platform via Node `os.homedir()` + `fs.mkdirSync({recursive: true})`;
idempotent; no third-party deps.
Update `.devcontainer/README.md`:
- New "How CLI state is shared with your host" section explaining the
bind-mount model up front so users know their host plugins/skills/
memory carry into the container.
- Mark first-time-login section as skippable when the user is already
authenticated on the host.
- Note the high-trust escape hatch: replace the three bind mounts with
`type=volume` named volumes if the host/container trust boundary
needs to be separated (Anthropic's reference pattern for enterprise).
- Replace the obsolete "rm named volume" troubleshooting row with one
that covers EACCES/EPERM on the host-bind-mount path.
|
||
|
|
a75fc2e060 |
fix(devcontainer): clear stale .husky/_ before npm install
When `npm install` runs the root `prepare` script (husky), husky tries
to copyfile `node_modules/husky/husky` → `.husky/_/h`. On Docker Desktop
Windows bind mounts, if `.husky/_/` already exists from a prior
container run, the new container's `node` user can't overwrite it via
the bind mount's permission translation and the install fails with:
Error: EPERM: operation not permitted, copyfile
'/workspace/node_modules/husky/husky' -> '.husky/_/h'
Drop `.husky/_` defensively in `postCreateCommand` before `npm install`
so husky always starts from a clean slate. `.husky/_` is a husky
runtime cache (gitignored), so removing it has no effect on the repo —
husky regenerates it. No-op for WSL2-side checkouts (where this class
of bind-mount permission collision doesn't occur).
Add a troubleshooting row to `.devcontainer/README.md` covering the
manual recovery (`rm -rf .husky/_` on the host) and the long-term fix
(clone in WSL2 — Windows-side bind mounts will keep biting on this
kind of issue across rebuilds with different UID alignment).
|
||
|
|
326cb06fe7 |
fix(devcontainer): install gitnexus-web before gitnexus in postCreateCommand
The previous order (root → gitnexus-shared → gitnexus → gitnexus-web) broke at the `gitnexus` install step because `gitnexus`'s `prepare` script runs `scripts/build.js`, which compiles `gitnexus-web` whenever its source tree exists. In the devcontainer the entire workspace is bind-mounted, so `gitnexus-web/` is present from the start — but its `node_modules/` wasn't yet, so `tsc -b` failed with: error TS2688: Cannot find type definition file for 'vite/client' error TS2688: Cannot find type definition file for 'node' Reorder so `gitnexus-web` installs before `gitnexus`. Verified end-to-end via `npx @devcontainers/cli up`: container builds clean, all three CLIs (Claude 2.1.153, Codex 0.134.0, Cursor) respond, and `npx tsc --noEmit` inside `/workspace/gitnexus` passes. Production Dockerfiles (`Dockerfile.cli` etc.) don't hit this because they only COPY `gitnexus/` + `gitnexus-shared/`, so `gitnexus-web/` doesn't exist at install time and `scripts/build.js` skips the web step. The devcontainer's full-tree bind mount changes that calculus. |
||
|
|
05e9c58fb1 |
fix(devcontainer): make interactive login the default auth path for all CLIs
The previous `containerEnv` injected `CURSOR_API_KEY: "${localEnv:CURSOR_API_KEY}"`.
When the host had no `CURSOR_API_KEY` set, this resolved to an empty
string and Docker injected `CURSOR_API_KEY=""` into the container.
Cursor CLI treats a set-but-empty `CURSOR_API_KEY` as "use this key"
rather than "fall back to stored login", which silently broke
`cursor-agent login` on the most common path — users who hadn't
explicitly opted into API key auth.
Drop `CURSOR_API_KEY` from `containerEnv`. Login is now the
unconditional default for all three CLIs (Claude Code, Codex CLI,
Cursor CLI); the named-volume + Dockerfile-chown pattern keeps
credentials persistent across container rebuilds for every login path.
Reorganize the README's auth section to put login first for all three
CLIs uniformly (matching the new behavior) and move API key
authentication into a separate "Alternative" section for CI/headless
use. Document that API keys are intentionally not auto-propagated from
the host and explain the export-in-shell or VS Code dotfiles-repo paths
for users who want them. Update the troubleshooting row to reflect the
new design.
|
||
|
|
9c07ef9cf9 |
feat(devcontainer): add cross-platform devcontainer for Claude Code, Codex, and Cursor CLIs
Add a Dev Container that pre-installs Claude Code (2.1.153, via Anthropic's
official Feature), OpenAI Codex CLI (pinned 0.134.0), and Cursor CLI alongside
the GitNexus native build chain. Opens via VS Code's Dev Containers extension
on Windows 11 (Docker Desktop + WSL2), macOS, or Linux without OS-specific
branches in devcontainer.json.
Topology and base
- Base image `mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm`
(multi-arch, monthly patched, ships the `node` non-root user, zsh, `gh`).
- Node 22 LTS satisfies `gitnexus/`'s engines `>=22.0.0` and matches the
`node:22-bookworm-slim` SHA-pinned base used by `Dockerfile.cli`.
- Single container with all three CLIs co-installed (vs. docker-compose
per-tool) — prevailing 2026 community pattern, lowest daily-driver friction.
Persistence and auth
- Per-devcontainer named volumes scoped by `${devcontainerId}` for
`/home/node/.claude`, `/home/node/.codex`, `/home/node/.cursor`,
`/commandhistory`, and `/home/node/.npm`. Authentication survives rebuilds
without leaking between workspaces.
- Four sub-workspace `node_modules` volumes (root, gitnexus, gitnexus-web,
gitnexus-shared) keep tree-sitter native bindings and onnxruntime off the
bind mount — the actual Win/Mac perf win.
- Credential mount paths are pre-created in the Dockerfile with
`chown node:node` BEFORE `USER node`, so empty named volumes inherit
correct ownership on first mount and first-run logins don't EACCES.
- `CURSOR_API_KEY` is injected via `containerEnv: ${localEnv:CURSOR_API_KEY}`
(Cursor's documented headless path); falls back to interactive
`cursor-agent login` when the host env var is unset.
Build-arg promotion
- Build args (`CLAUDE_CODE_VERSION`, `CODEX_VERSION`, `CURSOR_VERSION`, `TZ`)
are promoted to ENV in the Dockerfile so lifecycle commands and shells can
resolve them. Without this promotion, Docker ARG values are build-only and
silently no-op at lifecycle time.
Workspace setup
- `postCreateCommand` chowns the four workspace `node_modules` volumes
(Docker creates them root-owned), then installs in dependency order:
root → gitnexus-shared (install + build) → gitnexus → gitnexus-web. The
shared package must build before its consumers (`file:../gitnexus-shared`).
Ports
- 5173 (Vite dev) and 4173 (Vite preview) auto-forwarded.
- 4747 (`gitnexus serve`) marked `requireLocalPort: true` because
`gitnexus-web/src/services/backend-client.ts` hardcodes
`http://localhost:4747` as the default backend URL; a remapped port would
silently break the web UI.
VS Code integration
- Recommended extensions: `anthropic.claude-code`,
`dbaeumer.vscode-eslint`, `esbenp.prettier-vscode`, `eamodio.gitlens`.
- Settings: format-on-save with Prettier, ESLint auto-fix on save, zsh as
default terminal profile, persistent zsh history via `HISTFILE` →
`/commandhistory`.
Documentation
- `.devcontainer/README.md` covers WSL2 setup (clone inside WSL2 for IO and
file-watcher reliability), first-time auth flows for each CLI, port-
forwarding notes, LadybugDB container limitations, and the bumping
procedure for each CLI version.
- `CONTRIBUTING.md` gets a "Containerized development (optional)"
subsection pointing at the devcontainer README.
Deferred to a follow-up PR
- Opt-in egress firewall (originally planned as a fourth implementation
unit). The Dev Containers spec makes `runArgs` static — toggling
`NET_ADMIN`/`NET_RAW` capabilities cleanly requires either a separate
`devcontainer-firewall.json` profile or an `initializeCommand`-generated
overlay. Keeping this PR focused on the working baseline.
- Codespaces-specific tuning (works incidentally when the firewall is off,
not actively tested).
- Inside-container Playwright e2e (needs Chromium libs not in the base
image).
Verification deferred to user
- This change introduces a new dev tooling artifact. Validate by running
`docker build .devcontainer/`, opening the repo in VS Code via
"Dev Containers: Reopen in Container", confirming `claude --version`,
`codex --version`, `cursor-agent --version` resolve inside the container,
and `cd gitnexus && npm run test:unit` runs clean against the
named-volume `node_modules`.
|
||
|
|
ccf9a4d564 |
chore: extend .gitattributes for shell scripts and binary assets
Append explicit `*.sh text eol=lf` and `*.bash text eol=lf` rules so shell scripts (notably anything COPYed into a Linux container) check out with LF endings on Windows hosts with `core.autocrlf=true`, regardless of the auto-detection on the existing `* text=auto eol=lf` line. Add binary markers for `*.node`, `*.wasm`, `*.onnx`, `*.so`, `*.dll`, `*.dylib` so native and ML model artifacts aren't ever subjected to text normalization. The existing `* text=auto eol=lf` and `.husky/* text eol=lf` rules are preserved. `git ls-files --eol` confirmed zero CRLF or mixed blobs in the index, so no `--renormalize` was needed. |
||
|
|
50715e3894
|
chore(deps)(deps-dev): bump @playwright/test in /gitnexus-web (#1860)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.58.2 to 1.60.0. - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.58.2...v1.60.0) --- updated-dependencies: - dependency-name: "@playwright/test" dependency-version: 1.60.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Abhigyan Patwari <126312502+abhigyanpatwari@users.noreply.github.com> |
||
|
|
ca95df6316
|
chore(deps): bump github/codeql-action from 4.35.4 to 4.35.5 (#1866)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.4 to 4.35.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](
|
||
|
|
9d609cc386
|
chore(deps)(deps): bump axios from 1.16.0 to 1.16.1 in /gitnexus-web (#1864)
Bumps [axios](https://github.com/axios/axios) from 1.16.0 to 1.16.1. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v1.16.0...v1.16.1) --- updated-dependencies: - dependency-name: axios dependency-version: 1.16.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
128a199970
|
chore(deps)(deps-dev): bump @types/node in /gitnexus-web (#1863)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.6.0 to 25.9.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.9.1 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
76409783aa
|
chore(deps)(deps): bump @langchain/langgraph in /gitnexus-web (#1861)
Bumps [@langchain/langgraph](https://github.com/langchain-ai/langgraphjs/tree/HEAD/libs/langgraph-core) from 1.2.9 to 1.3.2. - [Release notes](https://github.com/langchain-ai/langgraphjs/releases) - [Changelog](https://github.com/langchain-ai/langgraphjs/blob/main/libs/langgraph-core/CHANGELOG.md) - [Commits](https://github.com/langchain-ai/langgraphjs/commits/@langchain/langgraph@1.3.2/libs/langgraph-core) --- updated-dependencies: - dependency-name: "@langchain/langgraph" dependency-version: 1.3.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
99168be773
|
feat(ingestion): trace indirect call patterns — FastAPI Depends() and frontend HTTP consumers (#1852) | ||
|
|
d9d6318b64
|
feat(group): add Kotlin Spring HTTP consumer extraction (#1855)
* feat(group): add Kotlin Spring HTTP consumer extraction Follow-up to #1849 (Kotlin providers). Extends `http-patterns/kotlin.ts` with three call-site patterns common in Kotlin Spring projects: - RestTemplate: `restTemplate.getForObject("/x", ...)` and the full verb family (getForObject/getForEntity → GET, postForObject/postForEntity → POST, put → PUT, delete → DELETE, patchForObject → PATCH). Mirrors the Java plugin's `REST_TEMPLATE_TO_HTTP` map so polyglot repos coalesce on a single contract id. - WebClient short form: `webClient.get().uri("/x")` and the `.post()` / `.put()` / `.delete()` / `.patch()` siblings. The chain parses as two nested `call_expression` nodes; the query anchors on the outer `.uri(...)` and walks one level inward to constrain the verb. - OkHttp: `Request.Builder().url("/x")`. Kotlin parses `Request.Builder()` as a `call_expression` whose callee is a `navigation_expression` (not Java's `object_creation_expression`), so the query shape differs from `java.ts` but the receiver/method constraints (`Request` / `Builder` / `url`) and emitted contract format match. Out of scope: `webClient.method(HttpMethod.X).uri("/y")` long form. The verb sits on a sibling `call_expression` two hops away, so it needs a walk-up helper rather than a flat tree-sitter query. A dedicated anti-overreach test pins the current behavior so a future short-form change can't accidentally start matching the long form. Receiver name constraints (`#eq? @obj "restTemplate"`, `#eq? @cls "Request"`) match the Java plugin's heuristic — a project that aliases the receiver under a different name won't be picked up. This trade-off keeps false-positive rates low and is documented in the file header. Tests: 5 new cases under `consumer extraction — fetch patterns`, gated by tree-sitter-kotlin grammar availability. positive (3) - RestTemplate verbs (5 calls × 5 verbs) - WebClient short-form verbs (5 calls × 5 verbs) - OkHttp Request.Builder().url("/x") anti-regression (2) - WebClient long form `.method(HttpMethod.X)` produces no consumer (deferred-feature pin) - non-restTemplate receiver does not match (receiver-name pin) Reverse-validated: removing the `(#eq? @obj "restTemplate")` constraint causes the receiver-name anti-regression test to fail. Local validation: - test/unit/group/http-route-extractor.test.ts: 59/59 ✅ - test/unit/group: 539/539 ✅ - npm run format:check: clean ✅ * test(group): pin Kotlin OkHttp POST-chain heuristic-default GET behavior Address Claude review on PR #1855 (Finding 1). The OkHttp query in `kotlin.ts:OK_HTTP_PATTERNS` matches the `.url("/x")` sub-expression of a builder chain, but the verb is encoded on a separate sibling call (`.post(body)` / `.delete()` / ...). The query intentionally does not walk the chain to recover the verb — it emits `method: 'GET'` for every match, mirroring the Java plugin's `OK_HTTP_PATTERNS` (java.ts). Concretely: `Request.Builder().url("/x").post(body).build()` becomes `http::GET::/x`, not `http::POST::/x`. This is an already-accepted Java parity heuristic, but it was untested on the Kotlin side. This commit: - Adds an anti-overreach test pinning the current behavior: * exactly one consumer is emitted with method=GET * no second http::POST::/x consumer appears - Documents the limitation in kotlin.ts as a "Known limitation" block tied to the test, so a future verb-walk implementation has to update the comment in lockstep with the assertion. Rationale for not implementing verb-walk in this PR: - Verb-walk requires walking sibling call_expression nodes (the `.post(body)` chain), which is the same shape as the deferred WebClient long-form work - Java has the same limitation in production today; fixing only Kotlin would create polyglot drift - A coordinated future PR can add verb-walk to both plugins at once and update both comments + the pin tests together Finding 2 (silent test-skip when tree-sitter-kotlin grammar is unavailable) is intentionally NOT addressed here — same gating pattern was accepted in #1849 for Provider tests, and a coordinated follow-up should add a CI sentinel covering both Provider and Consumer suites in one place. Local validation: - test/unit/group/http-route-extractor.test.ts: 60/60 ✅ - test/unit/group: 540/540 ✅ - npm run format:check: clean ✅ --------- Co-authored-by: henry <zhangwei2017@unipus.cn> |
||
|
|
46eb0ebf56
|
feat(group): add Kotlin Spring HTTP route extraction (named + positional) (#1849)
* feat(group): add Kotlin Spring HTTP route extraction (named + positional)
Mirror the Java Spring named-argument fix for Kotlin Spring Boot
controllers. Adds a new `http-patterns/kotlin.ts` plugin behind the
optional `tree-sitter-kotlin` grammar, registered for `.kt`/`.kts`.
Both annotation forms produce providers:
@RequestMapping("/api") / @GetMapping("/users")
@RequestMapping(path = "/api") / @GetMapping(value = "/users")
@RequestMapping(value = "/api") / @GetMapping(path = "/users")
The Kotlin AST (fwcd/tree-sitter-kotlin) shares one node type
(`value_argument`) for positional and named forms, so the queries
are split:
- positional: anchors `string_literal` as the first named child
of `value_argument` via the immediate-child anchor `.`
- named: explicitly captures `simple_identifier` and constrains
it to `^(path|value)$` via `#match?`, mirroring the same
safety bar enforced by `http-patterns/java.ts` and
`topic-patterns/java.ts`. Without this constraint the query
would also capture non-route attributes like `produces`,
`consumes`, `headers`, `name`, `params`.
`tree-sitter-kotlin` is an optionalDependency (parser-loader.ts,
parse-worker.ts pattern). When the native binding is unavailable
the plugin exports `null` and `index.ts` skips registering
`.kt`/`.kts` so the orchestrator stays healthy.
Scope: providers only. Consumer detection (RestTemplate, WebClient,
OkHttp) on Kotlin call-site ASTs differs enough from Java's
`method_invocation` shape to warrant a separate, focused PR.
Tests: 11 new cases under `provider extraction — source-scan
fallback (Strategy B)`, gated by the kotlin grammar availability.
positive (8)
- class @RequestMapping("/api/v1") (positional)
- class @RequestMapping(path = "/api/v2")
- class @RequestMapping(value = "/orders")
- method @GetMapping(value = "/users")
- method @GetMapping(path = "/users")
- method @PostMapping(path = "/users")
- mixed: class named-arg + method positional
- mixed: class positional + method named-arg
anti-regression (3)
- @GetMapping(produces = "application/json") emits no provider
- @GetMapping(name = "x", value = "/users") emits exactly one provider
- @RequestMapping(path = "/api", name = "myApi") prefix stays /api
Reverse-validated: removing the `(#match? @key "^(path|value)$")`
constraint causes precisely the 3 anti-regression tests to fail.
Local validation:
- test/unit/group/http-route-extractor.test.ts: 54/54
- test/unit/group: 534/534
- npx tsc --noEmit: clean (modulo the pre-existing TS2339 in
user-defined-conversions.ts merged from main, unrelated)
* style(test): apply prettier line wrapping to long itKotlin titles
---------
Co-authored-by: henry <zhangwei2017@unipus.cn>
|
||
|
|
eeea46466b
|
fix(group): handle named annotation args in Java Spring route extraction (#1834)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* fix(group): handle named annotation args in Java Spring route extraction
The Java HTTP plugin only matched positional `@RequestMapping("/path")`
syntax for class-level prefixes and method-level routes. Named argument
forms (`path = "/path"` and `value = "/path"`) produce an
`element_value_pair` AST node that the tree-sitter queries did not cover,
causing the class prefix to be lost and named-arg method routes to be
missed entirely during cross-repo contract extraction.
Add a second pattern to both SPRING_CLASS_PREFIX_PATTERNS and
SPRING_METHOD_ROUTE_PATTERNS matching the element_value_pair structure.
* fix(group): constrain Spring named-arg query to path/value keys + add regression tests
Address Claude review on PR #1834. The named-argument patterns added
in
|
||
|
|
ca3e1755c2
|
chore(deps)(deps): bump lru-cache from 11.4.0 to 11.5.0 in /gitnexus (#1844)
Bumps [lru-cache](https://github.com/isaacs/node-lru-cache) from 11.4.0 to 11.5.0. - [Changelog](https://github.com/isaacs/node-lru-cache/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-lru-cache/compare/v11.4.0...v11.5.0) --- updated-dependencies: - dependency-name: lru-cache dependency-version: 11.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
6acdc49f06
|
chore(deps)(deps-dev): bump @types/node in /gitnexus (#1845)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.9.0 to 25.9.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.9.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
b1445daf04
|
feat(cpp): rank user-defined conversions (#1829) | ||
|
|
d903152eba
|
fix(typescript): reuse suffix index in scope resolver (#1840)
* fix(typescript): reuse suffix index in scope resolver Build a suffix index once per TypeScript scope-resolution pass and pass it into standard import resolution so package-style imports avoid repeated linear file-list scans.\n\nFixes #1839 * test(typescript): add wiring-level test for scope-resolver suffix index - Test typescriptScopeResolver.resolveImportTarget directly (the real production entry point) with package-style, unresolvable, and relative imports - Use vi.spyOn on buildSuffixIndex to verify the index is built inside the makeTsResolveImportTarget closure — fails if index wiring is removed - Fix existing test to pass real file lists instead of empty arrays alongside the prebuilt index, matching production wiring --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Test <test@example.com> |
||
|
|
6c572749b0
|
fix(web): stop Nexus AI agent when user clicks Stop (#1820)
* fix(web): stop Nexus AI agent when user clicks Stop Wire AbortController through chat streaming so Stop cancels the LangGraph run instead of only hiding the loading UI. Fixes #1615. * fix(web): address PR review feedback for Nexus AI stop Guard stream cleanup against Stop-then-Send races, remove dead cancelled handler, tighten abort error detection, add stopped tool-call status, and extend abort unit tests. Fixes #1615. * chore(autofix): apply prettier + eslint fixes via /autofix command * fix(web): address review findings for Nexus AI stop/cancel - Fix race conditions in useAppState.tsx abort lifecycle: - Replace stale isChatLoading closure guard with chatStateRef - Track and cancel rAF handles in stopChatResponse/finally - Move cancelled chunk check before onChunk dispatch - Simplify finally block to unconditional cleanup via chatStateRef - Guard tool_result from overwriting stopped status - Have clearChat abort in-flight streams before clearing - Reorder isAbortError to check error identity before signal.aborted - Refactor AgentStreamChunk to discriminated union for exhaustive switch - Fix test assertions to use exact .toEqual() per DoD §2.7 - Add test for plain Error with name AbortError - Remove dead markStopped alias, simplify signal spread-conditional --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Test <test@example.com> |
||
|
|
7556a8e73a
|
feat(cobol): migrate COBOL to scope-based resolution (regex provider) (#941) (#1835)
* feat(cobol): migrate COBOL to scope-based resolution (regex provider)
Migrate COBOL to scope-based registry resolution, validating the
parse-source-agnostic contract — COBOL uses regex, not tree-sitter,
but implements the same LanguageProvider interface via emitScopeCaptures.
Phase 1-5 complete per #941 DoD.
New files:
languages/cobol/captures.ts — emitScopeCaptures wrapping regex tagger
languages/cobol/interpret.ts — import/type-binding/receiver hooks
languages/cobol/index.ts — barrel export
languages/cobol/scope-resolver.ts — ScopeResolver wiring (9 fields, 3 toggles)
Modified files:
languages/cobol.ts — wire 4 scope-resolution hooks
registry.ts — register cobolScopeResolver
registry-primary-flag.ts — document REGISTRY_PRIMARY_COBOL
Fixtures:
17 fixture files, 30 test cases across 11 required classes
test/integration/resolvers/cobol-scope.test.ts
Tests: 24/24 pass (default + REGISTRY_PRIMARY_COBOL=0)
tsc: zero cobol-specific errors
Shadow mode (GITNEXUS_SHADOW_MODE=1): zero crashes
Regex perf: 10K-line file in 408ms (threshold: 2000ms)
NOT added to MIGRATED_LANGUAGES — REGISTRY_PRIMARY_COBOL env var only.
* chore(cobol): add COBOL to MIGRATED_LANGUAGES
* Revert "chore(cobol): add COBOL to MIGRATED_LANGUAGES"
This reverts commit
|
||
|
|
c8117d1292
|
feat(web): Introduce Tree View and Circles View in Web Viewer (#1799)
* feat(graph-view): add tree and circles layout modes
Add alternate graph layouts to the web viewer with new graph view state, canvas controls, adapters, and Sigma layout logic for tree and concentric-circle rendering. Include layout and adapter tests plus tree-view E2E coverage aligned with the English UI labels, and tune node visibility, edge layering, large-graph behavior, and tree-layer spacing so the new views stay readable. Follow up the tree-view work by keeping noisy variables hidden by default and mapping Property/Const icons so filter coverage stays in sync with the expanded node taxonomy.
Co-authored-by: OpenAI Codex <noreply@openai.com>
AI-model: GPT-5 Codex
* fix(web): cap tree layout spring iterations and remove unused variable
Finding A (blocker): calculateTreeLayout runs 14 synchronous spring
iterations over all edges and nodes — O(N×E×14) + O(N log N) per layer
per iteration — with no size guard. At 10K+ nodes this freezes the
main thread for several seconds.
Fix: make SPRING_ITERATIONS adaptive:
- N > 10 000 → 0 iterations (proportional initial layout only)
- N > 3 000 → 4 iterations
- otherwise → 14 iterations (unchanged behaviour for small graphs)
Also removes the unused `const r` at useSigma.ts:1314, which was a
leftover after the radial-resistance decomposition was removed.
This clears the CodeQL "unused variable" warning (Finding G).
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
* test(graph-adapter): add circles adapter tests and tree layout perf bound
Finding B (high): knowledgeGraphToCirclesGraphology had zero test
coverage. Adds three new tests:
- ring placement: verifies Folder→ring 0, File→ring 1, Function→ring 3
and confirms circles-specific attributes (circlesRing, circlesAnchorX/Y)
are set while tree attributes (treeAnchorX/Y) are absent.
- edge styling: CONTAINS is marked isHierarchyEdge=true with the
hierarchy colour; CALLS is cross-cutting with its own colour.
- CALLS cross-cutting: a lone CALLS edge between two Functions is
correctly identified as a non-hierarchy edge.
Also adds a performance-bound test for the tree adapter at 2 000 nodes /
4 000 edges (the adaptive 14-iteration path) asserting completion within
2 s — catches regressions to the O(N×E×iterations) main-thread blocking
that Finding A identified.
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
* refactor(web): rename Tree View → Sequential Layout, Circles → Radial Layout
Aligns the UI labels with standard graph layout terminology from the
Cambridge Intelligence taxonomy (cambridge-intelligence.com/blog/automatic-graph-layouts):
Tree View → Sequential Layout (顺序布局)
Circles → Radial Layout (径向布局)
Force Graph → Force Graph (unchanged)
Internal graphViewMode keys ('tree', 'circles', 'force') are unchanged —
only the displayed strings in en/zh-CN locales and the E2E button selectors
are updated.
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
* perf(web): add adaptive large-graph guards to sequential layout physics
For graphs with N > 5 000 nodes, each rAF frame of runTreeLayout was
doing O(N log N) sort + O(N × k) repulsion pair comparisons (k ≈ 2 400
for a 20 K-node graph spread across 1 080 px at range 130). At that
scale each frame took hundreds of ms, making the canvas appear completely
frozen even though the physics loop was still running.
Fix mirrors the circles layout adaptive strategy:
N > 5 000 (large):
- Skip repulsion pass (O(N × k) → 0)
- Skip spread-force sort (O(N log N) → 0)
- Velocity cap raised to ±12 / ±6 px so nodes cover ground faster
- Damping 0.58, 1 sim step/frame, 30 s max duration
- Looser early-stop thresholds (max v 0.05, avg v 0.03, active 2 %)
N > 1 500 (medium):
- Velocity cap raised to ±6 / ±3 px
- 24 s max duration
- Repulsion and spread still active
N ≤ 1 500 (small):
- Unchanged behaviour (velocity ±3/±2, 18 s, all forces active)
Layer gravity (O(N)) and edge springs (O(E)) run for all graph sizes —
they provide the structural pull that replaces repulsion at large N.
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
* fix(web): fix stale closure in sigma event handlers breaking node selection
The sigma 'clickNode', 'clickStage', 'enterNode', and 'leaveNode' handlers
are registered in a one-time useEffect (empty dep array). They captured
options.onNodeClick via closure, so they always called the initial version
of handleNodeClick — the one created before the graph loaded where
`if (!graph) return` exits immediately.
Consequence: clicking a node in the canvas never updated the app-level
selectedNode state. This broke:
- The Focus Depth filter (warning "Select a node to apply depth filter"
persisted even after a canvas click)
- The depth hop filter not applying (selectedNode was always null)
- The code panel not opening on canvas node click
Fix: store the three callback props in refs (onNodeClickRef, onNodeHoverRef,
onStageClickRef) and update them synchronously on every render. The sigma
event handlers now read from the refs, so they always invoke the latest
version of the callbacks without needing to re-register.
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
* fix(web): address three code-review bugs in graph rendering
Bug 1 (useSigma.ts): forces in the tree physics loop were computed once
before the sub-steps loop and reused for every step, causing 2× displacement
on slow frames (>64ms, simulationSteps>1). Fix: move forceX/forceY Maps and
all force accumulation (layer gravity, edge springs, repulsion, spread) inside
the loop so each sub-step integrates from current node positions.
Bug 2 (graph-adapter.ts): all three adapters used `graph.hasEdge(src,tgt)`
as a dedup guard, which silently drops any second edge between the same node
pair. A CALLS relationship between nodes that also have a CONTAINS edge was
always lost. Fix: switch from `new Graph()` to `new MultiGraph()` (allows
multiple edges per pair) and dedup by `rel.id` instead of by node pair.
Bug 3 (graph-adapter.test.ts): the cross-cutting edge styling test never
executed its CALLS branch because Bug 2 dropped the CALLS edge before the
assertion ran. Fix: assert `sigmaGraph.size === 2` and verify both edges
individually after collecting attrs by relationType.
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-5
* fix(web): address three code-review bugs in graph rendering
- Move radial layout force accumulation inside the sub-step loop so
forces are recomputed from updated node positions each iteration
instead of using stale forces computed before the loop began
- Revert knowledgeGraphToGraphology from MultiGraph back to Graph with
node-pair deduplication to prevent ForceAtlas2 from double-applying
spring forces for node pairs that share multiple relation types
- Add Target to the lucide-icons import in FileTreePanel.tsx so the
Const node type icon resolves without a ReferenceError
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-6
* fix(web): address four more PR review comments
Edge visibility (useSigma.ts): HAS_METHOD / HAS_PROPERTY edges were hidden
when any edge-type filter was active because those types are not in the EdgeType
union. Normalize HAS_METHOD → DEFINES and HAS_PROPERTY → CONTAINS before the
visibleTypes.includes() guard so Kotlin/Java hierarchy edges follow the same
filter logic as their semantic equivalents.
Force-mode edge styles (graph-adapter.ts): HAS_METHOD / HAS_PROPERTY fell back
to the default gray color in the force-graph adapter because EDGE_STYLES had no
entries for them. Added explicit entries using the same hues as DEFINES/CONTAINS
so force mode renders Kotlin/Java hierarchy edges consistently with tree/circles.
Accessibility (GraphCanvas.tsx, locales): the layout-mode switcher (Force /
Tree / Circles) had no ARIA semantics. Added role="tablist" on the container
and role="tab" + aria-selected on each button. Added the viewModes.label i18n
key (used as aria-label on the tablist) to en and zh-CN locale files.
Flaky test (graph-adapter.test.ts): replaced the hard 2 s wall-clock assertion
with a structural check (node count + edge count) that is deterministic across
CI hardware. Timing tests are inherently flaky and provide no correctness signal.
Co-authored-by: Claude <noreply@anthropic.com>
AI-model: claude-sonnet-4-5
---------
Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
|
||
|
|
681a352006
|
fix(worker): analyze native worker aborts (#1833)
* fix(analyze): avoid native aborts on generated worker bundles Retire timed-out parse workers instead of force-terminating native parser state, and skip Monaco generated worker bundles by default while preserving explicit .gitnexusignore negation overrides. Constraint: Node native tree-sitter bindings can abort the process when a timed-out worker is terminated while inside parser state. Rejected: Falling back to sequential parsing for native stalls | it can move the same native crash onto the main thread. Confidence: high Scope-risk: moderate Directive: Keep timeout recovery from force-terminating workers until they return to JS or exit naturally. Tested: npm test; npx tsc --noEmit; npm run build; targeted analyze on /Users/wangxc/Code/keep; gitnexus detect_changes --scope staged Not-tested: Node 22 LTS runtime and non-macOS platforms * fix(worker): bound retired parser worker lifetimes Keep timeout recovery from immediately terminating workers that may still be inside native parser state, while making terminal pool shutdown own retired worker cleanup so long-lived processes do not accumulate retired threads. Constraint: Claude review on PR #1833 required retiredWorkers cleanup in pool.terminate() and tripBreaker() without regressing no-immediate-terminate timeout safety. Rejected: clearing the retiredWorkers set without terminating | would remove JS bookkeeping while leaking the underlying worker thread. Confidence: high Scope-risk: moderate Directive: Preserve the distinction between recoverable timeout retirement and terminal pool shutdown; do not reintroduce immediate terminate in removeWorkerFromSlot(..., 'retire'). Tested: npx vitest run test/unit/worker-pool-timeout-retire.test.ts; npx vitest run test/unit/worker-pool-timeout-retire.test.ts test/unit/worker-pool-resilience.test.ts test/unit/worker-pool-cumulative-timeout.test.ts test/unit/worker-pool-slot-generation.test.ts; npx tsc --noEmit; npm run build; npx prettier --check src/core/ingestion/workers/worker-pool.ts test/unit/worker-pool-timeout-retire.test.ts ../docs/todo/pr-1833-retired-worker-cleanup-plan.md; npx eslint src/core/ingestion/workers/worker-pool.ts test/unit/worker-pool-timeout-retire.test.ts; gitnexus detect_changes --scope staged. Not-tested: npm test full suite did not complete green in this environment; two runs each had one unrelated test/unit/hooks.test.ts parseHookOutput null failure, and each failed hook test passed when rerun in isolation. * ci: retrigger checks Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: wangxc <wangxc_a_bj@si-tech.com.cn> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> Co-authored-by: Test <test@example.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
5e012c373b
|
fix(cli): detect missing LadybugDB native binary at startup with actionable guidance (#835) (#1837)
* fix(cli): detect missing LadybugDB native binary at startup with actionable guidance (#835) Add checkLbugNative() pre-flight that verifies lbugjs.node exists before any command transitively imports @ladybugdb/core. When missing (bun default install, --ignore-scripts), prints repair instructions instead of crashing with ERR_DLOPEN_FAILED. Also enhances `gitnexus doctor` to probe the native binary status. * fix(review): guard eval-server, un-guard status command eval-server transitively loads @ladybugdb/core and needs the native binary check. status only reads filesystem metadata and should remain accessible when the binary is missing. * fix(lint): use console.log instead of console.error in native check gate The project eslint config only allows console.log. * fix(cli): route native-check to stderr and validate binary loadability Fixes two Codex adversarial review findings: 1. Native-check failure message now goes to process.stderr.write instead of console.log, preventing MCP stdout protocol contamination. 2. checkLbugNative now attempts a controlled require() probe after the existence check. Truncated, ABI-mismatched, or wrong-platform binaries produce actionable guidance instead of passing through to crash at process.dlopen. --------- Co-authored-by: Test <test@example.com> |
||
|
|
05d269ec28
|
feat(ruby): migrate Ruby to scope-based resolution (RFC #909 Ring 3) (#1831)
* feat(ruby): migrate Ruby to scope-based resolution (RFC #909 Ring 3) Implement the full scope-resolution pipeline for Ruby following the PR #1639 (Rust migration) standard, targeting registration in MIGRATED_LANGUAGES with 100% scope parity. Scope resolver hooks (languages/ruby/): - query.ts: RUBY_SCOPE_QUERY covering scopes, declarations, imports, type-bindings (constructor inference via .new), and references - captures.ts: emitRubyScopeCaptures orchestrator with import decomposition, receiver-binding synthesis, method reclassification, and arity metadata for both declarations and calls - receiver-binding.ts: self type-binding synthesis for instance methods, singleton methods, and class << self blocks - interpret.ts: interpretRubyImport (wildcard semantics) and interpretRubyTypeBinding (YARD, constructor, alias sources) - import-target.ts: resolveRubyImportTarget adapting the existing suffix resolver for require/require_relative/load - merge-bindings.ts: tier-based shadowing (local > namespace > import) - arity.ts: Ruby arity check with *args/**kwargs/&block support - scope-resolver.ts: rubyScopeResolver with custom buildRubyMro (kind-aware IMPLEMENTS partitioning: prepend > direct > include; extend excluded from instance MRO per legacy semantics) - simple-hooks.ts: bindingScopeFor, importOwningScope, receiverBinding Wiring: - ruby.ts provider gains 7 scope-resolution hooks - Registered in SCOPE_RESOLVERS map and MIGRATED_LANGUAGES - 127 legacy tests wired with createResolverParityIt('ruby') - 27 new scope-specific tests in ruby-scope.test.ts Parity: 89/127 legacy tests pass under registry-primary; 38 are heritage/property/YARD gaps expected in V1. All 127 pass under legacy. Closes #931 * feat(ruby): add emitHeritageEdges hook, YARD parsing, bare calls, property emission Extend the scope-resolution pipeline with a new optional `emitHeritageEdges` hook (ScopeResolver contract + run.ts wiring) that runs between `preEmitInheritanceEdges` and `buildMro`. This lets languages whose heritage declarations are syntactic method calls (Ruby include/extend/prepend) emit IMPLEMENTS edges from the scope-resolver without touching the legacy pipeline. Ruby scope-resolution improvements: - Heritage: intercept include/extend/prepend in captures.ts, encode as special imports, emit IMPLEMENTS edges via emitHeritageEdges hook - Properties: intercept attr_accessor/attr_reader/attr_writer, emit Property nodes + HAS_PROPERTY edges via the same hook - Bare calls: add (body_statement (identifier)) capture to scope query, matching the legacy query pattern for zero-arity method calls - YARD parsing: second-pass comment scanner for @param/@return/@type annotations with findFollowingMethod that handles body_statement nesting - Query fixes: @declaration.trait for modules (was @declaration.module which normalizeNodeLabel didn't recognize), constant constructor bindings (SERVICE = UserService.new), call-return inference Parity: 114/127 legacy tests pass under registry-primary (up from 89). Remaining 13 are advanced type-inference chain resolution (compound receiver, cross-file return-type propagation, for-in element types). * feat(ruby): achieve 100% scope-resolution parity (127/127) Fix all 13 remaining type-inference failures: - Add expandsWildcardTo hook (expandRubyWildcardNames) so finalize can materialize individual bindings from require/require_relative wildcard imports, unblocking cross-file return-type propagation - Add member-call-return type binding synthesis in captures.ts for assignments like `x = obj.method()` — enables compound receiver chaining through member call return types - Add YARD @return support for attr_accessor/attr_reader/attr_writer calls, creating field-type bindings for chain resolution - Add @declaration.property captures alongside __property__ imports so properties register in localDefs → model.fields → write-access - Add constructor-return inference for methods ending with Foo.new() - Add for-loop variable type aliasing in scope query - Rebuild nodeLookup after emitHeritageEdges in run.ts so Property nodes created by the heritage hook are visible to downstream passes - Extend compound-receiver resolver to handle compound member-call rawNames with () and increase max depth from 4 to 8 - Extend receiver-bound-calls Case 3b for compound rawNames All 127 legacy Ruby tests pass under both REGISTRY_PRIMARY_RUBY=0 (legacy) and =1 (registry-primary). Ruby is now fully registered in MIGRATED_LANGUAGES with 100% scope parity. * test(ruby): add pipeline benchmark exercising heritage emission Synthetic Ruby codebases at 100/250/500 files with include + extend + prepend mixins, diamond mixin patterns (shared BaseMixin modules), attr_accessor properties, YARD annotations, and cross-file imports. Strict equality assertions verify exact IMPLEMENTS and HAS_PROPERTY edge counts: 4 IMPLEMENTS per class (include x2, extend, prepend) plus 1 per non-base mixin module, 3 HAS_PROPERTY per class. Dedup in emitRubyMixinEdges prevents double-counting when the worker path (repos >= 15 files) already created Property/IMPLEMENTS edges before scope-resolution runs. Scaling: 0.76x and 1.40x (both linear, well under 3x threshold). * ci: retrigger build * fix(ci): resolve format, registry-primary-flag, and sequential-mixin test failures - Run prettier on all changed files (captures.ts, run.ts, ruby-scope.test.ts, ruby.test.ts, ruby-pipeline-benchmark.test.ts) - Update registry-primary-flag.test.ts: use Swift (not in MIGRATED_LANGUAGES) instead of Ruby for the isolation and env-var mutation tests - Pin ruby-sequential-mixin.test.ts to REGISTRY_PRIMARY_RUBY=0 (legacy mode) since it tests inferImplicitReceiver + selectDispatch hooks that live in the legacy call-processor (gated off under registry-primary) --------- Co-authored-by: Test <test@example.com> |
||
|
|
d5b2edddc4
|
fix(test): use retry cleanup in antigravity e2e to prevent ENOTEMPTY flake (#1838)
* fix(test): use retry cleanup in antigravity e2e to prevent ENOTEMPTY flake Replace bare `fsp.rm` / `fs.rmSync` in antigravity-hook-e2e.test.ts afterAll with `cleanupTempDir` / `cleanupTempDirSync` from test-db.ts which retry with backoff on transient filesystem errors. Also make `shouldSwallowCleanupError` swallow ENOTEMPTY on all platforms (was Windows-only). The CI failure on macOS was ENOTEMPTY on a deeply nested node-gyp cache directory inside the temp HOME — a cleanup-time race that retries usually resolve, but the final attempt must not crash the test suite if the race persists. * fix: restore fsp import needed for mkdtemp/mkdir --------- Co-authored-by: Test <test@example.com> |
||
|
|
4870879b21
|
fix(wiki): add budget-aware grouping to prevent context overflow on large repos (#627) (#1832)
* fix(wiki): add budget-aware grouping to prevent context overflow on large repos (#627) When the grouping prompt exceeds 100k tokens (e.g. Apache TVM with ~2,378 files and ~306k estimated tokens), batch files by top-level directory and issue one LLM call per batch. Partial results are deterministically merged; any batch failure falls back to directory-based grouping. * fix(wiki): address review findings — exact assertions, progress fix, error logging - Replace bounds-only .toBeGreaterThan assertions with exact .toBe values - Add per-batch budget compliance assertion for sub-batch case - Add assertion that partial LLM results don't leak through nuclear fallback - Pass fixedPercent/percentRange to streamOpts in batched LLM calls - Log batch failure in onProgress before falling back to directory grouping - Strengthen mergeGroupings dedup test from .toContain to exact .toEqual * chore(autofix): apply prettier + eslint fixes via /autofix command * fix(wiki): prevent slug collisions and handle single-file oversize in batched grouping mergeGroupings now normalizes module keys by slug so case/punctuation variants ("API Routes" vs "API routes") merge into one module instead of producing colliding .md files. batchFilesForGrouping now truncates per-file symbol lists via binary search when a single file exceeds GROUPING_TOKEN_BUDGET, so every LLM request stays within the context window. * style(wiki): apply prettier formatting to generator.ts --------- Co-authored-by: Test <test@example.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
c916c88361
|
feat(mcp): add limit/offset/summaryOnly pagination to impact tool (#1818)
* feat(mcp): add limit/offset/summaryOnly pagination to impact tool (#414) The impact tool returns unbounded byDepth arrays for hub symbols (base error classes, shared utilities), producing 140KB+ responses that get truncated by MCP clients. maxDepth alone does not help when most dependents are at depth 1. Add three new parameters: - summaryOnly: returns counts/risk/processes/modules without byDepth - limit: caps symbols per depth level (default 100) - offset: skips symbols for pagination Also adds byDepthCounts to all responses so agents can see total counts even when the symbol list is paginated or omitted. Closes #414 * fix(mcp): prevent pagination from silently truncating cross-repo impact Address review findings on #1818: - F1 (blocker): _runImpactBFS no longer defaults to limit 100 when limit is not set — only _impactImpl (MCP entry) applies the default. Internal callers (impactByUid, group impact) get complete results. GroupToolPort.impact interface gains optional limit param, and cross-impact.ts passes limit: 10000 for local UID collection. - F2 (blocker): tool description updated — byDepth is now documented as paginated, not 'all affected symbols'. - F3: impactByUid calls _runImpactBFS without limit, so Phase-2 neighbor results are no longer capped at 100. - F4: pagination metadata now appears when offset > 0 (head truncation), not just tail truncation. Pagination.limit is null when uncapped. - F5: limit/offset schema types changed from number to integer; Math.trunc applied in implementation as defense-in-depth. - F6: 7 new tests — multi-depth pagination, offset-only truncation, offset past end, float inputs, _runImpactBFS internal uncapped path, collectImpactSymbolUids with paginated vs complete data. * fix(mcp): NaN guard on pagination params, complete GroupToolPort interface - Add Number.isFinite guard to limit/offset in _runImpactBFS so NaN inputs fall through to uncapped/zero defaults instead of producing silent empty byDepth with no truncation signal. - Add offset and summaryOnly to GroupToolPort.impact interface to match the implementation and prevent silent param loss at the port boundary. - Replace bounds-only toBeLessThan assertion with exact byDepthCounts and pagination assertions per DoD §2.7. * fix(mcp): address remaining review findings for impact pagination - #3: Forward limit/offset/summaryOnly through callToolAtGroupRepo so group-mode MCP callers can use the new pagination params. - #4: Extract GROUP_LOCAL_PHASE_LIMIT constant from magic 10000 in cross-impact.ts with a comment explaining the intent. - #7: eval-server formatImpactResult uses byDepthCounts[depth] for the 'and N more' suffix instead of paginated slice length. - #8: Extract ImpactParams interface from duplicate inline type definitions in impact() and _impactImpl(). - #9: Add --limit, --offset, --summary-only CLI flags to the impact command with i18n help strings (en + zh-CN). - #10: Clarify in tool description that limit/offset apply per depth level, not per total result set. * chore(autofix): apply prettier + eslint fixes via /autofix command * @ fix(mcp): address Copilot review feedback on impact pagination - Sanitize limit/offset with Number.isFinite in _impactImpl to prevent NaN passthrough from bypassing the default limit of 100 - Omit pagination.limit field instead of emitting null when paginationLimit is Infinity, keeping the response schema consistent - Move GROUP_LOCAL_PHASE_LIMIT after all imports in cross-impact.ts - Stop forwarding limit/offset/summaryOnly to group-mode impact since runGroupImpact overrides limit with GROUP_LOCAL_PHASE_LIMIT for UID collection and does not re-paginate - Validate CLI parseInt results with Number.isFinite before passing to the backend, falling back to undefined so defaults apply - Use byDepthCounts to decide whether to render depth sections in formatImpactResult, handling empty pages from offset past end @ * @ fix(mcp): address code review findings on impact pagination - Fix formatImpactResult "N more" count: use Math.min(items.length, 12) instead of hardcoded 12, so paginated pages with <12 items show the correct remaining count - Detect summaryOnly responses (byDepth absent, byDepthCounts present) and show a summary-mode message instead of misleading "(0 items on this page — adjust offset)" per depth level - Document that limit/offset/summaryOnly are single-repo only and ignored in group mode (@groupName) in MCP tool schema descriptions - List byDepthCounts in summaryOnly description and note byDepth absence when summaryOnly is true - Remove unused limit/offset/summaryOnly from GroupToolPort.impact interface since they are never forwarded to group impact - Deduplicate parseInt calls in CLI tool.ts: extract to local variables with consistent optional-chain usage @ * chore(autofix): apply prettier + eslint fixes via /autofix command * @ fix(group): restore limit in GroupToolPort.impact interface cross-impact.ts passes limit: GROUP_LOCAL_PHASE_LIMIT through the GroupToolPort.impact interface for UID collection. Only offset and summaryOnly were truly unused — limit must stay. @ * @ docs: add limit/offset/summaryOnly to impact tool options in README @ --------- Co-authored-by: Test <test@example.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |
||
|
|
966ddb981e
|
feat(cpp): thread base-specifier qualifier through dependent-base lookup (#1815) (#1819)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
* feat(cpp): thread base-specifier qualifier through dependent-base lookup (#1815) captures.ts: add extractBaseLookupQualifier, fix isBaseDependent for qualified_identifier bases. two-phase-lookup.ts: qualifier storage, markCppDependentBase accepts qualifier, dedup index by nodeId (last-wins), V3 qualifier targeting (dormant). Infrastructure delivered: qualifier extraction, storage, dedup, isBaseDependent fix. V3 targeting dormant until qualifiedName computation fix reaches localDefs. Part of #1564. Infrastructure for #1815. * fix(cpp): three conservatism fixes for dependent-base lookup Fix 1 — Map collision in markCppDependentBase (line 83): Change innermost storage from Map<baseName, qualifier> to Map<baseName, Set<qualifier>> so multiple captures of the same dependent base name with different qualifiers don't collide. Fix 2 — Single-candidate bypass (lines 197-206): For qualified bases with only one candidate, verify namespace match before accepting. Unqualified bases still accept the unique candidate. Previously accepted regardless, creating false edges. Fix 3 — V3→V2 fallthrough (line 221): When a syntactic qualifier is present but no exact match is found, suppress rather than falling through to V2 prefix-heuristic. V2 only runs for truly unqualified bases, which is what it was designed for. All three are conservative bug fixes — turn false positives into suppression, not behavior changes. 250/250 tests pass both modes. --------- Co-authored-by: Gergő Magyar <gergomagyar@icloud.com> |
||
|
|
05151b1079
|
chore(deps)(deps): bump lru-cache from 11.3.6 to 11.4.0 in /gitnexus (#1826) | ||
|
|
5458ddce77
|
chore(deps)(deps-dev): bump @types/node in /gitnexus (#1825) | ||
|
|
3161938730
|
chore(deps)(deps-dev): bump vitest from 4.1.6 to 4.1.7 in /gitnexus (#1824) | ||
|
|
d4449b4ec8
|
fix(lbug): resolve non-ASCII paths for KuzuDB on Windows (#1811) (#1817)
* fix(lbug): resolve non-ASCII paths to 8.3 short form on Windows (#1811) KuzuDB's native C++ layer uses ANSI file APIs (fopen) on Windows. When the repo path contains CJK or other non-ASCII characters, the UTF-8 bytes from Node.js are misinterpreted as the system's Active Code Page (e.g. GBK), producing a garbled path — "Error 3: The system cannot find the path specified." Add `toNativeSafePath()` which converts non-ASCII paths to their Windows 8.3 short-name form (all-ASCII) before passing them to the native layer. Applied to both the database open path and the COPY CSV paths. No-ops on non-Windows and on all-ASCII paths. Closes #1811 * test(lbug): add unit + integration tests for non-ASCII path handling (#1811) - Unit tests for toNativeSafePath: ASCII passthrough, non-Windows no-op, Windows short-path conversion, nonexistent-path fallback - Integration test: full initLbug + loadGraphToLbug round-trip with CJK characters in the storage path — runs on all platforms - Fix toNativeSafePath to reject cmd.exe output containing '?' chars (replacement for unrepresentable Unicode in the console code page) - Register integration test in vitest lbug-db project and cross-platform-tests.ts matrix * chore(autofix): apply prettier + eslint fixes via /autofix command * feat(lbug): junction fallback, tmpdir CSV staging, pool-adapter coverage (#1811) U1+U4: toNativeSafePath now tries 8.3 short path → NTFS junction fallback → diagnostic warning. Junctions target path.dirname(p) and reconstruct the leaf. Handles EEXIST races. Registers cleanup on exit/SIGTERM/SIGINT. Orphan scan on first call removes stale junctions from prior crashes. U2: loadGraphToLbug redirects csvDir to os.tmpdir() when storagePath contains non-ASCII on Windows, avoiding non-ASCII characters in COPY FROM paths entirely. U3: All 4 createLbugDatabase call sites in pool-adapter.ts now wrap dbPath with toNativeSafePath. * fix(test): fix CI failures from toNativeSafePath addition (#1811) - Fix lbug-non-ascii-path integration test: use CodeRelation (actual relationship table name) instead of CALLS - Add toNativeSafePath to lbug-config.js mocks in pool-wal-recovery and lbug-pool-win-fts-probe tests — pool-adapter now imports it * fix(lbug): sanitize path before cmd.exe shell expansion (CodeQL) Reject paths containing cmd.exe metacharacters (" % | & < > ^) before interpolating into the `for %I` short-path command. Prevents command injection via crafted path names. * fix(lbug): address code review findings in non-ASCII path implementation - U1: Use process.exit(0) on Windows instead of process.kill re-raise (SIGTERM forcefully kills on Windows, handlers never fire) - U2: Pass safePath to openWithLockRetry so sidecar sweep targets the path KuzuDB actually opened, not the original non-ASCII path - U3: Skip junction creation in worker threads (isMainThread guard) to prevent junction leaks from pool-adapter workers - U4: Replace existsSync with lstatSync in orphan scan to avoid 30s blocking on unreachable UNC network targets * chore(autofix): apply prettier + eslint fixes via /autofix command * fix(lbug): correct SIGTERM exit code and run Prettier (#1811) - Use exit code 143 (SIGTERM) / 130 (SIGINT) on Windows instead of 0 so termination is not masked as success - Run Prettier to fix formatting (CI Gate blocker) * fix(lbug): eliminate CodeQL command-injection taint in tryShortPath Pass the path via GITNEXUS_SP environment variable instead of interpolating it into the cmd.exe command string. The FOR loop reads %GITNEXUS_SP% from the environment, so the command text is entirely static — no user-controlled data in the shell command. Also removes CMD_UNSAFE_RE since the env var approach makes character-level sanitization unnecessary. --------- Co-authored-by: Test <test@example.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> |