mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-16 04:28:11 +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
|
||||
|
||||
| Boundary | Rule |
|
||||
|----------|------|
|
||||
| **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. |
|
||||
| **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. |
|
||||
| Boundary | Rule |
|
||||
| -------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **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. |
|
||||
| **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. |
|
||||
|
||||
## Model Configuration
|
||||
|
||||
|
|
@ -22,11 +22,12 @@ Last reviewed: 2026-09-24
|
|||
## Execution Sequence (complex tasks)
|
||||
|
||||
For multi-step work, state up front:
|
||||
|
||||
1. Which rules in this file and **[GUARDRAILS.md](GUARDRAILS.md)** apply (and any relevant Signs).
|
||||
2. Current **Scope** boundaries.
|
||||
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
|
||||
|
||||
|
|
@ -89,30 +90,31 @@ mirror. `gitnexus/test/unit/shipped-skills-sync.test.ts` guards the copies. Toke
|
|||
|
||||
## Changelog
|
||||
|
||||
| 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.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-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.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.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-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-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-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-03-24 | 1.2.0 | Fixed gitnexus:start block duplication. |
|
||||
| 2026-03-23 | 1.1.0 | Updated agent instructions, references, Cursor layout. |
|
||||
| 2026-03-22 | 1.0.0 | Initial structured header and changelog. |
|
||||
| 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.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-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.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.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-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-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-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-03-24 | 1.2.0 | Fixed gitnexus:start block duplication. |
|
||||
| 2026-03-23 | 1.1.0 | Updated agent instructions, references, Cursor layout. |
|
||||
| 2026-03-22 | 1.0.0 | Initial structured header and changelog. |
|
||||
|
||||
---
|
||||
|
||||
<!-- gitnexus:start -->
|
||||
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
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
|
||||
|
||||
- **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 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.
|
||||
|
|
@ -139,23 +141,23 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
|||
|
||||
## Resources
|
||||
|
||||
| Resource | Use for |
|
||||
| --- | --- |
|
||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||
| Resource | Use for |
|
||||
| ----------------------------------------- | ---------------------------------------- |
|
||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||
|
||||
## CLI
|
||||
|
||||
| Task | Read this skill file |
|
||||
| --- | --- |
|
||||
| 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` |
|
||||
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
||||
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
||||
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
||||
| Task | Read this skill file |
|
||||
| -------------------------------------------- | -------------------------------------------------- |
|
||||
| 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` |
|
||||
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
||||
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
||||
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
||||
|
||||
<!-- gitnexus:end -->
|
||||
|
||||
|
|
@ -163,14 +165,14 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
|||
|
||||
### Packages
|
||||
|
||||
| Package | Path | Purpose |
|
||||
|---------|------|---------|
|
||||
| **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. |
|
||||
| **Shared** | `gitnexus-shared/` | Shared TypeScript types and constants. |
|
||||
| Claude Plugin | `gitnexus-claude-plugin/` | Static config for Claude marketplace. |
|
||||
| Cursor Integration | `gitnexus-cursor-integration/` | Static config for Cursor editor. |
|
||||
| Eval | `eval/` | Python evaluation harness (Docker + LLM API keys). |
|
||||
| Package | Path | Purpose |
|
||||
| ------------------ | ------------------------------ | ------------------------------------------------------------------ |
|
||||
| **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. |
|
||||
| **Shared** | `gitnexus-shared/` | Shared TypeScript types and constants. |
|
||||
| Claude Plugin | `gitnexus-claude-plugin/` | Static config for Claude marketplace. |
|
||||
| Cursor Integration | `gitnexus-cursor-integration/` | Static config for Cursor editor. |
|
||||
| Eval | `eval/` | Python evaluation harness (Docker + LLM API keys). |
|
||||
|
||||
### Running services
|
||||
|
||||
|
|
@ -183,12 +185,14 @@ npx gitnexus serve # HTTP API on port 4747 (from any ind
|
|||
### Testing
|
||||
|
||||
**CLI / Core (`gitnexus/`)**
|
||||
|
||||
- `npm test` — full vitest suite (~2000 tests)
|
||||
- `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).
|
||||
- `npx tsc --noEmit` — typecheck
|
||||
|
||||
**Web UI (`gitnexus-web/`)**
|
||||
|
||||
- `npm test` — vitest (~200 tests)
|
||||
- `npm run test:e2e` — Playwright (7 spec files; requires `gitnexus serve` + `npm run dev`)
|
||||
- `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
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -41,17 +41,17 @@ If always-on instructions grow, load deep conventions via conditional reads (e.g
|
|||
|
||||
## Changelog
|
||||
|
||||
| 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-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-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-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-23 | 1.1.0 | Updated agent instructions to match AGENTS.md. |
|
||||
| 2026-03-22 | 1.0.0 | Added structured header and changelog. |
|
||||
| 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-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-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-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-23 | 1.1.0 | Updated agent instructions to match AGENTS.md. |
|
||||
| 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.
|
||||
|
||||
<!-- gitnexus:start -->
|
||||
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
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
|
||||
|
||||
- **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 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.
|
||||
|
|
@ -86,22 +87,22 @@ This project is indexed by GitNexus as **GitNexus** (248612 symbols, 565510 rela
|
|||
|
||||
## Resources
|
||||
|
||||
| Resource | Use for |
|
||||
| --- | --- |
|
||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||
| Resource | Use for |
|
||||
| ----------------------------------------- | ---------------------------------------- |
|
||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||
|
||||
## CLI
|
||||
|
||||
| Task | Read this skill file |
|
||||
| --- | --- |
|
||||
| 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` |
|
||||
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
||||
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
||||
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
||||
| Task | Read this skill file |
|
||||
| -------------------------------------------- | -------------------------------------------------- |
|
||||
| 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` |
|
||||
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus-debugging/SKILL.md` |
|
||||
| Rename / extract / split / refactor | `.claude/skills/gitnexus-refactoring/SKILL.md` |
|
||||
| Tools, resources, schema reference | `.claude/skills/gitnexus-guide/SKILL.md` |
|
||||
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus-cli/SKILL.md` |
|
||||
|
||||
<!-- gitnexus:end -->
|
||||
|
|
|
|||
|
|
@ -117,6 +117,15 @@ export function markdownSafeBranch(branch: string): string {
|
|||
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
|
||||
* params, #2188 review — six `undefined`s to reach `hasPdg` was the smell). */
|
||||
export interface GitNexusContentOptions {
|
||||
|
|
@ -129,9 +138,9 @@ export interface GitNexusContentOptions {
|
|||
* the available runner (global `gitnexus` → `pnpm dlx` → `bunx` → `npx`) at
|
||||
* call time. */
|
||||
runnerPath?: string;
|
||||
/** Default branch for the regression-compare example (#243). Configurable so
|
||||
* projects on `develop`/`master`/etc. don't get `base_ref: "main"` rewritten
|
||||
* back over their fix on every analyze. The value is embedded inside a
|
||||
/** Default target branch for the regression-review guidance (#243, #3509).
|
||||
* This is a target hint only: compare scope must receive the exact merge-base
|
||||
* SHA, never the moving branch tip. The value is embedded inside a
|
||||
* Markdown inline-code span: validateBranchName rejects backticks upstream,
|
||||
* and `markdownSafeBranch` strips any remaining backtick here as defense in
|
||||
* 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
|
||||
// it with a bootstrap command it could not run.
|
||||
const bootstrapNote =
|
||||
`No \`${runnerPath}\` yet? Bootstrap with \`npx\`, \`bunx\`, or \`pnpm dlx\` — ` +
|
||||
'e.g. `bunx gitnexus@latest analyze` (npm 11 npx crash; #1939).';
|
||||
`No \`${runnerPath}\`? Bootstrap with \`npx\`, \`bunx\`, or \`pnpm dlx\`; ` +
|
||||
'e.g. `bunx gitnexus@latest analyze` (#1939).';
|
||||
|
||||
// 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
|
||||
|
|
@ -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)`}.
|
||||
|
||||
> 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.
|
||||
|
||||
## 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
|
||||
? ` 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 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.${
|
||||
|
|
@ -726,8 +735,8 @@ export async function generateAIContextFiles(
|
|||
}
|
||||
|
||||
/**
|
||||
* Refresh only the `base_ref: "..."` value inside the GitNexus block of an
|
||||
* already-generated AGENTS.md / CLAUDE.md, in place (#1996 tri-review P2).
|
||||
* Refresh the regression-review target inside the GitNexus block of an
|
||||
* already-generated AGENTS.md / CLAUDE.md, in place (#1996 P2, #3509).
|
||||
*
|
||||
* The `alreadyUpToDate` analyze fast path returns before the normal
|
||||
* {@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 —
|
||||
* 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
|
||||
* `base_ref` line (e.g. a user-trimmed keep block) are silently skipped. Writes
|
||||
* Legacy blocks that passed the branch tip as `base_ref` are migrated to the
|
||||
* 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.
|
||||
*/
|
||||
export async function refreshBaseRefLine(
|
||||
|
|
@ -746,7 +757,6 @@ export async function refreshBaseRefLine(
|
|||
options?: { skipAgentsMd?: boolean },
|
||||
): Promise<{ files: string[] }> {
|
||||
if (options?.skipAgentsMd) return { files: [] };
|
||||
const replacement = `base_ref: ${JSON.stringify(markdownSafeBranch(defaultBranch))}`;
|
||||
const updated: string[] = [];
|
||||
for (const name of ['AGENTS.md', 'CLAUDE.md']) {
|
||||
const filePath = path.join(repoPath, name);
|
||||
|
|
@ -763,9 +773,36 @@ export async function refreshBaseRefLine(
|
|||
if (endIdx === -1 || endIdx <= startIdx) continue;
|
||||
const blockEnd = endIdx + GITNEXUS_END_MARKER.length;
|
||||
const block = content.substring(startIdx, blockEnd);
|
||||
// Only the generated regression example carries a base_ref line, and only
|
||||
// one per block; replace its quoted value while leaving the rest untouched.
|
||||
const newBlock = block.replace(/base_ref: "(?:[^"\\]|\\.)*"/, replacement);
|
||||
// Preserve the runner emitted with the block. Its location follows the
|
||||
// configured storage path, so assuming `.gitnexus/run.cjs` would break the
|
||||
// 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
|
||||
const newContent = content.substring(0, startIdx) + newBlock + content.substring(blockEnd);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1663,7 +1663,7 @@ const analyzeCommandImpl = async (
|
|||
}
|
||||
if (baseRefRefreshed.length > 0) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -191,8 +191,13 @@ describe('generateAIContextFiles', () => {
|
|||
);
|
||||
expect(content).toContain('detect_changes({scope: "all"})');
|
||||
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('--scope compare --base-ref "develop" --repo .');
|
||||
expect(content).toContain('target branch "develop"');
|
||||
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).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 develop = generateGitNexusContent('P', stats, { defaultBranch: 'develop' });
|
||||
expect(develop).toContain('base_ref: "develop"');
|
||||
expect(develop).not.toContain('base_ref: "main"');
|
||||
expect(develop).toContain('target branch "develop" from its remote');
|
||||
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 });
|
||||
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)', () => {
|
||||
|
|
@ -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
|
||||
// raw, so it stays inside the inline code span.
|
||||
const content = generateGitNexusContent('P', { nodes: 1 }, { defaultBranch: 'we"ird' });
|
||||
expect(content).toContain('base_ref: "we\\"ird"');
|
||||
expect(content).toContain('--base-ref "we\\"ird" --repo .');
|
||||
expect(content).toContain('target branch "we\\"ird"');
|
||||
expect(content).toContain('--base-ref "<merge-base SHA>" --repo .');
|
||||
});
|
||||
|
||||
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
|
||||
// backtick would close it early. markdownSafeBranch strips it at the sink.
|
||||
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
|
||||
// and closes exactly one).
|
||||
expect((line.match(/`/g) || []).length % 2).toBe(0);
|
||||
expect(line).not.toContain('main`evil');
|
||||
expect(line).toContain('target branch "mainevil" from its remote');
|
||||
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-'));
|
||||
try {
|
||||
// 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 — 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 |
|
||||
|------|---------------------|
|
||||
|
|
@ -1550,7 +1567,11 @@ Indexed as **placeholder** (1 symbols, 1 relationships, 1 execution flows). Cust
|
|||
|
||||
for (const f of ['AGENTS.md', 'CLAUDE.md']) {
|
||||
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"');
|
||||
// The community-skill row (and everything else) is preserved.
|
||||
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 });
|
||||
expect(skipped.files).toEqual([]);
|
||||
expect(await fs.readFile(path.join(dir, 'AGENTS.md'), 'utf-8')).toContain(
|
||||
'base_ref: "develop"',
|
||||
'target branch "develop"',
|
||||
);
|
||||
} finally {
|
||||
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-'));
|
||||
try {
|
||||
// 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');
|
||||
expect((await refreshBaseRefLine(dir, 'develop')).files).toEqual([]);
|
||||
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 {
|
||||
await fs.rm(dir, { recursive: true, force: true });
|
||||
}
|
||||
|
|
@ -1601,8 +1766,9 @@ Indexed as **P**. Custom.
|
|||
});
|
||||
for (const f of ['CLAUDE.md', 'AGENTS.md']) {
|
||||
const content = await fs.readFile(path.join(subDir, f), 'utf-8');
|
||||
expect(content).toContain('base_ref: "release/1.0"');
|
||||
expect(content).not.toContain('base_ref: "main"');
|
||||
expect(content).toContain('target branch "release/1.0"');
|
||||
expect(content).toContain('base_ref: "<merge-base SHA>"');
|
||||
expect(content).not.toContain('base_ref: "release/1.0"');
|
||||
}
|
||||
} finally {
|
||||
await fs.rm(subDir, { recursive: true, force: true });
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue