@veewo/gitnexus 1.4.7-rc → 1.4.8-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -14
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +6 -3
- package/dist/cli/analyze.js +16 -2
- package/dist/cli/index.js +2 -0
- package/dist/cli/setup.d.ts +2 -0
- package/dist/cli/setup.js +73 -62
- package/dist/cli/setup.test.js +44 -0
- package/dist/cli/status.js +8 -1
- package/dist/config/cli-spec.d.ts +29 -0
- package/dist/config/cli-spec.js +87 -0
- package/dist/mcp/resources.js +11 -2
- package/dist/storage/repo-manager.d.ts +6 -0
- package/dist/storage/repo-manager.js +13 -0
- package/hooks/claude/gitnexus-hook.cjs +50 -7
- package/hooks/claude/pre-tool-use.sh +26 -1
- package/package.json +1 -1
- package/skills/gitnexus-cli.md +33 -8
- package/skills/gitnexus-debugging.md +1 -1
- package/skills/gitnexus-exploring.md +1 -1
- package/skills/gitnexus-guide.md +1 -1
- package/skills/gitnexus-impact-analysis.md +1 -1
- package/skills/gitnexus-pr-review.md +1 -1
- package/skills/gitnexus-refactoring.md +1 -1
package/README.md
CHANGED
|
@@ -18,13 +18,16 @@ AI coding tools don't understand your codebase structure. They edit a function w
|
|
|
18
18
|
## Quick Start
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
npm install -g @veewo/gitnexus
|
|
22
|
+
gitnexus setup --cli-spec @veewo/gitnexus
|
|
23
|
+
gitnexus analyze
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
That's it. This indexes the codebase, installs agent skills, registers Claude Code hooks, and creates `AGENTS.md` / `CLAUDE.md` context files — all in one command.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
After `setup`, repository workflows resolve any npx package fallback from `~/.gitnexus/config.json` instead of defaulting to `@latest`.
|
|
29
|
+
|
|
30
|
+
To configure MCP for your editor, run `gitnexus setup --cli-spec <packageSpec>` once — or set it up manually below.
|
|
28
31
|
|
|
29
32
|
`gitnexus setup` auto-detects your editors and writes the correct global MCP config. You only need to run it once.
|
|
30
33
|
|
|
@@ -52,7 +55,7 @@ If you prefer to configure manually instead of using `gitnexus setup`:
|
|
|
52
55
|
### Claude Code (full support — MCP + skills + hooks)
|
|
53
56
|
|
|
54
57
|
```bash
|
|
55
|
-
claude mcp add gitnexus --
|
|
58
|
+
claude mcp add gitnexus -- gitnexus mcp
|
|
56
59
|
```
|
|
57
60
|
|
|
58
61
|
### Cursor / Windsurf
|
|
@@ -63,8 +66,8 @@ Add to `~/.cursor/mcp.json` (global — works for all projects):
|
|
|
63
66
|
{
|
|
64
67
|
"mcpServers": {
|
|
65
68
|
"gitnexus": {
|
|
66
|
-
"command": "
|
|
67
|
-
"args": ["
|
|
69
|
+
"command": "gitnexus",
|
|
70
|
+
"args": ["mcp"]
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
}
|
|
@@ -78,8 +81,8 @@ Add to `~/.config/opencode/config.json`:
|
|
|
78
81
|
{
|
|
79
82
|
"mcp": {
|
|
80
83
|
"gitnexus": {
|
|
81
|
-
"command": "
|
|
82
|
-
"args": ["
|
|
84
|
+
"command": "gitnexus",
|
|
85
|
+
"args": ["mcp"]
|
|
83
86
|
}
|
|
84
87
|
}
|
|
85
88
|
}
|
|
@@ -137,10 +140,10 @@ Your AI agent gets these tools automatically:
|
|
|
137
140
|
|
|
138
141
|
```bash
|
|
139
142
|
gitnexus setup # Configure MCP for your editors (one-time)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
gitnexus analyze [path] # Index a repository (or update stale index)
|
|
144
|
+
gitnexus analyze --force # Force full re-index
|
|
145
|
+
gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
|
|
146
|
+
gitnexus analyze --verbose # Log skipped files when parsers are unavailable
|
|
144
147
|
gitnexus mcp # Start MCP server (stdio) — serves all indexed repos
|
|
145
148
|
gitnexus serve # Start local HTTP server (multi-repo) for web UI
|
|
146
149
|
gitnexus list # List all indexed repositories
|
|
@@ -153,7 +156,7 @@ gitnexus wiki --model <model> # Wiki with custom LLM model (default: gpt-4o-m
|
|
|
153
156
|
|
|
154
157
|
## Multi-Repo Support
|
|
155
158
|
|
|
156
|
-
GitNexus supports indexing multiple repositories. Each `
|
|
159
|
+
GitNexus supports indexing multiple repositories. Each `gitnexus analyze` registers the repo in a global registry (`~/.gitnexus/registry.json`). The MCP server serves all indexed repos automatically.
|
|
157
160
|
|
|
158
161
|
## Supported Languages
|
|
159
162
|
|
|
@@ -188,7 +191,7 @@ GitNexus ships with skill files that teach AI agents how to use the tools effect
|
|
|
188
191
|
- **Impact Analysis** — Analyze blast radius before changes
|
|
189
192
|
- **Refactoring** — Plan safe refactors using dependency mapping
|
|
190
193
|
|
|
191
|
-
Installed automatically by both `
|
|
194
|
+
Installed automatically by both `gitnexus analyze` (per-repo) and `gitnexus setup` (global).
|
|
192
195
|
|
|
193
196
|
## Requirements
|
|
194
197
|
|
package/dist/cli/ai-context.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ interface RepoStats {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats, options?: {
|
|
22
22
|
skillScope?: SkillScope;
|
|
23
|
+
cliPackageSpec?: string;
|
|
23
24
|
}, generatedSkills?: GeneratedSkillInfo[]): Promise<{
|
|
24
25
|
files: string[];
|
|
25
26
|
}>;
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
|
+
import { buildNpxCommand, resolveCliSpec } from '../config/cli-spec.js';
|
|
11
12
|
// ESM equivalent of __dirname
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = path.dirname(__filename);
|
|
@@ -24,13 +25,14 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
|
|
|
24
25
|
* - Exact tool commands with parameters — vague directives get ignored
|
|
25
26
|
* - Self-review checklist — forces model to verify its own work
|
|
26
27
|
*/
|
|
27
|
-
function generateGitNexusContent(projectName, stats, skillScope, generatedSkills) {
|
|
28
|
+
function generateGitNexusContent(projectName, stats, skillScope, cliPackageSpec, generatedSkills) {
|
|
28
29
|
const skillRoot = skillScope === 'global'
|
|
29
30
|
? '~/.agents/skills/gitnexus'
|
|
30
31
|
: '.agents/skills/gitnexus';
|
|
31
32
|
const generatedRows = (generatedSkills && generatedSkills.length > 0)
|
|
32
33
|
? `\n${generatedSkills.map((s) => `| Work in the ${s.label} area (${s.symbolCount} symbols) | \`.claude/skills/generated/${s.name}/SKILL.md\` |`).join('\n')}`
|
|
33
34
|
: '';
|
|
35
|
+
const reindexCmd = buildNpxCommand(cliPackageSpec, 'analyze');
|
|
34
36
|
return `${GITNEXUS_START_MARKER}
|
|
35
37
|
# GitNexus MCP
|
|
36
38
|
|
|
@@ -42,7 +44,7 @@ This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} s
|
|
|
42
44
|
2. **Match your task to a skill below** and **read that skill file**
|
|
43
45
|
3. **Follow the skill's workflow and checklist**
|
|
44
46
|
|
|
45
|
-
> If step 1 warns the index is stale, ask user whether to rebuild index via \`npx
|
|
47
|
+
> If step 1 warns the index is stale, ask user whether to rebuild index via \`gitnexus analyze\` when local CLI exists; otherwise resolve the pinned npx package spec from \`~/.gitnexus/config.json\` (\`cliPackageSpec\` first, then \`cliVersion\`) and run \`${reindexCmd}\` with that exact package spec (it reuses previous analyze scope/options by default; add \`--no-reuse-options\` to reset). If user declines, explicitly warn that retrieval may not reflect current codebase. For build/analyze/test commands, use a 10-30 minute timeout; on failure/timeout, report exact tool output and do not auto-retry or silently fall back to glob/grep.
|
|
46
48
|
|
|
47
49
|
## Skills
|
|
48
50
|
|
|
@@ -173,7 +175,8 @@ Use GitNexus tools to accomplish this task.
|
|
|
173
175
|
*/
|
|
174
176
|
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats, options, generatedSkills) {
|
|
175
177
|
const skillScope = options?.skillScope === 'global' ? 'global' : 'project';
|
|
176
|
-
const
|
|
178
|
+
const cliPackageSpec = options?.cliPackageSpec || resolveCliSpec().packageSpec;
|
|
179
|
+
const content = generateGitNexusContent(projectName, stats, skillScope, cliPackageSpec, generatedSkills);
|
|
177
180
|
const createdFiles = [];
|
|
178
181
|
// Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Codex, Cline, etc.)
|
|
179
182
|
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
package/dist/cli/analyze.js
CHANGED
|
@@ -22,6 +22,7 @@ import { resolveEffectiveAnalyzeOptions } from './analyze-options.js';
|
|
|
22
22
|
import { formatFallbackSummary, formatUnityDiagnosticsSummary } from './analyze-summary.js';
|
|
23
23
|
import { resolveChildProcessExit } from './exit-code.js';
|
|
24
24
|
import { toPipelineRuntimeSummary } from './analyze-runtime-summary.js';
|
|
25
|
+
import { resolveCliSpec } from '../config/cli-spec.js';
|
|
25
26
|
const HEAP_MB = 8192;
|
|
26
27
|
const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
|
|
27
28
|
/** Re-exec the process with an 8GB heap if we're currently below that. */
|
|
@@ -98,6 +99,14 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
98
99
|
}
|
|
99
100
|
const currentCommit = getCurrentCommit(repoPath);
|
|
100
101
|
const existingMeta = await loadMeta(storagePath);
|
|
102
|
+
let hasLbugIndex = false;
|
|
103
|
+
try {
|
|
104
|
+
await fs.stat(lbugPath);
|
|
105
|
+
hasLbugIndex = true;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
hasLbugIndex = false;
|
|
109
|
+
}
|
|
101
110
|
let includeExtensions = [];
|
|
102
111
|
let scopeRules = [];
|
|
103
112
|
let repoAlias;
|
|
@@ -121,7 +130,10 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
121
130
|
process.exitCode = 1;
|
|
122
131
|
return;
|
|
123
132
|
}
|
|
124
|
-
if (existingMeta && !
|
|
133
|
+
if (existingMeta && !hasLbugIndex && !options?.force) {
|
|
134
|
+
console.log(' Existing metadata found, but LadybugDB index file is missing — rebuilding index...\n');
|
|
135
|
+
}
|
|
136
|
+
if (existingMeta && hasLbugIndex && !options?.force && existingMeta.lastCommit === currentCommit && !options?.skills) {
|
|
125
137
|
const hasScopePrefixInput = Array.isArray(options?.scopePrefix)
|
|
126
138
|
? options.scopePrefix.length > 0
|
|
127
139
|
: Boolean(options?.scopePrefix);
|
|
@@ -368,6 +380,7 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
368
380
|
const skillResult = await generateSkillFiles(repoPath, projectName, pipelineForSkills);
|
|
369
381
|
generatedSkills = skillResult.skills;
|
|
370
382
|
}
|
|
383
|
+
const cliConfig = await loadCLIConfig();
|
|
371
384
|
const aiContext = await generateAIContextFiles(repoPath, storagePath, projectName, {
|
|
372
385
|
files: pipelineRuntime.totalFileCount,
|
|
373
386
|
nodes: stats.nodes,
|
|
@@ -376,7 +389,8 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
376
389
|
clusters: aggregatedClusterCount,
|
|
377
390
|
processes: pipelineRuntime.processResult?.stats.totalProcesses,
|
|
378
391
|
}, {
|
|
379
|
-
skillScope: (
|
|
392
|
+
skillScope: (cliConfig.setupScope === 'global') ? 'global' : 'project',
|
|
393
|
+
cliPackageSpec: resolveCliSpec({ config: cliConfig }).packageSpec,
|
|
380
394
|
}, generatedSkills);
|
|
381
395
|
await closeLbug();
|
|
382
396
|
// Note: we intentionally do NOT call disposeEmbedder() here.
|
package/dist/cli/index.js
CHANGED
|
@@ -17,6 +17,8 @@ program
|
|
|
17
17
|
.description('One-time setup: configure MCP for a selected coding agent (claude/opencode/codex)')
|
|
18
18
|
.option('--scope <scope>', 'Install target: global (default) or project')
|
|
19
19
|
.option('--agent <agent>', 'Target coding agent: claude, opencode, or codex')
|
|
20
|
+
.option('--cli-version <version>', 'Pin npx GitNexus version/tag for generated MCP commands (e.g. 1.4.7-rc)')
|
|
21
|
+
.option('--cli-spec <spec>', 'Pin full npx package spec for generated MCP commands (e.g. @veewo/gitnexus@1.4.7-rc)')
|
|
20
22
|
.action(createLazyAction(() => import('./setup.js'), 'setupCommand'));
|
|
21
23
|
program
|
|
22
24
|
.command('analyze [path]')
|
package/dist/cli/setup.d.ts
CHANGED
package/dist/cli/setup.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* in either global or project scope.
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
|
-
import { readFileSync } from 'node:fs';
|
|
10
9
|
import path from 'path';
|
|
11
10
|
import os from 'os';
|
|
12
11
|
import { execFile } from 'node:child_process';
|
|
@@ -15,10 +14,10 @@ import { fileURLToPath } from 'url';
|
|
|
15
14
|
import { getGlobalDir, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
|
|
16
15
|
import { getGitRoot } from '../storage/git.js';
|
|
17
16
|
import { glob } from 'glob';
|
|
17
|
+
import { buildNpxCommand, resolveCliSpec } from '../config/cli-spec.js';
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = path.dirname(__filename);
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
-
const FALLBACK_MCP_PACKAGE = '@veewo/gitnexus@latest';
|
|
22
21
|
const LEGACY_CURSOR_AGENT = 'cursor';
|
|
23
22
|
function resolveSetupScope(rawScope) {
|
|
24
23
|
if (!rawScope || rawScope.trim() === '')
|
|
@@ -48,43 +47,25 @@ async function installLegacyCursorSkills(result) {
|
|
|
48
47
|
result.errors.push(`Cursor skills: ${err.message}`);
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
* Resolve the package spec used by MCP commands.
|
|
53
|
-
* Defaults to @veewo/gitnexus@latest when package metadata is unavailable.
|
|
54
|
-
*/
|
|
55
|
-
function resolveMcpPackageSpec() {
|
|
56
|
-
try {
|
|
57
|
-
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
|
|
58
|
-
const raw = readFileSync(packageJsonPath, 'utf-8');
|
|
59
|
-
const parsed = JSON.parse(raw);
|
|
60
|
-
if (typeof parsed.name === 'string' && parsed.name.trim().length > 0) {
|
|
61
|
-
return `${parsed.name}@latest`;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
// Fallback keeps behavior for unusual runtimes.
|
|
66
|
-
}
|
|
67
|
-
return FALLBACK_MCP_PACKAGE;
|
|
68
|
-
}
|
|
69
|
-
const MCP_PACKAGE_SPEC = resolveMcpPackageSpec();
|
|
50
|
+
const DEFAULT_MCP_PACKAGE_SPEC = resolveCliSpec().packageSpec;
|
|
70
51
|
/**
|
|
71
52
|
* The MCP server entry for all editors.
|
|
72
53
|
* On Windows, npx must be invoked via cmd /c since it's a .cmd script.
|
|
73
54
|
*/
|
|
74
|
-
function getMcpEntry() {
|
|
55
|
+
function getMcpEntry(mcpPackageSpec) {
|
|
75
56
|
if (process.platform === 'win32') {
|
|
76
57
|
return {
|
|
77
58
|
command: 'cmd',
|
|
78
|
-
args: ['/c', 'npx', '-y',
|
|
59
|
+
args: ['/c', 'npx', '-y', mcpPackageSpec, 'mcp'],
|
|
79
60
|
};
|
|
80
61
|
}
|
|
81
62
|
return {
|
|
82
63
|
command: 'npx',
|
|
83
|
-
args: ['-y',
|
|
64
|
+
args: ['-y', mcpPackageSpec, 'mcp'],
|
|
84
65
|
};
|
|
85
66
|
}
|
|
86
|
-
function getOpenCodeMcpEntry() {
|
|
87
|
-
const entry = getMcpEntry();
|
|
67
|
+
function getOpenCodeMcpEntry(mcpPackageSpec) {
|
|
68
|
+
const entry = getMcpEntry(mcpPackageSpec);
|
|
88
69
|
return {
|
|
89
70
|
type: 'local',
|
|
90
71
|
command: [entry.command, ...entry.args],
|
|
@@ -94,28 +75,28 @@ function getOpenCodeMcpEntry() {
|
|
|
94
75
|
* Merge gitnexus entry into an existing MCP config JSON object.
|
|
95
76
|
* Returns the updated config.
|
|
96
77
|
*/
|
|
97
|
-
function mergeMcpConfig(existing) {
|
|
78
|
+
function mergeMcpConfig(existing, mcpPackageSpec) {
|
|
98
79
|
if (!existing || typeof existing !== 'object') {
|
|
99
80
|
existing = {};
|
|
100
81
|
}
|
|
101
82
|
if (!existing.mcpServers || typeof existing.mcpServers !== 'object') {
|
|
102
83
|
existing.mcpServers = {};
|
|
103
84
|
}
|
|
104
|
-
existing.mcpServers.gitnexus = getMcpEntry();
|
|
85
|
+
existing.mcpServers.gitnexus = getMcpEntry(mcpPackageSpec);
|
|
105
86
|
return existing;
|
|
106
87
|
}
|
|
107
88
|
/**
|
|
108
89
|
* Merge gitnexus entry into an OpenCode config JSON object.
|
|
109
90
|
* Returns the updated config.
|
|
110
91
|
*/
|
|
111
|
-
function mergeOpenCodeConfig(existing) {
|
|
92
|
+
function mergeOpenCodeConfig(existing, mcpPackageSpec) {
|
|
112
93
|
if (!existing || typeof existing !== 'object') {
|
|
113
94
|
existing = {};
|
|
114
95
|
}
|
|
115
96
|
if (!existing.mcp || typeof existing.mcp !== 'object') {
|
|
116
97
|
existing.mcp = {};
|
|
117
98
|
}
|
|
118
|
-
existing.mcp.gitnexus = getOpenCodeMcpEntry();
|
|
99
|
+
existing.mcp.gitnexus = getOpenCodeMcpEntry(mcpPackageSpec);
|
|
119
100
|
return existing;
|
|
120
101
|
}
|
|
121
102
|
/**
|
|
@@ -167,16 +148,16 @@ async function fileExists(filePath) {
|
|
|
167
148
|
function toTomlString(value) {
|
|
168
149
|
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
169
150
|
}
|
|
170
|
-
function buildCodexMcpTable() {
|
|
171
|
-
const entry = getMcpEntry();
|
|
151
|
+
function buildCodexMcpTable(mcpPackageSpec) {
|
|
152
|
+
const entry = getMcpEntry(mcpPackageSpec);
|
|
172
153
|
return [
|
|
173
154
|
'[mcp_servers.gitnexus]',
|
|
174
155
|
`command = ${toTomlString(entry.command)}`,
|
|
175
156
|
`args = [${entry.args.map(toTomlString).join(', ')}]`,
|
|
176
157
|
].join('\n');
|
|
177
158
|
}
|
|
178
|
-
function mergeCodexConfig(existingRaw) {
|
|
179
|
-
const table = buildCodexMcpTable();
|
|
159
|
+
function mergeCodexConfig(existingRaw, mcpPackageSpec) {
|
|
160
|
+
const table = buildCodexMcpTable(mcpPackageSpec);
|
|
180
161
|
const normalized = existingRaw.replace(/\r\n/g, '\n');
|
|
181
162
|
const tablePattern = /^\[mcp_servers\.gitnexus\][\s\S]*?(?=^\[[^\]]+\]|(?![\s\S]))/m;
|
|
182
163
|
if (tablePattern.test(normalized)) {
|
|
@@ -198,7 +179,7 @@ async function resolveOpenCodeConfigPath(opencodeDir) {
|
|
|
198
179
|
return preferredPath;
|
|
199
180
|
}
|
|
200
181
|
// ─── Editor-specific setup ─────────────────────────────────────────
|
|
201
|
-
async function setupCursor(result) {
|
|
182
|
+
async function setupCursor(result, mcpPackageSpec) {
|
|
202
183
|
const cursorDir = path.join(os.homedir(), '.cursor');
|
|
203
184
|
if (!(await dirExists(cursorDir))) {
|
|
204
185
|
result.skipped.push('Cursor (not installed)');
|
|
@@ -207,7 +188,7 @@ async function setupCursor(result) {
|
|
|
207
188
|
const mcpPath = path.join(cursorDir, 'mcp.json');
|
|
208
189
|
try {
|
|
209
190
|
const existing = await readJsonFile(mcpPath);
|
|
210
|
-
const updated = mergeMcpConfig(existing);
|
|
191
|
+
const updated = mergeMcpConfig(existing, mcpPackageSpec);
|
|
211
192
|
await writeJsonFile(mcpPath, updated);
|
|
212
193
|
result.configured.push('Cursor');
|
|
213
194
|
}
|
|
@@ -215,7 +196,7 @@ async function setupCursor(result) {
|
|
|
215
196
|
result.errors.push(`Cursor: ${err.message}`);
|
|
216
197
|
}
|
|
217
198
|
}
|
|
218
|
-
async function setupClaudeCode(result) {
|
|
199
|
+
async function setupClaudeCode(result, mcpPackageSpec) {
|
|
219
200
|
const claudeDir = path.join(os.homedir(), '.claude');
|
|
220
201
|
const hasClaude = await dirExists(claudeDir);
|
|
221
202
|
if (!hasClaude) {
|
|
@@ -226,7 +207,7 @@ async function setupClaudeCode(result) {
|
|
|
226
207
|
console.log('');
|
|
227
208
|
console.log(' Claude Code detected. Run this command to add GitNexus MCP:');
|
|
228
209
|
console.log('');
|
|
229
|
-
console.log(` claude mcp add gitnexus --
|
|
210
|
+
console.log(` claude mcp add gitnexus -- ${buildNpxCommand(mcpPackageSpec, 'mcp')}`);
|
|
230
211
|
console.log('');
|
|
231
212
|
result.configured.push('Claude Code (MCP manual step printed)');
|
|
232
213
|
}
|
|
@@ -262,7 +243,7 @@ async function installProjectAgentSkills(repoRoot, result) {
|
|
|
262
243
|
* Install GitNexus hooks to ~/.claude/settings.json for Claude Code.
|
|
263
244
|
* Merges hook config without overwriting existing hooks.
|
|
264
245
|
*/
|
|
265
|
-
async function installClaudeCodeHooks(result) {
|
|
246
|
+
async function installClaudeCodeHooks(result, mcpPackageSpec) {
|
|
266
247
|
const claudeDir = path.join(os.homedir(), '.claude');
|
|
267
248
|
if (!(await dirExists(claudeDir)))
|
|
268
249
|
return;
|
|
@@ -314,7 +295,7 @@ async function installClaudeCodeHooks(result) {
|
|
|
314
295
|
result.errors.push(`Claude Code hooks: ${err.message}`);
|
|
315
296
|
}
|
|
316
297
|
}
|
|
317
|
-
async function setupOpenCode(result) {
|
|
298
|
+
async function setupOpenCode(result, mcpPackageSpec) {
|
|
318
299
|
const opencodeDir = path.join(os.homedir(), '.config', 'opencode');
|
|
319
300
|
if (!(await dirExists(opencodeDir))) {
|
|
320
301
|
result.skipped.push('OpenCode (not installed)');
|
|
@@ -323,7 +304,7 @@ async function setupOpenCode(result) {
|
|
|
323
304
|
const configPath = await resolveOpenCodeConfigPath(opencodeDir);
|
|
324
305
|
try {
|
|
325
306
|
const existing = await readJsonFile(configPath);
|
|
326
|
-
const config = mergeOpenCodeConfig(existing);
|
|
307
|
+
const config = mergeOpenCodeConfig(existing, mcpPackageSpec);
|
|
327
308
|
await writeJsonFile(configPath, config);
|
|
328
309
|
result.configured.push(`OpenCode (${path.basename(configPath)})`);
|
|
329
310
|
}
|
|
@@ -331,8 +312,8 @@ async function setupOpenCode(result) {
|
|
|
331
312
|
result.errors.push(`OpenCode: ${err.message}`);
|
|
332
313
|
}
|
|
333
314
|
}
|
|
334
|
-
async function setupCodex(result) {
|
|
335
|
-
const entry = getMcpEntry();
|
|
315
|
+
async function setupCodex(result, mcpPackageSpec) {
|
|
316
|
+
const entry = getMcpEntry(mcpPackageSpec);
|
|
336
317
|
try {
|
|
337
318
|
await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], { timeout: 15000 });
|
|
338
319
|
result.configured.push('Codex');
|
|
@@ -345,11 +326,11 @@ async function setupCodex(result) {
|
|
|
345
326
|
result.errors.push(`Codex: ${err.message}`);
|
|
346
327
|
}
|
|
347
328
|
}
|
|
348
|
-
async function setupProjectMcp(repoRoot, result) {
|
|
329
|
+
async function setupProjectMcp(repoRoot, result, mcpPackageSpec) {
|
|
349
330
|
const mcpPath = path.join(repoRoot, '.mcp.json');
|
|
350
331
|
try {
|
|
351
332
|
const existing = await readJsonFile(mcpPath);
|
|
352
|
-
const updated = mergeMcpConfig(existing);
|
|
333
|
+
const updated = mergeMcpConfig(existing, mcpPackageSpec);
|
|
353
334
|
await writeJsonFile(mcpPath, updated);
|
|
354
335
|
result.configured.push(`Project MCP (${path.relative(repoRoot, mcpPath)})`);
|
|
355
336
|
}
|
|
@@ -357,7 +338,7 @@ async function setupProjectMcp(repoRoot, result) {
|
|
|
357
338
|
result.errors.push(`Project MCP: ${err.message}`);
|
|
358
339
|
}
|
|
359
340
|
}
|
|
360
|
-
async function setupProjectCodex(repoRoot, result) {
|
|
341
|
+
async function setupProjectCodex(repoRoot, result, mcpPackageSpec) {
|
|
361
342
|
const codexConfigPath = path.join(repoRoot, '.codex', 'config.toml');
|
|
362
343
|
try {
|
|
363
344
|
let existingRaw = '';
|
|
@@ -368,7 +349,7 @@ async function setupProjectCodex(repoRoot, result) {
|
|
|
368
349
|
if (err?.code !== 'ENOENT')
|
|
369
350
|
throw err;
|
|
370
351
|
}
|
|
371
|
-
const merged = mergeCodexConfig(existingRaw);
|
|
352
|
+
const merged = mergeCodexConfig(existingRaw, mcpPackageSpec);
|
|
372
353
|
await fs.mkdir(path.dirname(codexConfigPath), { recursive: true });
|
|
373
354
|
await fs.writeFile(codexConfigPath, merged, 'utf-8');
|
|
374
355
|
result.configured.push(`Project Codex MCP (${path.relative(repoRoot, codexConfigPath)})`);
|
|
@@ -377,11 +358,11 @@ async function setupProjectCodex(repoRoot, result) {
|
|
|
377
358
|
result.errors.push(`Project Codex MCP: ${err.message}`);
|
|
378
359
|
}
|
|
379
360
|
}
|
|
380
|
-
async function setupProjectOpenCode(repoRoot, result) {
|
|
361
|
+
async function setupProjectOpenCode(repoRoot, result, mcpPackageSpec) {
|
|
381
362
|
const opencodePath = path.join(repoRoot, 'opencode.json');
|
|
382
363
|
try {
|
|
383
364
|
const existing = await readJsonFile(opencodePath);
|
|
384
|
-
const merged = mergeOpenCodeConfig(existing);
|
|
365
|
+
const merged = mergeOpenCodeConfig(existing, mcpPackageSpec);
|
|
385
366
|
await writeJsonFile(opencodePath, merged);
|
|
386
367
|
result.configured.push(`Project OpenCode MCP (${path.relative(repoRoot, opencodePath)})`);
|
|
387
368
|
}
|
|
@@ -389,11 +370,28 @@ async function setupProjectOpenCode(repoRoot, result) {
|
|
|
389
370
|
result.errors.push(`Project OpenCode MCP: ${err.message}`);
|
|
390
371
|
}
|
|
391
372
|
}
|
|
392
|
-
|
|
373
|
+
function extractVersionFromPackageSpec(packageSpec) {
|
|
374
|
+
const trimmed = packageSpec.trim();
|
|
375
|
+
if (!trimmed)
|
|
376
|
+
return undefined;
|
|
377
|
+
if (trimmed.startsWith('@')) {
|
|
378
|
+
const at = trimmed.indexOf('@', 1);
|
|
379
|
+
return at > 0 ? trimmed.slice(at + 1) : undefined;
|
|
380
|
+
}
|
|
381
|
+
const at = trimmed.lastIndexOf('@');
|
|
382
|
+
return at > 0 ? trimmed.slice(at + 1) : undefined;
|
|
383
|
+
}
|
|
384
|
+
async function saveSetupConfig(scope, packageSpec, result) {
|
|
393
385
|
try {
|
|
394
386
|
const existing = await loadCLIConfig();
|
|
395
|
-
await saveCLIConfig({
|
|
387
|
+
await saveCLIConfig({
|
|
388
|
+
...existing,
|
|
389
|
+
setupScope: scope,
|
|
390
|
+
cliPackageSpec: packageSpec,
|
|
391
|
+
cliVersion: extractVersionFromPackageSpec(packageSpec),
|
|
392
|
+
});
|
|
396
393
|
result.configured.push(`Default setup scope (${scope})`);
|
|
394
|
+
result.configured.push(`CLI package spec (${packageSpec})`);
|
|
397
395
|
}
|
|
398
396
|
catch (err) {
|
|
399
397
|
result.errors.push(`Persist setup scope: ${err.message}`);
|
|
@@ -477,6 +475,11 @@ export const setupCommand = async (options = {}) => {
|
|
|
477
475
|
console.log(' GitNexus Setup');
|
|
478
476
|
console.log(' ==============');
|
|
479
477
|
console.log('');
|
|
478
|
+
if (options.cliSpec && options.cliVersion) {
|
|
479
|
+
console.log(' Use either --cli-spec or --cli-version, not both.\n');
|
|
480
|
+
process.exitCode = 1;
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
480
483
|
let scope;
|
|
481
484
|
let agent;
|
|
482
485
|
const legacyCursorMode = !options.agent || options.agent.trim() === '';
|
|
@@ -492,6 +495,13 @@ export const setupCommand = async (options = {}) => {
|
|
|
492
495
|
// Ensure global directory exists
|
|
493
496
|
const globalDir = getGlobalDir();
|
|
494
497
|
await fs.mkdir(globalDir, { recursive: true });
|
|
498
|
+
const existingConfig = await loadCLIConfig();
|
|
499
|
+
const resolvedCliSpec = resolveCliSpec({
|
|
500
|
+
explicitSpec: options.cliSpec,
|
|
501
|
+
explicitVersion: options.cliVersion,
|
|
502
|
+
config: existingConfig,
|
|
503
|
+
});
|
|
504
|
+
const mcpPackageSpec = resolvedCliSpec.packageSpec || DEFAULT_MCP_PACKAGE_SPEC;
|
|
495
505
|
const result = {
|
|
496
506
|
configured: [],
|
|
497
507
|
skipped: [],
|
|
@@ -499,27 +509,27 @@ export const setupCommand = async (options = {}) => {
|
|
|
499
509
|
};
|
|
500
510
|
if (scope === 'global') {
|
|
501
511
|
if (legacyCursorMode) {
|
|
502
|
-
await setupCursor(result);
|
|
512
|
+
await setupCursor(result, mcpPackageSpec);
|
|
503
513
|
await installLegacyCursorSkills(result);
|
|
504
|
-
await
|
|
514
|
+
await saveSetupConfig(scope, mcpPackageSpec, result);
|
|
505
515
|
agent = LEGACY_CURSOR_AGENT;
|
|
506
516
|
}
|
|
507
517
|
else {
|
|
508
518
|
// Configure only the selected agent MCP
|
|
509
519
|
if (agent === 'claude') {
|
|
510
|
-
await setupClaudeCode(result);
|
|
520
|
+
await setupClaudeCode(result, mcpPackageSpec);
|
|
511
521
|
// Claude-only hooks should only be installed when Claude is selected.
|
|
512
|
-
await installClaudeCodeHooks(result);
|
|
522
|
+
await installClaudeCodeHooks(result, mcpPackageSpec);
|
|
513
523
|
}
|
|
514
524
|
else if (agent === 'opencode') {
|
|
515
|
-
await setupOpenCode(result);
|
|
525
|
+
await setupOpenCode(result, mcpPackageSpec);
|
|
516
526
|
}
|
|
517
527
|
else if (agent === 'codex') {
|
|
518
|
-
await setupCodex(result);
|
|
528
|
+
await setupCodex(result, mcpPackageSpec);
|
|
519
529
|
}
|
|
520
530
|
// Install shared global skills once
|
|
521
531
|
await installGlobalAgentSkills(result);
|
|
522
|
-
await
|
|
532
|
+
await saveSetupConfig(scope, mcpPackageSpec, result);
|
|
523
533
|
}
|
|
524
534
|
}
|
|
525
535
|
else {
|
|
@@ -530,16 +540,16 @@ export const setupCommand = async (options = {}) => {
|
|
|
530
540
|
return;
|
|
531
541
|
}
|
|
532
542
|
if (agent === 'claude') {
|
|
533
|
-
await setupProjectMcp(repoRoot, result);
|
|
543
|
+
await setupProjectMcp(repoRoot, result, mcpPackageSpec);
|
|
534
544
|
}
|
|
535
545
|
else if (agent === 'codex') {
|
|
536
|
-
await setupProjectCodex(repoRoot, result);
|
|
546
|
+
await setupProjectCodex(repoRoot, result, mcpPackageSpec);
|
|
537
547
|
}
|
|
538
548
|
else if (agent === 'opencode') {
|
|
539
|
-
await setupProjectOpenCode(repoRoot, result);
|
|
549
|
+
await setupProjectOpenCode(repoRoot, result, mcpPackageSpec);
|
|
540
550
|
}
|
|
541
551
|
await installProjectAgentSkills(repoRoot, result);
|
|
542
|
-
await
|
|
552
|
+
await saveSetupConfig(scope, mcpPackageSpec, result);
|
|
543
553
|
}
|
|
544
554
|
// Print results
|
|
545
555
|
if (result.configured.length > 0) {
|
|
@@ -566,6 +576,7 @@ export const setupCommand = async (options = {}) => {
|
|
|
566
576
|
console.log(' Summary:');
|
|
567
577
|
console.log(` Scope: ${scope}`);
|
|
568
578
|
console.log(` Agent: ${legacyCursorMode ? LEGACY_CURSOR_AGENT : agent}`);
|
|
579
|
+
console.log(` CLI package spec: ${mcpPackageSpec}`);
|
|
569
580
|
console.log(` MCP configured for: ${result.configured.filter(c => !c.includes('skills')).join(', ') || 'none'}`);
|
|
570
581
|
console.log(` Skills installed to: ${result.configured.filter(c => c.includes('skills')).length > 0 ? result.configured.filter(c => c.includes('skills')).join(', ') : 'none'}`);
|
|
571
582
|
console.log('');
|
package/dist/cli/setup.test.js
CHANGED
|
@@ -61,6 +61,26 @@ test('setup rejects invalid --agent', async () => {
|
|
|
61
61
|
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
62
62
|
}
|
|
63
63
|
});
|
|
64
|
+
test('setup rejects using --cli-spec and --cli-version together', async () => {
|
|
65
|
+
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
66
|
+
try {
|
|
67
|
+
try {
|
|
68
|
+
await runSetup(['--agent', 'opencode', '--cli-spec', '@veewo/gitnexus@1.4.7-rc', '--cli-version', '1.4.7-rc'], {
|
|
69
|
+
...process.env,
|
|
70
|
+
HOME: fakeHome,
|
|
71
|
+
USERPROFILE: fakeHome,
|
|
72
|
+
});
|
|
73
|
+
assert.fail('expected setup with conflicting CLI options to fail');
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
assert.equal(typeof err?.stdout, 'string');
|
|
77
|
+
assert.match(err.stdout, /Use either --cli-spec or --cli-version/);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
64
84
|
test('setup installs global skills under ~/.agents/skills/gitnexus', async () => {
|
|
65
85
|
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
66
86
|
try {
|
|
@@ -148,6 +168,30 @@ test('setup configures OpenCode MCP in ~/.config/opencode/opencode.json', async
|
|
|
148
168
|
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
149
169
|
}
|
|
150
170
|
});
|
|
171
|
+
test('setup --cli-version pins MCP package spec and persists it in config', async () => {
|
|
172
|
+
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
173
|
+
try {
|
|
174
|
+
const opencodeDir = path.join(fakeHome, '.config', 'opencode');
|
|
175
|
+
await fs.mkdir(opencodeDir, { recursive: true });
|
|
176
|
+
await runSetup(['--agent', 'opencode', '--cli-version', '1.4.7-rc'], {
|
|
177
|
+
...process.env,
|
|
178
|
+
HOME: fakeHome,
|
|
179
|
+
USERPROFILE: fakeHome,
|
|
180
|
+
});
|
|
181
|
+
const opencodeConfigPath = path.join(opencodeDir, 'opencode.json');
|
|
182
|
+
const opencodeRaw = await fs.readFile(opencodeConfigPath, 'utf-8');
|
|
183
|
+
const opencodeConfig = JSON.parse(opencodeRaw);
|
|
184
|
+
const configPath = path.join(fakeHome, '.gitnexus', 'config.json');
|
|
185
|
+
const savedConfigRaw = await fs.readFile(configPath, 'utf-8');
|
|
186
|
+
const savedConfig = JSON.parse(savedConfigRaw);
|
|
187
|
+
assert.deepEqual(opencodeConfig.mcp?.gitnexus?.command, ['npx', '-y', '@veewo/gitnexus@1.4.7-rc', 'mcp']);
|
|
188
|
+
assert.equal(savedConfig.cliPackageSpec, '@veewo/gitnexus@1.4.7-rc');
|
|
189
|
+
assert.equal(savedConfig.cliVersion, '1.4.7-rc');
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
});
|
|
151
195
|
test('setup keeps using legacy ~/.config/opencode/config.json when it already exists', async () => {
|
|
152
196
|
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
153
197
|
try {
|
package/dist/cli/status.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Shows the indexing status of the current repository.
|
|
5
5
|
*/
|
|
6
|
-
import { findRepo, getStoragePaths, hasKuzuIndex } from '../storage/repo-manager.js';
|
|
6
|
+
import { findRepo, getStoragePaths, hasKuzuIndex, hasLbugIndex } from '../storage/repo-manager.js';
|
|
7
7
|
import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
|
|
8
8
|
export const statusCommand = async () => {
|
|
9
9
|
const cwd = process.cwd();
|
|
@@ -28,6 +28,13 @@ export const statusCommand = async () => {
|
|
|
28
28
|
}
|
|
29
29
|
const currentCommit = getCurrentCommit(repo.repoPath);
|
|
30
30
|
const isUpToDate = currentCommit === repo.meta.lastCommit;
|
|
31
|
+
const lbugReady = await hasLbugIndex(repo.storagePath);
|
|
32
|
+
if (!lbugReady) {
|
|
33
|
+
console.log(`Repository: ${repo.repoPath}`);
|
|
34
|
+
console.log('Status: ⚠️ index metadata exists but LadybugDB is missing');
|
|
35
|
+
console.log('Run: gitnexus analyze --force');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
31
38
|
console.log(`Repository: ${repo.repoPath}`);
|
|
32
39
|
console.log(`Indexed: ${new Date(repo.meta.indexedAt).toLocaleString()}`);
|
|
33
40
|
console.log(`Indexed commit: ${repo.meta.lastCommit?.slice(0, 7)}`);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const DEFAULT_GITNEXUS_PACKAGE_NAME = "@veewo/gitnexus";
|
|
2
|
+
export declare const DEFAULT_GITNEXUS_DIST_TAG = "latest";
|
|
3
|
+
export declare const CLI_SPEC_ENV_KEY = "GITNEXUS_CLI_SPEC";
|
|
4
|
+
export declare const CLI_VERSION_ENV_KEY = "GITNEXUS_CLI_VERSION";
|
|
5
|
+
type CliSpecSource = 'explicit-spec' | 'explicit-version' | 'env-spec' | 'env-version' | 'config-spec' | 'config-version' | 'default';
|
|
6
|
+
export interface CliSpecConfigLike {
|
|
7
|
+
cliPackageSpec?: string;
|
|
8
|
+
cliVersion?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ResolveCliSpecInput {
|
|
11
|
+
packageName?: string;
|
|
12
|
+
explicitSpec?: string;
|
|
13
|
+
explicitVersion?: string;
|
|
14
|
+
config?: CliSpecConfigLike;
|
|
15
|
+
env?: NodeJS.ProcessEnv;
|
|
16
|
+
defaultDistTag?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ResolvedCliSpec {
|
|
19
|
+
packageName: string;
|
|
20
|
+
packageSpec: string;
|
|
21
|
+
source: CliSpecSource;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve package name from package.json with fallback for unusual runtimes.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveGitNexusPackageName(): string;
|
|
27
|
+
export declare function resolveCliSpec(input?: ResolveCliSpecInput): ResolvedCliSpec;
|
|
28
|
+
export declare function buildNpxCommand(packageSpec: string, subcommand: string): string;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
export const DEFAULT_GITNEXUS_PACKAGE_NAME = '@veewo/gitnexus';
|
|
5
|
+
export const DEFAULT_GITNEXUS_DIST_TAG = 'latest';
|
|
6
|
+
export const CLI_SPEC_ENV_KEY = 'GITNEXUS_CLI_SPEC';
|
|
7
|
+
export const CLI_VERSION_ENV_KEY = 'GITNEXUS_CLI_VERSION';
|
|
8
|
+
let cachedPackageName = null;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve package name from package.json with fallback for unusual runtimes.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveGitNexusPackageName() {
|
|
13
|
+
if (cachedPackageName)
|
|
14
|
+
return cachedPackageName;
|
|
15
|
+
try {
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const packageJsonPath = path.join(__dirname, '..', '..', '..', 'package.json');
|
|
19
|
+
const raw = readFileSync(packageJsonPath, 'utf-8');
|
|
20
|
+
const parsed = JSON.parse(raw);
|
|
21
|
+
const trimmed = typeof parsed.name === 'string' ? parsed.name.trim() : '';
|
|
22
|
+
cachedPackageName = trimmed || DEFAULT_GITNEXUS_PACKAGE_NAME;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
cachedPackageName = DEFAULT_GITNEXUS_PACKAGE_NAME;
|
|
26
|
+
}
|
|
27
|
+
return cachedPackageName;
|
|
28
|
+
}
|
|
29
|
+
function hasPinnedVersion(packageSpec) {
|
|
30
|
+
const trimmed = packageSpec.trim();
|
|
31
|
+
if (!trimmed)
|
|
32
|
+
return false;
|
|
33
|
+
if (trimmed.startsWith('@')) {
|
|
34
|
+
return trimmed.indexOf('@', 1) !== -1;
|
|
35
|
+
}
|
|
36
|
+
return trimmed.includes('@');
|
|
37
|
+
}
|
|
38
|
+
function looksLikePackageSpec(token) {
|
|
39
|
+
if (!token)
|
|
40
|
+
return false;
|
|
41
|
+
if (token.startsWith('@'))
|
|
42
|
+
return true;
|
|
43
|
+
if (token.includes('/'))
|
|
44
|
+
return true;
|
|
45
|
+
return token.includes('@');
|
|
46
|
+
}
|
|
47
|
+
function normalizePackageSpec(packageName, raw, defaultDistTag) {
|
|
48
|
+
const token = raw.trim();
|
|
49
|
+
if (!token)
|
|
50
|
+
return `${packageName}@${defaultDistTag}`;
|
|
51
|
+
if (looksLikePackageSpec(token)) {
|
|
52
|
+
return hasPinnedVersion(token) ? token : `${token}@${defaultDistTag}`;
|
|
53
|
+
}
|
|
54
|
+
return `${packageName}@${token}`;
|
|
55
|
+
}
|
|
56
|
+
export function resolveCliSpec(input = {}) {
|
|
57
|
+
const packageName = (input.packageName || resolveGitNexusPackageName()).trim() || DEFAULT_GITNEXUS_PACKAGE_NAME;
|
|
58
|
+
const env = input.env || process.env;
|
|
59
|
+
const config = input.config || {};
|
|
60
|
+
const defaultDistTag = (input.defaultDistTag || DEFAULT_GITNEXUS_DIST_TAG).trim() || DEFAULT_GITNEXUS_DIST_TAG;
|
|
61
|
+
const candidates = [
|
|
62
|
+
{ value: input.explicitSpec, source: 'explicit-spec' },
|
|
63
|
+
{ value: input.explicitVersion, source: 'explicit-version' },
|
|
64
|
+
{ value: env[CLI_SPEC_ENV_KEY], source: 'env-spec' },
|
|
65
|
+
{ value: env[CLI_VERSION_ENV_KEY], source: 'env-version' },
|
|
66
|
+
{ value: config.cliPackageSpec, source: 'config-spec' },
|
|
67
|
+
{ value: config.cliVersion, source: 'config-version' },
|
|
68
|
+
];
|
|
69
|
+
for (const candidate of candidates) {
|
|
70
|
+
const trimmed = (candidate.value || '').trim();
|
|
71
|
+
if (!trimmed)
|
|
72
|
+
continue;
|
|
73
|
+
return {
|
|
74
|
+
packageName,
|
|
75
|
+
packageSpec: normalizePackageSpec(packageName, trimmed, defaultDistTag),
|
|
76
|
+
source: candidate.source,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
packageName,
|
|
81
|
+
packageSpec: `${packageName}@${defaultDistTag}`,
|
|
82
|
+
source: 'default',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function buildNpxCommand(packageSpec, subcommand) {
|
|
86
|
+
return `npx -y ${packageSpec} ${subcommand}`.trim();
|
|
87
|
+
}
|
package/dist/mcp/resources.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* All resources use repo-scoped URIs: gitnexus://repo/{name}/context
|
|
6
6
|
*/
|
|
7
7
|
import { checkStaleness } from './staleness.js';
|
|
8
|
+
import { loadCLIConfig } from '../storage/repo-manager.js';
|
|
9
|
+
import { buildNpxCommand, resolveCliSpec } from '../config/cli-spec.js';
|
|
8
10
|
/**
|
|
9
11
|
* Static resources — includes per-repo resources and the global repos list
|
|
10
12
|
*/
|
|
@@ -121,6 +123,11 @@ export async function readResource(uri, backend) {
|
|
|
121
123
|
throw new Error(`Unknown resource: ${uri}`);
|
|
122
124
|
}
|
|
123
125
|
}
|
|
126
|
+
async function resolveAnalyzeNpxCommand() {
|
|
127
|
+
const config = await loadCLIConfig();
|
|
128
|
+
const packageSpec = resolveCliSpec({ config }).packageSpec;
|
|
129
|
+
return buildNpxCommand(packageSpec, 'analyze');
|
|
130
|
+
}
|
|
124
131
|
// ─── Resource Implementations ─────────────────────────────────────────
|
|
125
132
|
/**
|
|
126
133
|
* Repos resource — list all indexed repositories
|
|
@@ -186,7 +193,8 @@ async function getContextResource(backend, repoName) {
|
|
|
186
193
|
lines.push(' - cypher: Raw graph queries');
|
|
187
194
|
lines.push(' - list_repos: Discover all indexed repositories');
|
|
188
195
|
lines.push('');
|
|
189
|
-
|
|
196
|
+
const analyzeCmd = await resolveAnalyzeNpxCommand();
|
|
197
|
+
lines.push(`re_index: If data is stale, ask user whether to run \`gitnexus analyze\` when local CLI exists; otherwise run \`${analyzeCmd}\` (reuses previous analyze scope/options unless \`--no-reuse-options\` is passed). If user declines, clearly state retrieval may not reflect current code. For build/analyze/test commands, use 10-30 minute timeout; on failure/timeout, report exact tool output and do not auto-retry or silently switch to glob/grep fallback.`);
|
|
190
198
|
lines.push('');
|
|
191
199
|
lines.push('resources_available:');
|
|
192
200
|
lines.push(' - gitnexus://repos: All indexed repositories');
|
|
@@ -371,8 +379,9 @@ async function getProcessDetailResource(name, backend, repoName) {
|
|
|
371
379
|
*/
|
|
372
380
|
async function getSetupResource(backend) {
|
|
373
381
|
const repos = await backend.listRepos();
|
|
382
|
+
const analyzeCmd = await resolveAnalyzeNpxCommand();
|
|
374
383
|
if (repos.length === 0) {
|
|
375
|
-
return
|
|
384
|
+
return `# GitNexus\n\nNo repositories indexed. Run: \`gitnexus analyze\` when local CLI exists; otherwise \`${analyzeCmd}\` in a repository.`;
|
|
376
385
|
}
|
|
377
386
|
const sections = [];
|
|
378
387
|
for (const repo of repos) {
|
|
@@ -64,6 +64,10 @@ export declare const getStoragePaths: (repoPath: string) => {
|
|
|
64
64
|
* Non-destructive — safe to call from status commands.
|
|
65
65
|
*/
|
|
66
66
|
export declare const hasKuzuIndex: (storagePath: string) => Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Check whether a LadybugDB index exists in the given storage path.
|
|
69
|
+
*/
|
|
70
|
+
export declare const hasLbugIndex: (storagePath: string) => Promise<boolean>;
|
|
67
71
|
/**
|
|
68
72
|
* Clean up stale KuzuDB files after migration to LadybugDB.
|
|
69
73
|
*
|
|
@@ -135,6 +139,8 @@ export interface CLIConfig {
|
|
|
135
139
|
model?: string;
|
|
136
140
|
baseUrl?: string;
|
|
137
141
|
setupScope?: 'global' | 'project';
|
|
142
|
+
cliPackageSpec?: string;
|
|
143
|
+
cliVersion?: string;
|
|
138
144
|
}
|
|
139
145
|
/**
|
|
140
146
|
* Get the path to the global CLI config file
|
|
@@ -52,6 +52,18 @@ export const hasKuzuIndex = async (storagePath) => {
|
|
|
52
52
|
return false;
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Check whether a LadybugDB index exists in the given storage path.
|
|
57
|
+
*/
|
|
58
|
+
export const hasLbugIndex = async (storagePath) => {
|
|
59
|
+
try {
|
|
60
|
+
await fs.stat(path.join(storagePath, 'lbug'));
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
55
67
|
/**
|
|
56
68
|
* Clean up stale KuzuDB files after migration to LadybugDB.
|
|
57
69
|
*
|
|
@@ -279,6 +291,7 @@ export const listRegisteredRepos = async (opts) => {
|
|
|
279
291
|
for (const entry of entries) {
|
|
280
292
|
try {
|
|
281
293
|
await fs.access(path.join(entry.storagePath, 'meta.json'));
|
|
294
|
+
await fs.access(path.join(entry.storagePath, 'lbug'));
|
|
282
295
|
valid.push(entry);
|
|
283
296
|
}
|
|
284
297
|
catch {
|
|
@@ -13,7 +13,41 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
16
17
|
const { spawnSync } = require('child_process');
|
|
18
|
+
const DEFAULT_NPX_SPEC = '';
|
|
19
|
+
const DEFAULT_PACKAGE_NAME = '@veewo/gitnexus';
|
|
20
|
+
|
|
21
|
+
function normalizeNpxSpec(raw) {
|
|
22
|
+
const value = (raw || '').trim();
|
|
23
|
+
if (!value) return DEFAULT_NPX_SPEC;
|
|
24
|
+
if (value.startsWith('@') || value.includes('/') || value.includes('@')) {
|
|
25
|
+
if (value.startsWith('@') && value.indexOf('@', 1) === -1) return `${value}@latest`;
|
|
26
|
+
if (!value.startsWith('@') && !value.includes('/') && !value.includes('@')) return `${DEFAULT_PACKAGE_NAME}@${value}`;
|
|
27
|
+
if (!value.startsWith('@') && value.includes('@')) return value;
|
|
28
|
+
if (!value.includes('@')) return `${value}@latest`;
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
return `${DEFAULT_PACKAGE_NAME}@${value}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveNpxSpec() {
|
|
35
|
+
if (process.env.GITNEXUS_CLI_SPEC) return normalizeNpxSpec(process.env.GITNEXUS_CLI_SPEC);
|
|
36
|
+
if (process.env.GITNEXUS_CLI_VERSION) return normalizeNpxSpec(process.env.GITNEXUS_CLI_VERSION);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const raw = fs.readFileSync(path.join(os.homedir(), '.gitnexus', 'config.json'), 'utf-8');
|
|
40
|
+
const parsed = JSON.parse(raw);
|
|
41
|
+
if (typeof parsed.cliPackageSpec === 'string' && parsed.cliPackageSpec.trim()) {
|
|
42
|
+
return normalizeNpxSpec(parsed.cliPackageSpec);
|
|
43
|
+
}
|
|
44
|
+
if (typeof parsed.cliVersion === 'string' && parsed.cliVersion.trim()) {
|
|
45
|
+
return normalizeNpxSpec(parsed.cliVersion);
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
return DEFAULT_NPX_SPEC;
|
|
50
|
+
}
|
|
17
51
|
|
|
18
52
|
/**
|
|
19
53
|
* Read JSON input from stdin synchronously.
|
|
@@ -107,7 +141,7 @@ function resolveCliPath() {
|
|
|
107
141
|
* Spawn a gitnexus CLI command synchronously.
|
|
108
142
|
* Returns the stderr output (KuzuDB captures stdout at OS level).
|
|
109
143
|
*/
|
|
110
|
-
function runGitNexusCli(cliPath, args, cwd, timeout) {
|
|
144
|
+
function runGitNexusCli(cliPath, args, cwd, timeout, npxSpec) {
|
|
111
145
|
const isWin = process.platform === 'win32';
|
|
112
146
|
if (cliPath) {
|
|
113
147
|
return spawnSync(
|
|
@@ -116,10 +150,11 @@ function runGitNexusCli(cliPath, args, cwd, timeout) {
|
|
|
116
150
|
{ encoding: 'utf-8', timeout, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
117
151
|
);
|
|
118
152
|
}
|
|
153
|
+
if (!npxSpec) return null;
|
|
119
154
|
// On Windows, invoke npx.cmd directly (no shell needed)
|
|
120
155
|
return spawnSync(
|
|
121
156
|
isWin ? 'npx.cmd' : 'npx',
|
|
122
|
-
['-y',
|
|
157
|
+
['-y', npxSpec, ...args],
|
|
123
158
|
{ encoding: 'utf-8', timeout: timeout + 5000, cwd, stdio: ['pipe', 'pipe', 'pipe'] }
|
|
124
159
|
);
|
|
125
160
|
}
|
|
@@ -141,10 +176,11 @@ function handlePreToolUse(input) {
|
|
|
141
176
|
if (!pattern || pattern.length < 3) return;
|
|
142
177
|
|
|
143
178
|
const cliPath = resolveCliPath();
|
|
179
|
+
const npxSpec = resolveNpxSpec();
|
|
144
180
|
let result = '';
|
|
145
181
|
try {
|
|
146
|
-
const child = runGitNexusCli(cliPath, ['augment', '--', pattern], cwd, 7000);
|
|
147
|
-
if (!child.error && child.status === 0) {
|
|
182
|
+
const child = runGitNexusCli(cliPath, ['augment', '--', pattern], cwd, 7000, npxSpec);
|
|
183
|
+
if (child && !child.error && child.status === 0) {
|
|
148
184
|
result = child.stderr || '';
|
|
149
185
|
}
|
|
150
186
|
} catch { /* graceful failure */ }
|
|
@@ -166,7 +202,7 @@ function sendHookResponse(hookEventName, message) {
|
|
|
166
202
|
/**
|
|
167
203
|
* PostToolUse handler — detect index staleness after git mutations.
|
|
168
204
|
*
|
|
169
|
-
* Instead of spawning a full
|
|
205
|
+
* Instead of spawning a full analyze command synchronously (which blocks
|
|
170
206
|
* the agent for up to 120s and risks KuzuDB corruption on timeout), we do a
|
|
171
207
|
* lightweight staleness check: compare `git rev-parse HEAD` against the
|
|
172
208
|
* lastCommit stored in `.gitnexus/meta.json`. If they differ, notify the
|
|
@@ -210,10 +246,17 @@ function handlePostToolUse(input) {
|
|
|
210
246
|
// If HEAD matches last indexed commit, no reindex needed
|
|
211
247
|
if (currentHead && currentHead === lastCommit) return;
|
|
212
248
|
|
|
213
|
-
const
|
|
249
|
+
const npxSpec = resolveNpxSpec();
|
|
250
|
+
const analyzeArgs = `analyze${hadEmbeddings ? ' --embeddings' : ''}`;
|
|
251
|
+
const analyzeCmd = `gitnexus ${analyzeArgs}`;
|
|
252
|
+
const fallbackCmd = npxSpec
|
|
253
|
+
? `npx -y ${npxSpec} ${analyzeArgs}`
|
|
254
|
+
: null;
|
|
214
255
|
sendHookResponse('PostToolUse',
|
|
215
256
|
`GitNexus index is stale (last indexed: ${lastCommit ? lastCommit.slice(0, 7) : 'never'}). ` +
|
|
216
|
-
|
|
257
|
+
(fallbackCmd
|
|
258
|
+
? `Run \`${analyzeCmd}\` (or \`${fallbackCmd}\`) to update the knowledge graph.`
|
|
259
|
+
: `Run \`${analyzeCmd}\` to update the knowledge graph. If local CLI is unavailable, populate ~/.gitnexus/config.json via \`gitnexus setup --cli-spec <packageSpec>\` first.`)
|
|
217
260
|
);
|
|
218
261
|
}
|
|
219
262
|
|
|
@@ -64,7 +64,32 @@ fi
|
|
|
64
64
|
|
|
65
65
|
# Run gitnexus augment — must be fast (<500ms target)
|
|
66
66
|
# augment writes to stderr (KuzuDB captures stdout at OS level), so capture stderr and discard stdout
|
|
67
|
-
|
|
67
|
+
if command -v gitnexus >/dev/null 2>&1; then
|
|
68
|
+
RESULT=$(cd "$CWD" && gitnexus augment "$PATTERN" 2>&1 1>/dev/null)
|
|
69
|
+
else
|
|
70
|
+
if [ -n "$GITNEXUS_CLI_SPEC" ]; then
|
|
71
|
+
:
|
|
72
|
+
elif [ -n "$GITNEXUS_CLI_VERSION" ]; then
|
|
73
|
+
GITNEXUS_CLI_SPEC="@veewo/gitnexus@$GITNEXUS_CLI_VERSION"
|
|
74
|
+
elif [ -f "${HOME}/.gitnexus/config.json" ]; then
|
|
75
|
+
GITNEXUS_CLI_SPEC="$(
|
|
76
|
+
node -e 'const fs=require("fs");const os=require("os");const path=require("path");
|
|
77
|
+
try {
|
|
78
|
+
const raw=fs.readFileSync(path.join(os.homedir(),".gitnexus","config.json"),"utf8");
|
|
79
|
+
const parsed=JSON.parse(raw);
|
|
80
|
+
const spec=typeof parsed.cliPackageSpec==="string" && parsed.cliPackageSpec.trim()
|
|
81
|
+
? parsed.cliPackageSpec.trim()
|
|
82
|
+
: typeof parsed.cliVersion==="string" && parsed.cliVersion.trim()
|
|
83
|
+
? `@veewo/gitnexus@${parsed.cliVersion.trim()}`
|
|
84
|
+
: "";
|
|
85
|
+
if (spec) process.stdout.write(spec);
|
|
86
|
+
} catch {}'
|
|
87
|
+
)"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
[ -z "$GITNEXUS_CLI_SPEC" ] && exit 0
|
|
91
|
+
RESULT=$(cd "$CWD" && npx -y "$GITNEXUS_CLI_SPEC" augment "$PATTERN" 2>&1 1>/dev/null)
|
|
92
|
+
fi
|
|
68
93
|
|
|
69
94
|
if [ -n "$RESULT" ]; then
|
|
70
95
|
ESCAPED=$(echo "$RESULT" | jq -Rs .)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veewo/gitnexus",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.8-rc.2",
|
|
4
4
|
"description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
|
|
5
5
|
"author": "Abhigyan Patwari",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
package/skills/gitnexus-cli.md
CHANGED
|
@@ -5,14 +5,39 @@ description: "Use when the user needs to run GitNexus CLI commands like analyze/
|
|
|
5
5
|
|
|
6
6
|
# GitNexus CLI Commands
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Use one command alias in the session so every CLI/MCP call stays on one version line. After `setup`, treat `~/.gitnexus/config.json` as the only npx version source.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
if command -v gitnexus >/dev/null 2>&1; then
|
|
12
|
+
GN="gitnexus"
|
|
13
|
+
else
|
|
14
|
+
GITNEXUS_CLI_SPEC="$(
|
|
15
|
+
node -e 'const fs=require("fs");const os=require("os");const path=require("path");
|
|
16
|
+
try {
|
|
17
|
+
const raw=fs.readFileSync(path.join(os.homedir(),".gitnexus","config.json"),"utf8");
|
|
18
|
+
const parsed=JSON.parse(raw);
|
|
19
|
+
const spec=typeof parsed.cliPackageSpec==="string" && parsed.cliPackageSpec.trim()
|
|
20
|
+
? parsed.cliPackageSpec.trim()
|
|
21
|
+
: typeof parsed.cliVersion==="string" && parsed.cliVersion.trim()
|
|
22
|
+
? `@veewo/gitnexus@${parsed.cliVersion.trim()}`
|
|
23
|
+
: "";
|
|
24
|
+
if (spec) process.stdout.write(spec);
|
|
25
|
+
} catch {}'
|
|
26
|
+
)"
|
|
27
|
+
if [ -z "$GITNEXUS_CLI_SPEC" ]; then
|
|
28
|
+
echo "Missing GitNexus CLI package spec in ~/.gitnexus/config.json. Run gitnexus setup --cli-spec <packageSpec> first." >&2
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
GN="npx -y ${GITNEXUS_CLI_SPEC}"
|
|
32
|
+
fi
|
|
33
|
+
```
|
|
9
34
|
|
|
10
35
|
## Commands
|
|
11
36
|
|
|
12
37
|
### analyze — Build or refresh the index
|
|
13
38
|
|
|
14
39
|
```bash
|
|
15
|
-
|
|
40
|
+
$GN analyze
|
|
16
41
|
```
|
|
17
42
|
|
|
18
43
|
Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files.
|
|
@@ -27,7 +52,7 @@ Run from the project root. This parses all source files, builds the knowledge gr
|
|
|
27
52
|
### status — Check index freshness
|
|
28
53
|
|
|
29
54
|
```bash
|
|
30
|
-
|
|
55
|
+
$GN status
|
|
31
56
|
```
|
|
32
57
|
|
|
33
58
|
Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed.
|
|
@@ -35,7 +60,7 @@ Shows whether the current repo has a GitNexus index, when it was last updated, a
|
|
|
35
60
|
### clean — Delete the index
|
|
36
61
|
|
|
37
62
|
```bash
|
|
38
|
-
|
|
63
|
+
$GN clean
|
|
39
64
|
```
|
|
40
65
|
|
|
41
66
|
Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project.
|
|
@@ -48,7 +73,7 @@ Deletes the `.gitnexus/` directory and unregisters the repo from the global regi
|
|
|
48
73
|
### wiki — Generate documentation from the graph
|
|
49
74
|
|
|
50
75
|
```bash
|
|
51
|
-
|
|
76
|
+
$GN wiki
|
|
52
77
|
```
|
|
53
78
|
|
|
54
79
|
Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use).
|
|
@@ -65,7 +90,7 @@ Generates repository documentation from the knowledge graph using an LLM. Requir
|
|
|
65
90
|
### list — Show all indexed repos
|
|
66
91
|
|
|
67
92
|
```bash
|
|
68
|
-
|
|
93
|
+
$GN list
|
|
69
94
|
```
|
|
70
95
|
|
|
71
96
|
Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information.
|
|
@@ -75,11 +100,11 @@ Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_
|
|
|
75
100
|
For Unity resource retrieval:
|
|
76
101
|
|
|
77
102
|
```bash
|
|
78
|
-
|
|
103
|
+
$GN context DoorObj --repo neonnew-core --file Assets/NEON/Code/Game/Doors/DoorObj.cs --unity-resources on --unity-hydration compact
|
|
79
104
|
```
|
|
80
105
|
|
|
81
106
|
```bash
|
|
82
|
-
|
|
107
|
+
$GN query "DoorObj binding" --repo neonnew-core --unity-resources on --unity-hydration compact
|
|
83
108
|
```
|
|
84
109
|
|
|
85
110
|
Rules:
|
|
@@ -23,7 +23,7 @@ description: "Use when the user is debugging a bug, tracing an error, or asking
|
|
|
23
23
|
5. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
> If "Index is stale" → run `npx
|
|
26
|
+
> If "Index is stale" → run `gitnexus analyze` when local CLI exists; otherwise resolve the pinned npx package spec from `~/.gitnexus/config.json` and run `npx -y <resolved-cli-spec> analyze`.
|
|
27
27
|
|
|
28
28
|
## Checklist
|
|
29
29
|
|
|
@@ -24,7 +24,7 @@ description: "Use when the user asks how code works, wants to understand archite
|
|
|
24
24
|
6. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
> If step 2 says "Index is stale" → run `npx
|
|
27
|
+
> If step 2 says "Index is stale" → run `gitnexus analyze` when local CLI exists; otherwise resolve the pinned npx package spec from `~/.gitnexus/config.json` and run `npx -y <resolved-cli-spec> analyze`.
|
|
28
28
|
|
|
29
29
|
## Checklist
|
|
30
30
|
|
package/skills/gitnexus-guide.md
CHANGED
|
@@ -15,7 +15,7 @@ For any task involving code understanding, debugging, impact analysis, or refact
|
|
|
15
15
|
2. **Match your task to a skill below** and **read that skill file**
|
|
16
16
|
3. **Follow the skill's workflow and checklist**
|
|
17
17
|
|
|
18
|
-
> If step 1 warns the index is stale, run `
|
|
18
|
+
> If step 1 warns the index is stale, run `gitnexus analyze` when local CLI exists; otherwise resolve the pinned npx package spec from `~/.gitnexus/config.json` and run `npx -y <resolved-cli-spec> analyze`.
|
|
19
19
|
|
|
20
20
|
## Skills
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ description: "Use when the user wants to know what will break if they change som
|
|
|
23
23
|
4. Assess risk and report to user
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
> If "Index is stale" → run `npx
|
|
26
|
+
> If "Index is stale" → run `gitnexus analyze` when local CLI exists; otherwise resolve the pinned npx package spec from `~/.gitnexus/config.json` and run `npx -y <resolved-cli-spec> analyze`.
|
|
27
27
|
|
|
28
28
|
## Checklist
|
|
29
29
|
|
|
@@ -26,7 +26,7 @@ description: "Use when the user wants to review a pull request, understand what
|
|
|
26
26
|
6. Summarize findings with risk assessment
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
> If "Index is stale" → run `npx -y
|
|
29
|
+
> If "Index is stale" → run `gitnexus analyze` when local CLI exists; otherwise resolve the pinned npx package spec from `~/.gitnexus/config.json` and run `npx -y <resolved-cli-spec> analyze` before reviewing.
|
|
30
30
|
|
|
31
31
|
## Checklist
|
|
32
32
|
|
|
@@ -23,7 +23,7 @@ description: "Use when the user wants to rename, extract, split, move, or restru
|
|
|
23
23
|
5. Plan update order: interfaces → implementations → callers → tests
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
> If "Index is stale" → run `npx
|
|
26
|
+
> If "Index is stale" → run `gitnexus analyze` when local CLI exists; otherwise resolve the pinned npx package spec from `~/.gitnexus/config.json` and run `npx -y <resolved-cli-spec> analyze`.
|
|
27
27
|
|
|
28
28
|
## Checklists
|
|
29
29
|
|