mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-17 04:33:45 +00:00
fix: use merge-base in regression review guidance (#3514)
This commit is contained in:
parent
f8f97678d4
commit
1b4e3f0a81
5 changed files with 320 additions and 112 deletions
104
AGENTS.md
104
AGENTS.md
|
|
@ -7,12 +7,12 @@ Last reviewed: 2026-09-24
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
| Boundary | Rule |
|
| Boundary | Rule |
|
||||||
|----------|------|
|
| -------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| **Reads** | `gitnexus/`, `gitnexus-web/`, `eval/`, plugin packages, `.github/`, `.gitnexus/`, docs. |
|
| **Reads** | `gitnexus/`, `gitnexus-web/`, `eval/`, plugin packages, `.github/`, `.gitnexus/`, docs. |
|
||||||
| **Writes** | Only paths required for the change; keep diffs minimal. Update lockfiles when deps change. |
|
| **Writes** | Only paths required for the change; keep diffs minimal. Update lockfiles when deps change. |
|
||||||
| **Executes** | `npm`, `npx`, `node` under `gitnexus/` and `gitnexus-web/`; `uv run` for Python under `eval/`; documented CI/dev workflows. |
|
| **Executes** | `npm`, `npx`, `node` under `gitnexus/` and `gitnexus-web/`; `uv run` for Python under `eval/`; documented CI/dev workflows. |
|
||||||
| **Off-limits** | Real `.env` / secrets, production credentials, unrelated repos, destructive git ops without confirmation. |
|
| **Off-limits** | Real `.env` / secrets, production credentials, unrelated repos, destructive git ops without confirmation. |
|
||||||
|
|
||||||
## Model Configuration
|
## Model Configuration
|
||||||
|
|
||||||
|
|
@ -22,11 +22,12 @@ Last reviewed: 2026-09-24
|
||||||
## Execution Sequence (complex tasks)
|
## Execution Sequence (complex tasks)
|
||||||
|
|
||||||
For multi-step work, state up front:
|
For multi-step work, state up front:
|
||||||
|
|
||||||
1. Which rules in this file and **[GUARDRAILS.md](GUARDRAILS.md)** apply (and any relevant Signs).
|
1. Which rules in this file and **[GUARDRAILS.md](GUARDRAILS.md)** apply (and any relevant Signs).
|
||||||
2. Current **Scope** boundaries.
|
2. Current **Scope** boundaries.
|
||||||
3. Which **validation commands** you will run (`cd gitnexus && npm test`, `npx tsc --noEmit`).
|
3. Which **validation commands** you will run (`cd gitnexus && npm test`, `npx tsc --noEmit`).
|
||||||
|
|
||||||
On long threads, *"Remember: apply all AGENTS.md rules"* re-weights these instructions against context dilution.
|
On long threads, _"Remember: apply all AGENTS.md rules"_ re-weights these instructions against context dilution.
|
||||||
|
|
||||||
## Claude Code hooks
|
## Claude Code hooks
|
||||||
|
|
||||||
|
|
@ -89,30 +90,31 @@ mirror. `gitnexus/test/unit/shipped-skills-sync.test.ts` guards the copies. Toke
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
| Date | Version | Change |
|
| Date | Version | Change |
|
||||||
|------|---------|--------|
|
| ---------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| 2026-09-24 | 1.17.0 | Clones with the same `origin` URL now share a store automatically; `--no-share` records a lasting opt-out (#3352). |
|
| 2026-09-24 | 1.17.0 | Clones with the same `origin` URL now share a store automatically; `--no-share` records a lasting opt-out (#3352). |
|
||||||
| 2026-09-24 | 1.16.0 | Documented the shared worktree index store (`<GITNEXUS_HOME>/stores/`, `analyze --share-with`, `GITNEXUS_SHARED_STORE=off`) in the storage notes (#3352). |
|
| 2026-09-24 | 1.16.0 | Documented the shared worktree index store (`<GITNEXUS_HOME>/stores/`, `analyze --share-with`, `GITNEXUS_SHARED_STORE=off`) in the storage notes (#3352). |
|
||||||
| 2026-09-07 | 1.15.0 | Added the Objective-C provider guide as the required reference before changing Objective-C parsing or resolution. |
|
| 2026-09-07 | 1.15.0 | Added the Objective-C provider guide as the required reference before changing Objective-C parsing or resolution. |
|
||||||
| 2026-07-20 | 1.14.0 | `gitnexus-review` gains a coordinated swarm: six `ci-personas/` lanes the CI review agent dispatches as subagents (via the `Agent` tool), with a bounded critic gate and sidechain-excluded evidence. |
|
| 2026-07-20 | 1.14.0 | `gitnexus-review` gains a coordinated swarm: six `ci-personas/` lanes the CI review agent dispatches as subagents (via the `Agent` tool), with a bounded critic gate and sidechain-excluded evidence. |
|
||||||
| 2026-07-16 | 1.13.0 | `gitnexus-plan` asks plan depth up front (quick/standard/deep) in interactive runs; `gitnexus-lfg` gate slimmed to proceed/stop (Deepen stays as the route-back mechanism). |
|
| 2026-07-16 | 1.13.0 | `gitnexus-plan` asks plan depth up front (quick/standard/deep) in interactive runs; `gitnexus-lfg` gate slimmed to proceed/stop (Deepen stays as the route-back mechanism). |
|
||||||
| 2026-07-16 | 1.12.0 | Renamed `gitnexus-pr-review` to `gitnexus-review`; added PR URL/number, branch/range, and local-change targets plus install migration (setup warns on a legacy `gitnexus-pr-review` dir and leaves it in place; uninstall removes it). |
|
| 2026-07-16 | 1.12.0 | Renamed `gitnexus-pr-review` to `gitnexus-review`; added PR URL/number, branch/range, and local-change targets plus install migration (setup warns on a legacy `gitnexus-pr-review` dir and leaves it in place; uninstall removes it). |
|
||||||
| 2026-07-11 | 1.11.0 | Skill family shipped via npm skills/ + plugin (sync-guarded); added eval/workflow_bench token-savings benchmark. |
|
| 2026-07-11 | 1.11.0 | Skill family shipped via npm skills/ + plugin (sync-guarded); added eval/workflow_bench token-savings benchmark. |
|
||||||
| 2026-07-11 | 1.10.0 | Added `gitnexus-work` (plan executor) and `gitnexus-lfg` (plan → deepen/work gate → review pipeline) skills; section renamed to Engineering planning & execution. |
|
| 2026-07-11 | 1.10.0 | Added `gitnexus-work` (plan executor) and `gitnexus-lfg` (plan → deepen/work gate → review pipeline) skills; section renamed to Engineering planning & execution. |
|
||||||
| 2026-07-11 | 1.9.0 | Added Engineering planning (`/gitnexus-plan`) section; registered the `gitnexus-plan` skill (`.claude/skills/gitnexus-plan/`). |
|
| 2026-07-11 | 1.9.0 | Added Engineering planning (`/gitnexus-plan`) section; registered the `gitnexus-plan` skill (`.claude/skills/gitnexus-plan/`). |
|
||||||
| 2026-05-22 | 1.8.0 | Kotlin added to `MIGRATED_LANGUAGES` (registry-primary call resolution by default). Closes #1756 (companion-vs-instance dispatch) and #1757 (lambda scopes); refs #1746. RFC §6.4 corpus criterion waived (corpus-mode wiring is #927-scope); fixture criterion met. |
|
| 2026-05-22 | 1.8.0 | Kotlin added to `MIGRATED_LANGUAGES` (registry-primary call resolution by default). Closes #1756 (companion-vs-instance dispatch) and #1757 (lambda scopes); refs #1746. RFC §6.4 corpus criterion waived (corpus-mode wiring is #927-scope); fixture criterion met. |
|
||||||
| 2026-04-23 | 1.7.0 | TypeScript added to `MIGRATED_LANGUAGES` (registry-primary call resolution by default). |
|
| 2026-04-23 | 1.7.0 | TypeScript added to `MIGRATED_LANGUAGES` (registry-primary call resolution by default). |
|
||||||
| 2026-04-20 | 1.6.0 | Added scope-resolution pipeline pointer (RFC #909 Ring 3); Python migrated to registry-primary. |
|
| 2026-04-20 | 1.6.0 | Added scope-resolution pipeline pointer (RFC #909 Ring 3); Python migrated to registry-primary. |
|
||||||
| 2026-04-19 | 1.5.0 | Cross-repo impact (#794): `impact`/`query`/`context` accept `repo: "@<group>"` + `service`. Removed `group_query`/`group_contracts`/`group_status` MCP tools; added `gitnexus://group/{name}/contracts` and `gitnexus://group/{name}/status` resources. |
|
| 2026-04-19 | 1.5.0 | Cross-repo impact (#794): `impact`/`query`/`context` accept `repo: "@<group>"` + `service`. Removed `group_query`/`group_contracts`/`group_status` MCP tools; added `gitnexus://group/{name}/contracts` and `gitnexus://group/{name}/status` resources. |
|
||||||
| 2026-04-16 | 1.4.0 | Fixed: web UI description, pre-commit behavior, MCP tools (7->16), added gitnexus-shared, removed stale vite-plugin-wasm gotcha. |
|
| 2026-04-16 | 1.4.0 | Fixed: web UI description, pre-commit behavior, MCP tools (7->16), added gitnexus-shared, removed stale vite-plugin-wasm gotcha. |
|
||||||
| 2026-04-13 | 1.3.0 | Updated GitNexus index stats after DAG refactor. |
|
| 2026-04-13 | 1.3.0 | Updated GitNexus index stats after DAG refactor. |
|
||||||
| 2026-03-24 | 1.2.0 | Fixed gitnexus:start block duplication. |
|
| 2026-03-24 | 1.2.0 | Fixed gitnexus:start block duplication. |
|
||||||
| 2026-03-23 | 1.1.0 | Updated agent instructions, references, Cursor layout. |
|
| 2026-03-23 | 1.1.0 | Updated agent instructions, references, Cursor layout. |
|
||||||
| 2026-03-22 | 1.0.0 | Initial structured header and changelog. |
|
| 2026-03-22 | 1.0.0 | Initial structured header and changelog. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
|
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 relationships, 918 execution flows).
|
This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 relationships, 918 execution flows).
|
||||||
|
|
@ -123,7 +125,7 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
||||||
## Always Do
|
## Always Do
|
||||||
|
|
||||||
- **MUST run impact analysis before editing.** Use `impact({target: "symbolName", direction: "upstream"})` (MCP) or `node .gitnexus/run.cjs impact "symbolName" --direction upstream --repo .` (CLI fallback); report callers, processes, and risk. Never substitute grep for graph analysis. For unified PDG impact, add `mode: "pdg"` with optional `line: <N>` — it returns statement-level `affectedStatements` over CDG + REACHING_DEF and inter-procedural symbols in `interproceduralByDepth`/`byDepth`; no-layer/degraded PDG results are UNKNOWN-risk notes (`--pdg` layer). CLI equivalent: `node .gitnexus/run.cjs impact "symbolName" --direction upstream --mode pdg --line <N> --repo .`.
|
- **MUST run impact analysis before editing.** Use `impact({target: "symbolName", direction: "upstream"})` (MCP) or `node .gitnexus/run.cjs impact "symbolName" --direction upstream --repo .` (CLI fallback); report callers, processes, and risk. Never substitute grep for graph analysis. For unified PDG impact, add `mode: "pdg"` with optional `line: <N>` — it returns statement-level `affectedStatements` over CDG + REACHING_DEF and inter-procedural symbols in `interproceduralByDepth`/`byDepth`; no-layer/degraded PDG results are UNKNOWN-risk notes (`--pdg` layer). CLI equivalent: `node .gitnexus/run.cjs impact "symbolName" --direction upstream --mode pdg --line <N> --repo .`.
|
||||||
- **MUST analyze graph changes before committing.** Use `detect_changes({scope: "all"})` (MCP) or `node .gitnexus/run.cjs detect-changes --scope all --repo .` (CLI fallback). `partial: true` or `truncated: true` is not a clean check — a zero means unseen, not unaffected; re-run it. For regression review: `detect_changes({scope: "compare", base_ref: "main"})` or `node .gitnexus/run.cjs detect-changes --scope compare --base-ref "main" --repo .`.
|
- **MUST analyze changes before commit.** Run `detect_changes({scope: "all"})` or `node .gitnexus/run.cjs detect-changes --scope all --repo .`. `partial: true` or `truncated: true` is not a clean check — a zero means unseen, not unaffected; re-run it. For regression review, fetch target branch "main" from its remote; compute its merge base with the reviewed HEAD and pass that SHA to `detect_changes({scope: "compare", base_ref: "<merge-base SHA>"})` or `node .gitnexus/run.cjs detect-changes --scope compare --base-ref "<merge-base SHA>" --repo .`.
|
||||||
- MUST warn on HIGH/CRITICAL `risk` pre-edit; never use `riskSharedAxes` to waive a HIGH/CRITICAL `risk` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
|
- MUST warn on HIGH/CRITICAL `risk` pre-edit; never use `riskSharedAxes` to waive a HIGH/CRITICAL `risk` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
|
||||||
- **MUST treat `risk: UNKNOWN` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). `impact` pairs `UNKNOWN` with a `riskNote` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
|
- **MUST treat `risk: UNKNOWN` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). `impact` pairs `UNKNOWN` with a `riskNote` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
|
||||||
- **MUST use `query({search_query: "concept"})` for concepts/flows, `context({name: "symbolName"})` for a named symbol, or `impact` for blast radius, on read-only callers, dependencies, imports, or execution flow.** Graph first; text search only for empty/`UNKNOWN`/literals.
|
- **MUST use `query({search_query: "concept"})` for concepts/flows, `context({name: "symbolName"})` for a named symbol, or `impact` for blast radius, on read-only callers, dependencies, imports, or execution flow.** Graph first; text search only for empty/`UNKNOWN`/literals.
|
||||||
|
|
@ -139,23 +141,23 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
| Resource | Use for |
|
| Resource | Use for |
|
||||||
| --- | --- |
|
| ----------------------------------------- | ---------------------------------------- |
|
||||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
| Task | Read this skill file |
|
| Task | Read this skill file |
|
||||||
| --- | --- |
|
| -------------------------------------------- | -------------------------------------------------- |
|
||||||
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus-exploring/SKILL.md` |
|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus-exploring/SKILL.md` |
|
||||||
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus-impact-analysis/SKILL.md` |
|
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus-impact-analysis/SKILL.md` |
|
||||||
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
||||||
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
||||||
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
||||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
||||||
|
|
||||||
<!-- gitnexus:end -->
|
<!-- gitnexus:end -->
|
||||||
|
|
||||||
|
|
@ -163,14 +165,14 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
||||||
|
|
||||||
### Packages
|
### Packages
|
||||||
|
|
||||||
| Package | Path | Purpose |
|
| Package | Path | Purpose |
|
||||||
|---------|------|---------|
|
| ------------------ | ------------------------------ | ------------------------------------------------------------------ |
|
||||||
| **CLI/Core** | `gitnexus/` | TypeScript CLI, indexing pipeline, MCP server. Published to npm. |
|
| **CLI/Core** | `gitnexus/` | TypeScript CLI, indexing pipeline, MCP server. Published to npm. |
|
||||||
| **Web UI** | `gitnexus-web/` | React/Vite thin client. All queries via `gitnexus serve` HTTP API. |
|
| **Web UI** | `gitnexus-web/` | React/Vite thin client. All queries via `gitnexus serve` HTTP API. |
|
||||||
| **Shared** | `gitnexus-shared/` | Shared TypeScript types and constants. |
|
| **Shared** | `gitnexus-shared/` | Shared TypeScript types and constants. |
|
||||||
| Claude Plugin | `gitnexus-claude-plugin/` | Static config for Claude marketplace. |
|
| Claude Plugin | `gitnexus-claude-plugin/` | Static config for Claude marketplace. |
|
||||||
| Cursor Integration | `gitnexus-cursor-integration/` | Static config for Cursor editor. |
|
| Cursor Integration | `gitnexus-cursor-integration/` | Static config for Cursor editor. |
|
||||||
| Eval | `eval/` | Python evaluation harness (Docker + LLM API keys). |
|
| Eval | `eval/` | Python evaluation harness (Docker + LLM API keys). |
|
||||||
|
|
||||||
### Running services
|
### Running services
|
||||||
|
|
||||||
|
|
@ -183,12 +185,14 @@ npx gitnexus serve # HTTP API on port 4747 (from any ind
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
**CLI / Core (`gitnexus/`)**
|
**CLI / Core (`gitnexus/`)**
|
||||||
|
|
||||||
- `npm test` — full vitest suite (~2000 tests)
|
- `npm test` — full vitest suite (~2000 tests)
|
||||||
- `npm run test:unit` — unit tests only
|
- `npm run test:unit` — unit tests only
|
||||||
- `npm run test:integration` — integration (~1850 tests). LadybugDB file-locking tests may fail in containers (known env issue).
|
- `npm run test:integration` — integration (~1850 tests). LadybugDB file-locking tests may fail in containers (known env issue).
|
||||||
- `npx tsc --noEmit` — typecheck
|
- `npx tsc --noEmit` — typecheck
|
||||||
|
|
||||||
**Web UI (`gitnexus-web/`)**
|
**Web UI (`gitnexus-web/`)**
|
||||||
|
|
||||||
- `npm test` — vitest (~200 tests)
|
- `npm test` — vitest (~200 tests)
|
||||||
- `npm run test:e2e` — Playwright (7 spec files; requires `gitnexus serve` + `npm run dev`)
|
- `npm run test:e2e` — Playwright (7 spec files; requires `gitnexus serve` + `npm run dev`)
|
||||||
- `npx tsc -b --noEmit` — typecheck
|
- `npx tsc -b --noEmit` — typecheck
|
||||||
|
|
|
||||||
55
CLAUDE.md
55
CLAUDE.md
|
|
@ -30,7 +30,7 @@ Prefer **PreToolUse** hooks for hard gates (e.g. tests before `git_commit`). Ada
|
||||||
|
|
||||||
## Context budget
|
## Context budget
|
||||||
|
|
||||||
If always-on instructions grow, load deep conventions via conditional reads (e.g. *“When writing new code, read STANDARDS.md”*) instead of pasting long blocks here. In Cursor, prefer `.cursor/index.mdc` plus optional `.cursor/rules/*.mdc` globs (see [AGENTS.md](AGENTS.md) § Context budget).
|
If always-on instructions grow, load deep conventions via conditional reads (e.g. _“When writing new code, read STANDARDS.md”_) instead of pasting long blocks here. In Cursor, prefer `.cursor/index.mdc` plus optional `.cursor/rules/*.mdc` globs (see [AGENTS.md](AGENTS.md) § Context budget).
|
||||||
|
|
||||||
## Reference Documentation
|
## Reference Documentation
|
||||||
|
|
||||||
|
|
@ -41,17 +41,17 @@ If always-on instructions grow, load deep conventions via conditional reads (e.g
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
| Date | Version | Change |
|
| Date | Version | Change |
|
||||||
|------|---------|--------|
|
| ---------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| 2026-07-20 | 1.8.0 | The CI review agent runs `gitnexus-review` as a coordinated swarm — six `ci-personas/` lanes dispatched via the `Agent` tool with a bounded critic gate. |
|
| 2026-07-20 | 1.8.0 | The CI review agent runs `gitnexus-review` as a coordinated swarm — six `ci-personas/` lanes dispatched via the `Agent` tool with a bounded critic gate. |
|
||||||
| 2026-07-16 | 1.7.0 | `/gitnexus-plan` asks depth up front in interactive runs; `/gitnexus-lfg` gate slimmed to proceed/stop. |
|
| 2026-07-16 | 1.7.0 | `/gitnexus-plan` asks depth up front in interactive runs; `/gitnexus-lfg` gate slimmed to proceed/stop. |
|
||||||
| 2026-07-16 | 1.6.0 | Renamed `/gitnexus-pr-review` to `/gitnexus-review` and added PR, branch/range, and local-change targets. |
|
| 2026-07-16 | 1.6.0 | Renamed `/gitnexus-pr-review` to `/gitnexus-review` and added PR, branch/range, and local-change targets. |
|
||||||
| 2026-07-11 | 1.5.0 | Added `/gitnexus-work` and `/gitnexus-lfg` to the engineering plans & execution pointer. |
|
| 2026-07-11 | 1.5.0 | Added `/gitnexus-work` and `/gitnexus-lfg` to the engineering plans & execution pointer. |
|
||||||
| 2026-07-11 | 1.4.0 | Added `/gitnexus-plan` pointer to Reference Documentation. |
|
| 2026-07-11 | 1.4.0 | Added `/gitnexus-plan` pointer to Reference Documentation. |
|
||||||
| 2026-04-13 | 1.3.0 | Updated GitNexus index stats after DAG refactor. |
|
| 2026-04-13 | 1.3.0 | Updated GitNexus index stats after DAG refactor. |
|
||||||
| 2026-03-24 | 1.2.0 | Removed duplicated gitnexus:start block and scope table; replaced with pointers to AGENTS.md. |
|
| 2026-03-24 | 1.2.0 | Removed duplicated gitnexus:start block and scope table; replaced with pointers to AGENTS.md. |
|
||||||
| 2026-03-23 | 1.1.0 | Updated agent instructions to match AGENTS.md. |
|
| 2026-03-23 | 1.1.0 | Updated agent instructions to match AGENTS.md. |
|
||||||
| 2026-03-22 | 1.0.0 | Added structured header and changelog. |
|
| 2026-03-22 | 1.0.0 | Added structured header and changelog. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -60,6 +60,7 @@ If always-on instructions grow, load deep conventions via conditional reads (e.g
|
||||||
See the `<!-- gitnexus:start --> … <!-- gitnexus:end -->` block in **[AGENTS.md](AGENTS.md)** for the canonical MCP tools, impact analysis rules, and index instructions.
|
See the `<!-- gitnexus:start --> … <!-- gitnexus:end -->` block in **[AGENTS.md](AGENTS.md)** for the canonical MCP tools, impact analysis rules, and index instructions.
|
||||||
|
|
||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
|
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 relationships, 918 execution flows).
|
This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 relationships, 918 execution flows).
|
||||||
|
|
@ -70,7 +71,7 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
||||||
## Always Do
|
## Always Do
|
||||||
|
|
||||||
- **MUST run impact analysis before editing.** Use `impact({target: "symbolName", direction: "upstream"})` (MCP) or `node .gitnexus/run.cjs impact "symbolName" --direction upstream --repo .` (CLI fallback); report callers, processes, and risk. Never substitute grep for graph analysis. For unified PDG impact, add `mode: "pdg"` with optional `line: <N>` — it returns statement-level `affectedStatements` over CDG + REACHING_DEF and inter-procedural symbols in `interproceduralByDepth`/`byDepth`; no-layer/degraded PDG results are UNKNOWN-risk notes (`--pdg` layer). CLI equivalent: `node .gitnexus/run.cjs impact "symbolName" --direction upstream --mode pdg --line <N> --repo .`.
|
- **MUST run impact analysis before editing.** Use `impact({target: "symbolName", direction: "upstream"})` (MCP) or `node .gitnexus/run.cjs impact "symbolName" --direction upstream --repo .` (CLI fallback); report callers, processes, and risk. Never substitute grep for graph analysis. For unified PDG impact, add `mode: "pdg"` with optional `line: <N>` — it returns statement-level `affectedStatements` over CDG + REACHING_DEF and inter-procedural symbols in `interproceduralByDepth`/`byDepth`; no-layer/degraded PDG results are UNKNOWN-risk notes (`--pdg` layer). CLI equivalent: `node .gitnexus/run.cjs impact "symbolName" --direction upstream --mode pdg --line <N> --repo .`.
|
||||||
- **MUST analyze graph changes before committing.** Use `detect_changes({scope: "all"})` (MCP) or `node .gitnexus/run.cjs detect-changes --scope all --repo .` (CLI fallback). `partial: true` or `truncated: true` is not a clean check — a zero means unseen, not unaffected; re-run it. For regression review: `detect_changes({scope: "compare", base_ref: "main"})` or `node .gitnexus/run.cjs detect-changes --scope compare --base-ref "main" --repo .`.
|
- **MUST analyze changes before commit.** Run `detect_changes({scope: "all"})` or `node .gitnexus/run.cjs detect-changes --scope all --repo .`. `partial: true` or `truncated: true` is not a clean check — a zero means unseen, not unaffected; re-run it. For regression review, fetch target branch "main" from its remote; compute its merge base with the reviewed HEAD and pass that SHA to `detect_changes({scope: "compare", base_ref: "<merge-base SHA>"})` or `node .gitnexus/run.cjs detect-changes --scope compare --base-ref "<merge-base SHA>" --repo .`.
|
||||||
- MUST warn on HIGH/CRITICAL `risk` pre-edit; never use `riskSharedAxes` to waive a HIGH/CRITICAL `risk` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
|
- MUST warn on HIGH/CRITICAL `risk` pre-edit; never use `riskSharedAxes` to waive a HIGH/CRITICAL `risk` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
|
||||||
- **MUST treat `risk: UNKNOWN` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). `impact` pairs `UNKNOWN` with a `riskNote` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
|
- **MUST treat `risk: UNKNOWN` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). `impact` pairs `UNKNOWN` with a `riskNote` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
|
||||||
- **MUST use `query({search_query: "concept"})` for concepts/flows, `context({name: "symbolName"})` for a named symbol, or `impact` for blast radius, on read-only callers, dependencies, imports, or execution flow.** Graph first; text search only for empty/`UNKNOWN`/literals.
|
- **MUST use `query({search_query: "concept"})` for concepts/flows, `context({name: "symbolName"})` for a named symbol, or `impact` for blast radius, on read-only callers, dependencies, imports, or execution flow.** Graph first; text search only for empty/`UNKNOWN`/literals.
|
||||||
|
|
@ -86,22 +87,22 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
| Resource | Use for |
|
| Resource | Use for |
|
||||||
| --- | --- |
|
| ----------------------------------------- | ---------------------------------------- |
|
||||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
| Task | Read this skill file |
|
| Task | Read this skill file |
|
||||||
| --- | --- |
|
| -------------------------------------------- | -------------------------------------------------- |
|
||||||
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus-exploring/SKILL.md` |
|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus-exploring/SKILL.md` |
|
||||||
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus-impact-analysis/SKILL.md` |
|
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus-impact-analysis/SKILL.md` |
|
||||||
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
||||||
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
||||||
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
||||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
||||||
|
|
||||||
<!-- gitnexus:end -->
|
<!-- gitnexus:end -->
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,15 @@ export function markdownSafeBranch(branch: string): string {
|
||||||
return branch.replace(/`/g, '');
|
return branch.replace(/`/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function regressionReviewGuidance(defaultBranch: string, runner?: string): string {
|
||||||
|
const safeBranch = markdownSafeBranch(defaultBranch);
|
||||||
|
const target = JSON.stringify(safeBranch);
|
||||||
|
const cliFallback = runner
|
||||||
|
? ` or \`${runner} detect-changes --scope compare --base-ref "<merge-base SHA>" --repo .\``
|
||||||
|
: '';
|
||||||
|
return `For regression review, fetch target branch ${target} from its remote; compute its merge base with the reviewed HEAD and pass that SHA to \`detect_changes({scope: "compare", base_ref: "<merge-base SHA>"})\`${cliFallback}.`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Options for {@link generateGitNexusContent} (collapsed from positional
|
/** Options for {@link generateGitNexusContent} (collapsed from positional
|
||||||
* params, #2188 review — six `undefined`s to reach `hasPdg` was the smell). */
|
* params, #2188 review — six `undefined`s to reach `hasPdg` was the smell). */
|
||||||
export interface GitNexusContentOptions {
|
export interface GitNexusContentOptions {
|
||||||
|
|
@ -129,9 +138,9 @@ export interface GitNexusContentOptions {
|
||||||
* the available runner (global `gitnexus` → `pnpm dlx` → `bunx` → `npx`) at
|
* the available runner (global `gitnexus` → `pnpm dlx` → `bunx` → `npx`) at
|
||||||
* call time. */
|
* call time. */
|
||||||
runnerPath?: string;
|
runnerPath?: string;
|
||||||
/** Default branch for the regression-compare example (#243). Configurable so
|
/** Default target branch for the regression-review guidance (#243, #3509).
|
||||||
* projects on `develop`/`master`/etc. don't get `base_ref: "main"` rewritten
|
* This is a target hint only: compare scope must receive the exact merge-base
|
||||||
* back over their fix on every analyze. The value is embedded inside a
|
* SHA, never the moving branch tip. The value is embedded inside a
|
||||||
* Markdown inline-code span: validateBranchName rejects backticks upstream,
|
* Markdown inline-code span: validateBranchName rejects backticks upstream,
|
||||||
* and `markdownSafeBranch` strips any remaining backtick here as defense in
|
* and `markdownSafeBranch` strips any remaining backtick here as defense in
|
||||||
* depth, so JSON.stringify's quote/escape handling is sufficient and the
|
* depth, so JSON.stringify's quote/escape handling is sufficient and the
|
||||||
|
|
@ -203,8 +212,8 @@ ${tableBody}`
|
||||||
// bun-only machine has no npm, npx or pnpm at all, and the npx-only note left
|
// bun-only machine has no npm, npx or pnpm at all, and the npx-only note left
|
||||||
// it with a bootstrap command it could not run.
|
// it with a bootstrap command it could not run.
|
||||||
const bootstrapNote =
|
const bootstrapNote =
|
||||||
`No \`${runnerPath}\` yet? Bootstrap with \`npx\`, \`bunx\`, or \`pnpm dlx\` — ` +
|
`No \`${runnerPath}\`? Bootstrap with \`npx\`, \`bunx\`, or \`pnpm dlx\`; ` +
|
||||||
'e.g. `bunx gitnexus@latest analyze` (npm 11 npx crash; #1939).';
|
'e.g. `bunx gitnexus@latest analyze` (#1939).';
|
||||||
|
|
||||||
// This block is injected into every user's repo and its total size is capped
|
// This block is injected into every user's repo and its total size is capped
|
||||||
// by test (ai-context.test.ts, #856) — a new bullet or clause has to be paid
|
// by test (ai-context.test.ts, #856) — a new bullet or clause has to be paid
|
||||||
|
|
@ -222,17 +231,17 @@ ${tableBody}`
|
||||||
|
|
||||||
This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows)`}.
|
This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${stats.nodes || 0} symbols, ${stats.edges || 0} relationships, ${stats.processes || 0} execution flows)`}.
|
||||||
|
|
||||||
> Index stale? Run \`${runner} analyze --index-only\` from the project root — it auto-selects an available runner. ${bootstrapNote}
|
> Index stale? Run \`${runner} analyze --index-only\`. ${bootstrapNote}
|
||||||
> On query/context/impact/cypher object results, read staleness.status and branch/lastCommit. Re-analyze only for behind or diverged — current is clone HEAD, not main.
|
> On query/context/impact/cypher object results, read staleness.status and branch/lastCommit. Re-analyze only for behind or diverged — current is clone HEAD, not main.
|
||||||
|
|
||||||
## Always Do
|
## Always Do
|
||||||
|
|
||||||
- **MUST run impact before editing.** Use \`impact({target: "symbolName", direction: "upstream"})\` or \`${runner} impact "symbolName" --direction upstream --repo .\`; report callers, processes, and risk. Never substitute grep for graph analysis.${
|
- **MUST run impact before editing.** Use \`impact({target: "symbolName", direction: "upstream"})\` or \`${runner} impact "symbolName" --direction upstream --repo .\`; report callers/processes/risk. Never substitute grep for graph analysis.${
|
||||||
hasPdg
|
hasPdg
|
||||||
? ` For unified PDG impact, add \`mode: "pdg"\` with optional \`line: <N>\` — it returns statement-level \`affectedStatements\` over CDG + REACHING_DEF and inter-procedural symbols in \`interproceduralByDepth\`/\`byDepth\`; no-layer/degraded PDG results are UNKNOWN-risk notes (\`--pdg\` layer). CLI equivalent: \`${runner} impact "symbolName" --direction upstream --mode pdg --line <N> --repo .\`.`
|
? ` For unified PDG impact, add \`mode: "pdg"\` with optional \`line: <N>\` — it returns statement-level \`affectedStatements\` over CDG + REACHING_DEF and inter-procedural symbols in \`interproceduralByDepth\`/\`byDepth\`; no-layer/degraded PDG results are UNKNOWN-risk notes (\`--pdg\` layer). CLI equivalent: \`${runner} impact "symbolName" --direction upstream --mode pdg --line <N> --repo .\`.`
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
- **MUST analyze graph changes before committing.** Use \`detect_changes({scope: "all"})\` (MCP) or \`${runner} detect-changes --scope all --repo .\` (CLI fallback). \`partial: true\` or \`truncated: true\` is not a clean check — a zero means unseen, not unaffected; re-run it. For regression review: \`detect_changes({scope: "compare", base_ref: ${JSON.stringify(markdownSafeBranch(defaultBranch))}})\` or \`${runner} detect-changes --scope compare --base-ref ${JSON.stringify(markdownSafeBranch(defaultBranch))} --repo .\`.
|
- **MUST analyze changes before commit.** Run \`detect_changes({scope: "all"})\` or \`${runner} detect-changes --scope all --repo .\`. \`partial: true\` or \`truncated: true\` is not a clean check — a zero means unseen, not unaffected; re-run it. ${regressionReviewGuidance(defaultBranch, runner)}
|
||||||
- MUST warn on HIGH/CRITICAL \`risk\` pre-edit; never use \`riskSharedAxes\` to waive a HIGH/CRITICAL \`risk\` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
|
- MUST warn on HIGH/CRITICAL \`risk\` pre-edit; never use \`riskSharedAxes\` to waive a HIGH/CRITICAL \`risk\` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
|
||||||
- **MUST treat \`risk: UNKNOWN\` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). \`impact\` pairs \`UNKNOWN\` with a \`riskNote\` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
|
- **MUST treat \`risk: UNKNOWN\` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). \`impact\` pairs \`UNKNOWN\` with a \`riskNote\` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
|
||||||
- **MUST use \`query({search_query: "concept"})\` for concepts/flows, \`context({name: "symbolName"})\` for a named symbol, or \`impact\` for blast radius, on read-only callers, dependencies, imports, or execution flow.** Graph first; text search only for empty/\`UNKNOWN\`/literals.${
|
- **MUST use \`query({search_query: "concept"})\` for concepts/flows, \`context({name: "symbolName"})\` for a named symbol, or \`impact\` for blast radius, on read-only callers, dependencies, imports, or execution flow.** Graph first; text search only for empty/\`UNKNOWN\`/literals.${
|
||||||
|
|
@ -726,8 +735,8 @@ export async function generateAIContextFiles(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh only the `base_ref: "..."` value inside the GitNexus block of an
|
* Refresh the regression-review target inside the GitNexus block of an
|
||||||
* already-generated AGENTS.md / CLAUDE.md, in place (#1996 tri-review P2).
|
* already-generated AGENTS.md / CLAUDE.md, in place (#1996 P2, #3509).
|
||||||
*
|
*
|
||||||
* The `alreadyUpToDate` analyze fast path returns before the normal
|
* The `alreadyUpToDate` analyze fast path returns before the normal
|
||||||
* {@link generateAIContextFiles} call, so a changed `.gitnexusrc` defaultBranch
|
* {@link generateAIContextFiles} call, so a changed `.gitnexusrc` defaultBranch
|
||||||
|
|
@ -736,8 +745,10 @@ export async function generateAIContextFiles(
|
||||||
* block — including community-skill rows written by a prior `--skills` run —
|
* block — including community-skill rows written by a prior `--skills` run —
|
||||||
* rather than regenerating (which would drop those rows on a no-`--skills` run).
|
* rather than regenerating (which would drop those rows on a no-`--skills` run).
|
||||||
*
|
*
|
||||||
* Best-effort: missing files, a missing/blank block, or a block with no
|
* Legacy blocks that passed the branch tip as `base_ref` are migrated to the
|
||||||
* `base_ref` line (e.g. a user-trimmed keep block) are silently skipped. Writes
|
* merge-base guidance at the same time. Best-effort: missing files, a
|
||||||
|
* missing/blank block, or a block with no generated regression sentence are
|
||||||
|
* silently skipped. Writes
|
||||||
* only when the value actually changes, so a routine up-to-date run is a no-op.
|
* only when the value actually changes, so a routine up-to-date run is a no-op.
|
||||||
*/
|
*/
|
||||||
export async function refreshBaseRefLine(
|
export async function refreshBaseRefLine(
|
||||||
|
|
@ -746,7 +757,6 @@ export async function refreshBaseRefLine(
|
||||||
options?: { skipAgentsMd?: boolean },
|
options?: { skipAgentsMd?: boolean },
|
||||||
): Promise<{ files: string[] }> {
|
): Promise<{ files: string[] }> {
|
||||||
if (options?.skipAgentsMd) return { files: [] };
|
if (options?.skipAgentsMd) return { files: [] };
|
||||||
const replacement = `base_ref: ${JSON.stringify(markdownSafeBranch(defaultBranch))}`;
|
|
||||||
const updated: string[] = [];
|
const updated: string[] = [];
|
||||||
for (const name of ['AGENTS.md', 'CLAUDE.md']) {
|
for (const name of ['AGENTS.md', 'CLAUDE.md']) {
|
||||||
const filePath = path.join(repoPath, name);
|
const filePath = path.join(repoPath, name);
|
||||||
|
|
@ -763,9 +773,36 @@ export async function refreshBaseRefLine(
|
||||||
if (endIdx === -1 || endIdx <= startIdx) continue;
|
if (endIdx === -1 || endIdx <= startIdx) continue;
|
||||||
const blockEnd = endIdx + GITNEXUS_END_MARKER.length;
|
const blockEnd = endIdx + GITNEXUS_END_MARKER.length;
|
||||||
const block = content.substring(startIdx, blockEnd);
|
const block = content.substring(startIdx, blockEnd);
|
||||||
// Only the generated regression example carries a base_ref line, and only
|
// Preserve the runner emitted with the block. Its location follows the
|
||||||
// one per block; replace its quoted value while leaving the rest untouched.
|
// configured storage path, so assuming `.gitnexus/run.cjs` would break the
|
||||||
const newBlock = block.replace(/base_ref: "(?:[^"\\]|\\.)*"/, replacement);
|
// CLI fallback when migrating guidance generated for an external store.
|
||||||
|
const legacyRunner = block.match(
|
||||||
|
/or `([^`\n]+?) detect-changes --scope compare --base-ref\b/,
|
||||||
|
)?.[1];
|
||||||
|
const mergeBaseGuidance = regressionReviewGuidance(defaultBranch, legacyRunner);
|
||||||
|
let newBlock = block;
|
||||||
|
if (!block.includes(mergeBaseGuidance) && block.includes('base_ref: "<merge-base SHA>"')) {
|
||||||
|
newBlock = block.replace(
|
||||||
|
/(?:(?:For (?:regression review of )?)?target branch "(?:[^"\\]|\\.)*": `git fetch origin -- [^`\n]+`; pass `git merge-base [^`\n]+ HEAD`'s SHA|Regression base: (?:PR GitHub `baseRefOid`\/`headRefOid`|exact PR base\/head commits from host metadata); target branch "(?:[^"\\]|\\.)*" via tracking remote\. Fetch both; pass merge-base SHA|For regression review, fetch target branch "(?:[^"\\]|\\.)*" from its remote; compute its merge base with the reviewed HEAD and pass that SHA) to `detect_changes\(\{scope: "compare", base_ref: "<merge-base SHA>"\}\)`(?: or `[^`\n]+? detect-changes --scope compare --base-ref "<merge-base SHA>" --repo \.\`)?\./,
|
||||||
|
mergeBaseGuidance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (newBlock === block) {
|
||||||
|
// Migrate the legacy generated sentence.
|
||||||
|
newBlock = block.replace(
|
||||||
|
/For regression review: `detect_changes\(\{scope: "compare", base_ref: "(?:[^"\\]|\\.)*"\}\)`(?: or `[^`\n]+? detect-changes --scope compare --base-ref "(?:[^"\\]|\\.)*" --repo \.\`)?\./,
|
||||||
|
mergeBaseGuidance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (newBlock === block) {
|
||||||
|
// Older compact blocks used this exact generated bullet rather than the
|
||||||
|
// prose sentence. Keep the anchor narrow so user-authored compare calls
|
||||||
|
// elsewhere in the managed block remain untouched.
|
||||||
|
newBlock = block.replace(
|
||||||
|
/- run detect_changes\(\{scope: "compare", base_ref: "(?:[^"\\]|\\.)*"\}\)(?=\s*(?:\n|$))/,
|
||||||
|
`- ${mergeBaseGuidance}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (newBlock === block) continue; // no base_ref line present, or already current
|
if (newBlock === block) continue; // no base_ref line present, or already current
|
||||||
const newContent = content.substring(0, startIdx) + newBlock + content.substring(blockEnd);
|
const newContent = content.substring(0, startIdx) + newBlock + content.substring(blockEnd);
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1663,7 +1663,7 @@ const analyzeCommandImpl = async (
|
||||||
}
|
}
|
||||||
if (baseRefRefreshed.length > 0) {
|
if (baseRefRefreshed.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
` Updated base_ref to "${resolvedDefaultBranch}" in ${baseRefRefreshed.join(', ')}\n`,
|
` Updated regression-review target to "${resolvedDefaultBranch}" in ${baseRefRefreshed.join(', ')}\n`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// #2639: opt-in self-commit of any AGENTS.md/CLAUDE.md churn from this
|
// #2639: opt-in self-commit of any AGENTS.md/CLAUDE.md churn from this
|
||||||
|
|
|
||||||
|
|
@ -191,8 +191,13 @@ describe('generateAIContextFiles', () => {
|
||||||
);
|
);
|
||||||
expect(content).toContain('detect_changes({scope: "all"})');
|
expect(content).toContain('detect_changes({scope: "all"})');
|
||||||
expect(content).toContain('node .gitnexus/run.cjs detect-changes --scope all --repo .');
|
expect(content).toContain('node .gitnexus/run.cjs detect-changes --scope all --repo .');
|
||||||
expect(content).toContain('detect_changes({scope: "compare", base_ref: "develop"})');
|
expect(content).toContain('target branch "develop"');
|
||||||
expect(content).toContain('--scope compare --base-ref "develop" --repo .');
|
expect(content).toContain('target branch "develop" from its remote');
|
||||||
|
expect(content).toContain('compute its merge base with the reviewed HEAD');
|
||||||
|
expect(content).toContain(
|
||||||
|
'detect_changes({scope: "compare", base_ref: "<merge-base SHA>"})',
|
||||||
|
);
|
||||||
|
expect(content).toContain('--scope compare --base-ref "<merge-base SHA>" --repo .');
|
||||||
expect(content).toContain('Never substitute grep for graph analysis');
|
expect(content).toContain('Never substitute grep for graph analysis');
|
||||||
expect(content).not.toContain('gitnexus impact --target');
|
expect(content).not.toContain('gitnexus impact --target');
|
||||||
}
|
}
|
||||||
|
|
@ -1474,19 +1479,30 @@ Indexed as **placeholder** (1 symbols, 1 relationships, 1 execution flows). Cust
|
||||||
});
|
});
|
||||||
|
|
||||||
// ──────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────
|
||||||
// Configurable default branch in the regression example (#243)
|
// Configurable review target and exact merge-base guidance (#243, #3509)
|
||||||
// ──────────────────────────────────────────────────────────────────
|
// ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
it('generated regression-compare example uses the configured default branch (#243)', () => {
|
it('uses the configured branch only as a review target, never as base_ref (#243, #3509)', () => {
|
||||||
const stats = { nodes: 50, edges: 100, processes: 5 };
|
const stats = { nodes: 50, edges: 100, processes: 5 };
|
||||||
const develop = generateGitNexusContent('P', stats, { defaultBranch: 'develop' });
|
const develop = generateGitNexusContent('P', stats, { defaultBranch: 'develop' });
|
||||||
expect(develop).toContain('base_ref: "develop"');
|
expect(develop).toContain('target branch "develop" from its remote');
|
||||||
expect(develop).not.toContain('base_ref: "main"');
|
expect(develop).toContain('compute its merge base with the reviewed HEAD');
|
||||||
|
expect(develop).not.toContain('GitHub');
|
||||||
|
expect(develop).not.toContain('PR base/head');
|
||||||
|
expect(develop).not.toContain('git fetch origin');
|
||||||
|
expect(develop).toContain('base_ref: "<merge-base SHA>"');
|
||||||
|
expect(develop).not.toContain('base_ref: "develop"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults the regression-compare example to "main" when no branch is configured (#243)', () => {
|
it('defaults the review target to main without comparing to the main tip (#243, #3509)', () => {
|
||||||
const content = generateGitNexusContent('P', { nodes: 50, edges: 100, processes: 5 });
|
const content = generateGitNexusContent('P', { nodes: 50, edges: 100, processes: 5 });
|
||||||
expect(content).toContain('base_ref: "main"');
|
expect(content).toContain('target branch "main" from its remote');
|
||||||
|
expect(content).toContain('compute its merge base with the reviewed HEAD');
|
||||||
|
expect(content).not.toContain('GitHub');
|
||||||
|
expect(content).not.toContain('PR base/head');
|
||||||
|
expect(content).not.toContain('git fetch origin');
|
||||||
|
expect(content).toContain('base_ref: "<merge-base SHA>"');
|
||||||
|
expect(content).not.toContain('base_ref: "main"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('references MCP tools by their registered (unprefixed) names (#2059)', () => {
|
it('references MCP tools by their registered (unprefixed) names (#2059)', () => {
|
||||||
|
|
@ -1508,23 +1524,24 @@ Indexed as **placeholder** (1 symbols, 1 relationships, 1 execution flows). Cust
|
||||||
// A branch name with a double-quote must be JSON-escaped, not concatenated
|
// A branch name with a double-quote must be JSON-escaped, not concatenated
|
||||||
// raw, so it stays inside the inline code span.
|
// raw, so it stays inside the inline code span.
|
||||||
const content = generateGitNexusContent('P', { nodes: 1 }, { defaultBranch: 'we"ird' });
|
const content = generateGitNexusContent('P', { nodes: 1 }, { defaultBranch: 'we"ird' });
|
||||||
expect(content).toContain('base_ref: "we\\"ird"');
|
expect(content).toContain('target branch "we\\"ird"');
|
||||||
expect(content).toContain('--base-ref "we\\"ird" --repo .');
|
expect(content).toContain('--base-ref "<merge-base SHA>" --repo .');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('a backtick branch cannot break the generated Markdown code span (#1996 P1)', () => {
|
it('a backtick branch cannot break the generated Markdown code span (#1996 P1)', () => {
|
||||||
// The branch is embedded inside a backtick inline-code span; a stray
|
// The branch is embedded inside a backtick inline-code span; a stray
|
||||||
// backtick would close it early. markdownSafeBranch strips it at the sink.
|
// backtick would close it early. markdownSafeBranch strips it at the sink.
|
||||||
const content = generateGitNexusContent('P', { nodes: 1 }, { defaultBranch: 'main`evil' });
|
const content = generateGitNexusContent('P', { nodes: 1 }, { defaultBranch: 'main`evil' });
|
||||||
const line = content.split('\n').find((l) => l.includes('base_ref'))!;
|
const line = content.split('\n').find((l) => l.includes('target branch'))!;
|
||||||
// Even backtick count ⇒ every span is balanced (the regression line opens
|
// Even backtick count ⇒ every span is balanced (the regression line opens
|
||||||
// and closes exactly one).
|
// and closes exactly one).
|
||||||
expect((line.match(/`/g) || []).length % 2).toBe(0);
|
expect((line.match(/`/g) || []).length % 2).toBe(0);
|
||||||
expect(line).not.toContain('main`evil');
|
expect(line).not.toContain('main`evil');
|
||||||
|
expect(line).toContain('target branch "mainevil" from its remote');
|
||||||
expect(markdownSafeBranch('a`b`c')).toBe('abc');
|
expect(markdownSafeBranch('a`b`c')).toBe('abc');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('refreshBaseRefLine updates base_ref in place, preserving the rest of the block (#1996 P2)', async () => {
|
it('refreshBaseRefLine migrates legacy guidance and preserves the rest of the block (#1996 P2, #3509)', async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-baseref-'));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-baseref-'));
|
||||||
try {
|
try {
|
||||||
// Seed a realistic block: a configured base_ref "main" plus a community
|
// Seed a realistic block: a configured base_ref "main" plus a community
|
||||||
|
|
@ -1534,7 +1551,7 @@ Indexed as **placeholder** (1 symbols, 1 relationships, 1 execution flows). Cust
|
||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
- run \`detect_changes({scope: "compare", base_ref: "main"})\`.
|
For regression review: \`detect_changes({scope: "compare", base_ref: "main"})\` or \`.gitnexus/run.cjs detect-changes --scope compare --base-ref "main" --repo .\`.
|
||||||
|
|
||||||
| Task | Read this skill file |
|
| Task | Read this skill file |
|
||||||
|------|---------------------|
|
|------|---------------------|
|
||||||
|
|
@ -1550,7 +1567,11 @@ Indexed as **placeholder** (1 symbols, 1 relationships, 1 execution flows). Cust
|
||||||
|
|
||||||
for (const f of ['AGENTS.md', 'CLAUDE.md']) {
|
for (const f of ['AGENTS.md', 'CLAUDE.md']) {
|
||||||
const after = await fs.readFile(path.join(dir, f), 'utf-8');
|
const after = await fs.readFile(path.join(dir, f), 'utf-8');
|
||||||
expect(after).toContain('base_ref: "develop"');
|
expect(after).toContain('target branch "develop"');
|
||||||
|
expect(after).toContain('compute its merge base with the reviewed HEAD');
|
||||||
|
expect(after).toContain('target branch "develop" from its remote');
|
||||||
|
expect(after).toContain('base_ref: "<merge-base SHA>"');
|
||||||
|
expect(after).not.toContain('base_ref: "develop"');
|
||||||
expect(after).not.toContain('base_ref: "main"');
|
expect(after).not.toContain('base_ref: "main"');
|
||||||
// The community-skill row (and everything else) is preserved.
|
// The community-skill row (and everything else) is preserved.
|
||||||
expect(after).toContain('.claude/skills/gitnexus-area-auth/SKILL.md');
|
expect(after).toContain('.claude/skills/gitnexus-area-auth/SKILL.md');
|
||||||
|
|
@ -1564,14 +1585,14 @@ Indexed as **placeholder** (1 symbols, 1 relationships, 1 execution flows). Cust
|
||||||
const skipped = await refreshBaseRefLine(dir, 'master', { skipAgentsMd: true });
|
const skipped = await refreshBaseRefLine(dir, 'master', { skipAgentsMd: true });
|
||||||
expect(skipped.files).toEqual([]);
|
expect(skipped.files).toEqual([]);
|
||||||
expect(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8')).toContain(
|
expect(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8')).toContain(
|
||||||
'base_ref: "develop"',
|
'target branch "develop"',
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
await fs.rm(dir, { recursive: true, force: true });
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('refreshBaseRefLine is a no-op when there is no base_ref line or no file (#1996 P2)', async () => {
|
it('refreshBaseRefLine is a no-op when there is no generated review line or no file (#1996 P2)', async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-baseref-noop-'));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-baseref-noop-'));
|
||||||
try {
|
try {
|
||||||
// No AGENTS.md/CLAUDE.md at all → no files updated, no throw.
|
// No AGENTS.md/CLAUDE.md at all → no files updated, no throw.
|
||||||
|
|
@ -1585,6 +1606,150 @@ Indexed as **P**. Custom.
|
||||||
await fs.writeFile(path.join(dir, 'CLAUDE.md'), seed, 'utf-8');
|
await fs.writeFile(path.join(dir, 'CLAUDE.md'), seed, 'utf-8');
|
||||||
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual([]);
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual([]);
|
||||||
expect(await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8')).toBe(seed);
|
expect(await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8')).toBe(seed);
|
||||||
|
|
||||||
|
const custom = `<!-- gitnexus:start -->
|
||||||
|
Custom target branch "main" and base_ref: "main" notes.
|
||||||
|
<!-- gitnexus:end -->
|
||||||
|
`;
|
||||||
|
await fs.writeFile(path.join(dir, 'AGENTS.md'), custom, 'utf-8');
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual([]);
|
||||||
|
expect(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8')).toBe(custom);
|
||||||
|
|
||||||
|
// A user-authored compare instruction is not generated migration state,
|
||||||
|
// even though it contains the same tool call and base_ref shape.
|
||||||
|
const compareInstruction = `<!-- gitnexus:start -->
|
||||||
|
Before release, run \`detect_changes({scope: "compare", base_ref: "main"})\` and inspect every returned flow.
|
||||||
|
<!-- gitnexus:end -->
|
||||||
|
`;
|
||||||
|
await fs.writeFile(path.join(dir, 'CLAUDE.md'), compareInstruction, 'utf-8');
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual([]);
|
||||||
|
expect(await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8')).toBe(compareInstruction);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refreshBaseRefLine updates current merge-base guidance idempotently (#3509)', async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-review-target-'));
|
||||||
|
try {
|
||||||
|
const seed = generateGitNexusContent('P', { nodes: 1 }, { defaultBranch: 'main' });
|
||||||
|
await fs.writeFile(path.join(dir, 'CLAUDE.md'), seed, 'utf-8');
|
||||||
|
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual(['CLAUDE.md']);
|
||||||
|
const after = await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8');
|
||||||
|
expect(after).toContain('target branch "develop"');
|
||||||
|
expect(after).toContain('base_ref: "<merge-base SHA>"');
|
||||||
|
expect(after).not.toContain('base_ref: "develop"');
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual([]);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves a custom runner path while migrating legacy guidance (#3509)', async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-review-runner-'));
|
||||||
|
try {
|
||||||
|
const seed = `<!-- gitnexus:start -->
|
||||||
|
For regression review: \`detect_changes({scope: "compare", base_ref: "main"})\` or \`.external-index/run.cjs detect-changes --scope compare --base-ref "main" --repo .\`.
|
||||||
|
<!-- gitnexus:end -->
|
||||||
|
`;
|
||||||
|
await fs.writeFile(path.join(dir, 'CLAUDE.md'), seed, 'utf-8');
|
||||||
|
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual(['CLAUDE.md']);
|
||||||
|
const after = await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8');
|
||||||
|
expect(after).toContain('`.external-index/run.cjs detect-changes');
|
||||||
|
expect(after).not.toContain('`.gitnexus/run.cjs detect-changes');
|
||||||
|
expect(after).toContain('base_ref: "<merge-base SHA>"');
|
||||||
|
} finally {
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('preserves same-line custom text after legacy guidance (#3509)', async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-review-suffix-'));
|
||||||
|
try {
|
||||||
|
const seed = `<!-- gitnexus:start -->
|
||||||
|
For regression review: \`detect_changes({scope: "compare", base_ref: "main"})\`. Always inspect src/security.ts before release.
|
||||||
|
<!-- gitnexus:end -->
|
||||||
|
`;
|
||||||
|
await fs.writeFile(path.join(dir, 'AGENTS.md'), seed, 'utf-8');
|
||||||
|
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual(['AGENTS.md']);
|
||||||
|
const after = await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8');
|
||||||
|
expect(after).toContain('target branch "develop"');
|
||||||
|
expect(after).toContain('Always inspect src/security.ts before release.');
|
||||||
|
} finally {
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(['PR GitHub `baseRefOid`/`headRefOid`', 'exact PR base/head commits from host metadata'])(
|
||||||
|
'migrates provider-specific guidance (%s) without losing custom text',
|
||||||
|
async (source) => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-review-host-neutral-'));
|
||||||
|
try {
|
||||||
|
const seed =
|
||||||
|
'<!-- gitnexus:start -->\n' +
|
||||||
|
`Regression base: ${source}; target branch "main" via tracking remote. Fetch both; pass merge-base SHA to ` +
|
||||||
|
'`detect_changes({scope: "compare", base_ref: "<merge-base SHA>"})`. Keep this instruction.\n' +
|
||||||
|
'<!-- gitnexus:end -->\n';
|
||||||
|
await fs.writeFile(path.join(dir, 'AGENTS.md'), seed, 'utf-8');
|
||||||
|
expect((await refreshBaseRefLine(dir, 'main')).files).toEqual(['AGENTS.md']);
|
||||||
|
const after = await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8');
|
||||||
|
expect(after).toContain('fetch target branch "main" from its remote');
|
||||||
|
expect(after).toContain('compute its merge base with the reviewed HEAD');
|
||||||
|
expect(after).not.toContain(source);
|
||||||
|
expect(after).toContain('Keep this instruction.');
|
||||||
|
expect((await refreshBaseRefLine(dir, 'main')).files).toEqual([]);
|
||||||
|
} finally {
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it('preserves same-line custom text when refreshing current guidance (#3509)', async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-review-current-suffix-'));
|
||||||
|
try {
|
||||||
|
const current = generateGitNexusContent('P', { nodes: 1, edges: 1, processes: 0 });
|
||||||
|
const seed = current.replace(
|
||||||
|
/(For regression review,[^\n]*\.)/,
|
||||||
|
'$1 Always inspect src/security.ts before release.',
|
||||||
|
);
|
||||||
|
await fs.writeFile(path.join(dir, 'AGENTS.md'), seed, 'utf-8');
|
||||||
|
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual(['AGENTS.md']);
|
||||||
|
const after = await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8');
|
||||||
|
expect(after).toContain('target branch "develop"');
|
||||||
|
expect(after).toContain('compute its merge base with the reviewed HEAD');
|
||||||
|
expect(after).toContain('Always inspect src/security.ts before release.');
|
||||||
|
} finally {
|
||||||
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps MCP-only legacy guidance MCP-only and migrates the compact shape (#3509)', async () => {
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'gn-review-mcp-only-'));
|
||||||
|
try {
|
||||||
|
const prose = `<!-- gitnexus:start -->
|
||||||
|
For regression review: \`detect_changes({scope: "compare", base_ref: "main"})\`.
|
||||||
|
<!-- gitnexus:end -->
|
||||||
|
`;
|
||||||
|
await fs.writeFile(path.join(dir, 'AGENTS.md'), prose, 'utf-8');
|
||||||
|
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual(['AGENTS.md']);
|
||||||
|
const afterProse = await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8');
|
||||||
|
expect(afterProse).toContain('target branch "develop"');
|
||||||
|
expect(afterProse).not.toContain('.gitnexus/run.cjs');
|
||||||
|
await fs.rm(path.join(dir, 'AGENTS.md'));
|
||||||
|
|
||||||
|
const compact = `<!-- gitnexus:start -->
|
||||||
|
- run detect_changes({scope: "compare", base_ref: "main"})
|
||||||
|
<!-- gitnexus:end -->
|
||||||
|
`;
|
||||||
|
await fs.writeFile(path.join(dir, 'CLAUDE.md'), compact, 'utf-8');
|
||||||
|
expect((await refreshBaseRefLine(dir, 'release')).files).toEqual(['CLAUDE.md']);
|
||||||
|
const afterCompact = await fs.readFile(path.join(dir, 'CLAUDE.md'), 'utf-8');
|
||||||
|
expect(afterCompact).toContain('target branch "release"');
|
||||||
|
expect(afterCompact).toContain('merge-base SHA');
|
||||||
} finally {
|
} finally {
|
||||||
await fs.rm(dir, { recursive: true, force: true });
|
await fs.rm(dir, { recursive: true, force: true });
|
||||||
}
|
}
|
||||||
|
|
@ -1601,8 +1766,9 @@ Indexed as **P**. Custom.
|
||||||
});
|
});
|
||||||
for (const f of ['CLAUDE.md', 'AGENTS.md']) {
|
for (const f of ['CLAUDE.md', 'AGENTS.md']) {
|
||||||
const content = await fs.readFile(path.join(subDir, f), 'utf-8');
|
const content = await fs.readFile(path.join(subDir, f), 'utf-8');
|
||||||
expect(content).toContain('base_ref: "release/1.0"');
|
expect(content).toContain('target branch "release/1.0"');
|
||||||
expect(content).not.toContain('base_ref: "main"');
|
expect(content).toContain('base_ref: "<merge-base SHA>"');
|
||||||
|
expect(content).not.toContain('base_ref: "release/1.0"');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await fs.rm(subDir, { recursive: true, force: true });
|
await fs.rm(subDir, { recursive: true, force: true });
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue