@veewo/gitnexus 1.3.4 → 1.3.6
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 +19 -8
- package/dist/cli/ai-context.d.ts +5 -2
- package/dist/cli/ai-context.js +21 -15
- package/dist/cli/ai-context.test.js +23 -1
- package/dist/cli/analyze.js +3 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/setup.d.ts +5 -1
- package/dist/cli/setup.js +93 -10
- package/dist/cli/setup.test.js +96 -3
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/storage/repo-manager.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Graph-powered code intelligence for AI agents.** Index any codebase into a knowledge graph, then query it via MCP or CLI.
|
|
4
4
|
|
|
5
|
-
Works with **Cursor**, **Claude Code**, **Windsurf**, **Cline**, **OpenCode**, and any MCP-compatible tool.
|
|
5
|
+
Works with **Cursor**, **Claude Code**, **Codex**, **Windsurf**, **Cline**, **OpenCode**, and any MCP-compatible tool.
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/gitnexus)
|
|
8
8
|
[](https://polyformproject.org/licenses/noncommercial/1.0.0/)
|
|
@@ -22,11 +22,13 @@ AI coding tools don't understand your codebase structure. They edit a function w
|
|
|
22
22
|
npx gitnexus analyze
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
That's it. This indexes the codebase,
|
|
25
|
+
That's it. This indexes the codebase, updates `AGENTS.md` / `CLAUDE.md` context files, and (when using project scope) installs repo-local agent skills.
|
|
26
26
|
|
|
27
|
-
To configure MCP
|
|
27
|
+
To configure MCP + skills, run `npx gitnexus setup` once (default global mode), or use `npx gitnexus setup --scope project` for project-local mode.
|
|
28
28
|
|
|
29
|
-
`gitnexus setup`
|
|
29
|
+
`gitnexus setup` supports two scopes:
|
|
30
|
+
- `global` (default): configures global editor MCP + installs global skills
|
|
31
|
+
- `project`: writes repo-local `.mcp.json` + installs repo-local skills
|
|
30
32
|
|
|
31
33
|
## Team Deployment and Distribution
|
|
32
34
|
|
|
@@ -43,6 +45,7 @@ Key links:
|
|
|
43
45
|
|--------|-----|--------|---------------------|---------|
|
|
44
46
|
| **Claude Code** | Yes | Yes | Yes (PreToolUse) | **Full** |
|
|
45
47
|
| **Cursor** | Yes | Yes | — | MCP + Skills |
|
|
48
|
+
| **Codex** | Yes | Yes | — | MCP + Skills |
|
|
46
49
|
| **Windsurf** | Yes | — | — | MCP |
|
|
47
50
|
| **OpenCode** | Yes | Yes | — | MCP + Skills |
|
|
48
51
|
|
|
@@ -94,6 +97,12 @@ Add to `~/.config/opencode/config.json`:
|
|
|
94
97
|
}
|
|
95
98
|
```
|
|
96
99
|
|
|
100
|
+
### Codex
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
codex mcp add gitnexus -- npx -y gitnexus@latest mcp
|
|
104
|
+
```
|
|
105
|
+
|
|
97
106
|
## How It Works
|
|
98
107
|
|
|
99
108
|
GitNexus builds a complete knowledge graph of your codebase through a multi-phase indexing pipeline:
|
|
@@ -145,7 +154,8 @@ Your AI agent gets these tools automatically:
|
|
|
145
154
|
## CLI Commands
|
|
146
155
|
|
|
147
156
|
```bash
|
|
148
|
-
gitnexus setup #
|
|
157
|
+
gitnexus setup # Default: global MCP + global skills
|
|
158
|
+
gitnexus setup --scope project # Project-local MCP + project-local skills
|
|
149
159
|
gitnexus analyze [path] # Index a repository (or update stale index)
|
|
150
160
|
gitnexus analyze --force # Force full re-index
|
|
151
161
|
gitnexus analyze --embeddings # Enable semantic embeddings (off by default)
|
|
@@ -205,9 +215,10 @@ GitNexus ships with skill files that teach AI agents how to use the tools effect
|
|
|
205
215
|
|
|
206
216
|
Installation rules:
|
|
207
217
|
|
|
208
|
-
- `gitnexus
|
|
209
|
-
-
|
|
210
|
-
-
|
|
218
|
+
- `gitnexus setup` controls skill scope:
|
|
219
|
+
- default `global`: installs to `~/.agents/skills/gitnexus/`
|
|
220
|
+
- `--scope project`: installs to `.agents/skills/gitnexus/` in current repo
|
|
221
|
+
- `gitnexus analyze` always updates `AGENTS.md` / `CLAUDE.md`; skill install follows configured setup scope.
|
|
211
222
|
|
|
212
223
|
## Requirements
|
|
213
224
|
|
package/dist/cli/ai-context.d.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* AI Context Generator
|
|
3
3
|
*
|
|
4
4
|
* Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
|
|
5
|
-
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Cline, etc.
|
|
5
|
+
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
|
|
6
6
|
* CLAUDE.md is for Claude Code which only reads that file.
|
|
7
7
|
*/
|
|
8
|
+
type SkillScope = 'project' | 'global';
|
|
8
9
|
interface RepoStats {
|
|
9
10
|
files?: number;
|
|
10
11
|
nodes?: number;
|
|
@@ -16,7 +17,9 @@ interface RepoStats {
|
|
|
16
17
|
/**
|
|
17
18
|
* Generate AI context files after indexing
|
|
18
19
|
*/
|
|
19
|
-
export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats
|
|
20
|
+
export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats, options?: {
|
|
21
|
+
skillScope?: SkillScope;
|
|
22
|
+
}): Promise<{
|
|
20
23
|
files: string[];
|
|
21
24
|
}>;
|
|
22
25
|
export {};
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* AI Context Generator
|
|
3
3
|
*
|
|
4
4
|
* Creates AGENTS.md and CLAUDE.md with full inline GitNexus context.
|
|
5
|
-
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Cline, etc.
|
|
5
|
+
* AGENTS.md is the standard read by Cursor, Windsurf, OpenCode, Codex, Cline, etc.
|
|
6
6
|
* CLAUDE.md is for Claude Code which only reads that file.
|
|
7
7
|
*/
|
|
8
8
|
import fs from 'fs/promises';
|
|
@@ -23,7 +23,10 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
|
|
|
23
23
|
* - One-line quick start (read context resource) gives agents an entry point
|
|
24
24
|
* - Tools/Resources sections are labeled "Reference" — agents treat them as lookup, not workflow
|
|
25
25
|
*/
|
|
26
|
-
function generateGitNexusContent(projectName, stats) {
|
|
26
|
+
function generateGitNexusContent(projectName, stats, skillScope) {
|
|
27
|
+
const skillRoot = skillScope === 'global'
|
|
28
|
+
? '~/.agents/skills/gitnexus'
|
|
29
|
+
: '.agents/skills/gitnexus';
|
|
27
30
|
return `${GITNEXUS_START_MARKER}
|
|
28
31
|
# GitNexus MCP
|
|
29
32
|
|
|
@@ -41,12 +44,12 @@ This project is indexed by GitNexus as **${projectName}** (${stats.nodes || 0} s
|
|
|
41
44
|
|
|
42
45
|
| Task | Read this skill file |
|
|
43
46
|
|------|---------------------|
|
|
44
|
-
| Understand architecture / "How does X work?" |
|
|
45
|
-
| Blast radius / "What breaks if I change X?" |
|
|
46
|
-
| Trace bugs / "Why is X failing?" |
|
|
47
|
-
| Rename / extract / split / refactor |
|
|
48
|
-
| Tools, resources, schema reference |
|
|
49
|
-
| Index, status, clean, wiki CLI commands |
|
|
47
|
+
| Understand architecture / "How does X work?" | \`${skillRoot}/gitnexus-exploring/SKILL.md\` |
|
|
48
|
+
| Blast radius / "What breaks if I change X?" | \`${skillRoot}/gitnexus-impact-analysis/SKILL.md\` |
|
|
49
|
+
| Trace bugs / "Why is X failing?" | \`${skillRoot}/gitnexus-debugging/SKILL.md\` |
|
|
50
|
+
| Rename / extract / split / refactor | \`${skillRoot}/gitnexus-refactoring/SKILL.md\` |
|
|
51
|
+
| Tools, resources, schema reference | \`${skillRoot}/gitnexus-guide/SKILL.md\` |
|
|
52
|
+
| Index, status, clean, wiki CLI commands | \`${skillRoot}/gitnexus-cli/SKILL.md\` |
|
|
50
53
|
|
|
51
54
|
${GITNEXUS_END_MARKER}`;
|
|
52
55
|
}
|
|
@@ -164,10 +167,11 @@ Use GitNexus tools to accomplish this task.
|
|
|
164
167
|
/**
|
|
165
168
|
* Generate AI context files after indexing
|
|
166
169
|
*/
|
|
167
|
-
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats) {
|
|
168
|
-
const
|
|
170
|
+
export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats, options) {
|
|
171
|
+
const skillScope = options?.skillScope === 'global' ? 'global' : 'project';
|
|
172
|
+
const content = generateGitNexusContent(projectName, stats, skillScope);
|
|
169
173
|
const createdFiles = [];
|
|
170
|
-
// Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.)
|
|
174
|
+
// Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Codex, Cline, etc.)
|
|
171
175
|
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
|
172
176
|
const agentsResult = await upsertGitNexusSection(agentsPath, content);
|
|
173
177
|
createdFiles.push(`AGENTS.md (${agentsResult})`);
|
|
@@ -175,10 +179,12 @@ export async function generateAIContextFiles(repoPath, _storagePath, projectName
|
|
|
175
179
|
const claudePath = path.join(repoPath, 'CLAUDE.md');
|
|
176
180
|
const claudeResult = await upsertGitNexusSection(claudePath, content);
|
|
177
181
|
createdFiles.push(`CLAUDE.md (${claudeResult})`);
|
|
178
|
-
// Install skills
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
+
// Install repo-local skills only when project scope is selected.
|
|
183
|
+
if (skillScope === 'project') {
|
|
184
|
+
const installedSkills = await installSkills(repoPath);
|
|
185
|
+
if (installedSkills.length > 0) {
|
|
186
|
+
createdFiles.push(`.agents/skills/gitnexus/ (${installedSkills.length} skills)`);
|
|
187
|
+
}
|
|
182
188
|
}
|
|
183
189
|
return { files: createdFiles };
|
|
184
190
|
}
|
|
@@ -11,7 +11,7 @@ test('generateAIContextFiles installs repo skills under .agents/skills/gitnexus'
|
|
|
11
11
|
nodes: 1,
|
|
12
12
|
edges: 2,
|
|
13
13
|
processes: 3,
|
|
14
|
-
});
|
|
14
|
+
}, { skillScope: 'project' });
|
|
15
15
|
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
|
16
16
|
const claudePath = path.join(repoPath, 'CLAUDE.md');
|
|
17
17
|
const skillPath = path.join(repoPath, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
|
|
@@ -28,3 +28,25 @@ test('generateAIContextFiles installs repo skills under .agents/skills/gitnexus'
|
|
|
28
28
|
await fs.rm(repoPath, { recursive: true, force: true });
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
|
+
test('generateAIContextFiles with global scope skips repo skill install', async () => {
|
|
32
|
+
const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-ai-context-global-'));
|
|
33
|
+
try {
|
|
34
|
+
const result = await generateAIContextFiles(repoPath, '', 'demo-repo', {
|
|
35
|
+
nodes: 1,
|
|
36
|
+
edges: 2,
|
|
37
|
+
processes: 3,
|
|
38
|
+
}, { skillScope: 'global' });
|
|
39
|
+
const agentsPath = path.join(repoPath, 'AGENTS.md');
|
|
40
|
+
const claudePath = path.join(repoPath, 'CLAUDE.md');
|
|
41
|
+
const localSkillsDir = path.join(repoPath, '.agents', 'skills', 'gitnexus');
|
|
42
|
+
const agentsContent = await fs.readFile(agentsPath, 'utf-8');
|
|
43
|
+
const claudeContent = await fs.readFile(claudePath, 'utf-8');
|
|
44
|
+
assert.match(agentsContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
|
|
45
|
+
assert.match(claudeContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
|
|
46
|
+
assert.ok(!result.files.some((entry) => entry.includes('.agents/skills/gitnexus/')), 'did not expect repo-local skills in generated file summary');
|
|
47
|
+
await assert.rejects(fs.access(localSkillsDir));
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await fs.rm(repoPath, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
});
|
package/dist/cli/analyze.js
CHANGED
|
@@ -13,7 +13,7 @@ import { initKuzu, loadGraphToKuzu, getKuzuStats, executeQuery, executeWithReuse
|
|
|
13
13
|
// loaded when embeddings are not requested. This avoids crashes on Node
|
|
14
14
|
// versions whose ABI is not yet supported by the native binary (#89).
|
|
15
15
|
// disposeEmbedder intentionally not called — ONNX Runtime segfaults on cleanup (see #38)
|
|
16
|
-
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath } from '../storage/repo-manager.js';
|
|
16
|
+
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, getGlobalRegistryPath, loadCLIConfig } from '../storage/repo-manager.js';
|
|
17
17
|
import { getCurrentCommit, isGitRepo, getGitRoot } from '../storage/git.js';
|
|
18
18
|
import { generateAIContextFiles } from './ai-context.js';
|
|
19
19
|
import fs from 'fs/promises';
|
|
@@ -312,6 +312,8 @@ export const analyzeCommand = async (inputPath, options) => {
|
|
|
312
312
|
communities: pipelineResult.communityResult?.stats.totalCommunities,
|
|
313
313
|
clusters: aggregatedClusterCount,
|
|
314
314
|
processes: pipelineResult.processResult?.stats.totalProcesses,
|
|
315
|
+
}, {
|
|
316
|
+
skillScope: ((await loadCLIConfig()).setupScope === 'global') ? 'global' : 'project',
|
|
315
317
|
});
|
|
316
318
|
await closeKuzu();
|
|
317
319
|
// Note: we intentionally do NOT call disposeEmbedder() here.
|
package/dist/cli/index.js
CHANGED
|
@@ -59,7 +59,8 @@ program
|
|
|
59
59
|
.version(resolveCliVersion());
|
|
60
60
|
program
|
|
61
61
|
.command('setup')
|
|
62
|
-
.description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode')
|
|
62
|
+
.description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode, Codex')
|
|
63
|
+
.option('--scope <scope>', 'Install target: global (default) or project')
|
|
63
64
|
.action(setupCommand);
|
|
64
65
|
program
|
|
65
66
|
.command('analyze [path]')
|
package/dist/cli/setup.d.ts
CHANGED
|
@@ -5,4 +5,8 @@
|
|
|
5
5
|
* Detects installed AI editors and writes the appropriate MCP config
|
|
6
6
|
* so the GitNexus MCP server is available in all projects.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
interface SetupOptions {
|
|
9
|
+
scope?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const setupCommand: (options?: SetupOptions) => Promise<void>;
|
|
12
|
+
export {};
|
package/dist/cli/setup.js
CHANGED
|
@@ -8,10 +8,21 @@
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import os from 'os';
|
|
11
|
+
import { execFile } from 'node:child_process';
|
|
12
|
+
import { promisify } from 'node:util';
|
|
11
13
|
import { fileURLToPath } from 'url';
|
|
12
|
-
import { getGlobalDir } from '../storage/repo-manager.js';
|
|
14
|
+
import { getGlobalDir, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
|
|
15
|
+
import { getGitRoot } from '../storage/git.js';
|
|
13
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
17
|
const __dirname = path.dirname(__filename);
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
function resolveSetupScope(rawScope) {
|
|
20
|
+
if (!rawScope || rawScope.trim() === '')
|
|
21
|
+
return 'global';
|
|
22
|
+
if (rawScope === 'global' || rawScope === 'project')
|
|
23
|
+
return rawScope;
|
|
24
|
+
throw new Error(`Invalid --scope value "${rawScope}". Use "global" or "project".`);
|
|
25
|
+
}
|
|
15
26
|
/**
|
|
16
27
|
* The MCP server entry for all editors.
|
|
17
28
|
* On Windows, npx must be invoked via cmd /c since it's a .cmd script.
|
|
@@ -122,6 +133,18 @@ async function installGlobalAgentSkills(result) {
|
|
|
122
133
|
result.errors.push(`Global agent skills: ${err.message}`);
|
|
123
134
|
}
|
|
124
135
|
}
|
|
136
|
+
async function installProjectAgentSkills(repoRoot, result) {
|
|
137
|
+
const skillsDir = path.join(repoRoot, '.agents', 'skills', 'gitnexus');
|
|
138
|
+
try {
|
|
139
|
+
const installed = await installSkillsTo(skillsDir);
|
|
140
|
+
if (installed.length > 0) {
|
|
141
|
+
result.configured.push(`Project agent skills (${installed.length} skills → ${path.relative(repoRoot, skillsDir)}/)`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
result.errors.push(`Project agent skills: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
125
148
|
/**
|
|
126
149
|
* Install GitNexus hooks to ~/.claude/settings.json for Claude Code.
|
|
127
150
|
* Merges hook config without overwriting existing hooks.
|
|
@@ -195,6 +218,42 @@ async function setupOpenCode(result) {
|
|
|
195
218
|
result.errors.push(`OpenCode: ${err.message}`);
|
|
196
219
|
}
|
|
197
220
|
}
|
|
221
|
+
async function setupCodex(result) {
|
|
222
|
+
const entry = getMcpEntry();
|
|
223
|
+
try {
|
|
224
|
+
await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], { timeout: 15000 });
|
|
225
|
+
result.configured.push('Codex');
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
if (err?.code === 'ENOENT') {
|
|
229
|
+
result.skipped.push('Codex (not installed)');
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
result.errors.push(`Codex: ${err.message}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async function setupProjectMcp(repoRoot, result) {
|
|
236
|
+
const mcpPath = path.join(repoRoot, '.mcp.json');
|
|
237
|
+
try {
|
|
238
|
+
const existing = await readJsonFile(mcpPath);
|
|
239
|
+
const updated = mergeMcpConfig(existing);
|
|
240
|
+
await writeJsonFile(mcpPath, updated);
|
|
241
|
+
result.configured.push(`Project MCP (${path.relative(repoRoot, mcpPath)})`);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
result.errors.push(`Project MCP: ${err.message}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
async function saveSetupScope(scope, result) {
|
|
248
|
+
try {
|
|
249
|
+
const existing = await loadCLIConfig();
|
|
250
|
+
await saveCLIConfig({ ...existing, setupScope: scope });
|
|
251
|
+
result.configured.push(`Default setup scope (${scope})`);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
result.errors.push(`Persist setup scope: ${err.message}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
198
257
|
// ─── Skill Installation ───────────────────────────────────────────
|
|
199
258
|
const SKILL_NAMES = ['gitnexus-exploring', 'gitnexus-debugging', 'gitnexus-impact-analysis', 'gitnexus-refactoring', 'gitnexus-guide', 'gitnexus-cli'];
|
|
200
259
|
/**
|
|
@@ -256,11 +315,20 @@ async function copyDirRecursive(src, dest) {
|
|
|
256
315
|
}
|
|
257
316
|
}
|
|
258
317
|
// ─── Main command ──────────────────────────────────────────────────
|
|
259
|
-
export const setupCommand = async () => {
|
|
318
|
+
export const setupCommand = async (options = {}) => {
|
|
260
319
|
console.log('');
|
|
261
320
|
console.log(' GitNexus Setup');
|
|
262
321
|
console.log(' ==============');
|
|
263
322
|
console.log('');
|
|
323
|
+
let scope;
|
|
324
|
+
try {
|
|
325
|
+
scope = resolveSetupScope(options.scope);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
console.log(` ${err?.message || String(err)}\n`);
|
|
329
|
+
process.exitCode = 1;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
264
332
|
// Ensure global directory exists
|
|
265
333
|
const globalDir = getGlobalDir();
|
|
266
334
|
await fs.mkdir(globalDir, { recursive: true });
|
|
@@ -269,14 +337,28 @@ export const setupCommand = async () => {
|
|
|
269
337
|
skipped: [],
|
|
270
338
|
errors: [],
|
|
271
339
|
};
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
340
|
+
if (scope === 'global') {
|
|
341
|
+
// Detect and configure each editor's MCP
|
|
342
|
+
await setupCursor(result);
|
|
343
|
+
await setupClaudeCode(result);
|
|
344
|
+
await setupOpenCode(result);
|
|
345
|
+
await setupCodex(result);
|
|
346
|
+
// Install shared global skills once
|
|
347
|
+
await installGlobalAgentSkills(result);
|
|
348
|
+
// Optional Claude-specific hooks
|
|
349
|
+
await installClaudeCodeHooks(result);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
const repoRoot = getGitRoot(process.cwd());
|
|
353
|
+
if (!repoRoot) {
|
|
354
|
+
console.log(' --scope project requires running inside a git repository\n');
|
|
355
|
+
process.exitCode = 1;
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
await setupProjectMcp(repoRoot, result);
|
|
359
|
+
await installProjectAgentSkills(repoRoot, result);
|
|
360
|
+
}
|
|
361
|
+
await saveSetupScope(scope, result);
|
|
280
362
|
// Print results
|
|
281
363
|
if (result.configured.length > 0) {
|
|
282
364
|
console.log(' Configured:');
|
|
@@ -300,6 +382,7 @@ export const setupCommand = async () => {
|
|
|
300
382
|
}
|
|
301
383
|
console.log('');
|
|
302
384
|
console.log(' Summary:');
|
|
385
|
+
console.log(` Scope: ${scope}`);
|
|
303
386
|
console.log(` MCP configured for: ${result.configured.filter(c => !c.includes('skills')).join(', ') || 'none'}`);
|
|
304
387
|
console.log(` Skills installed to: ${result.configured.filter(c => c.includes('skills')).length > 0 ? result.configured.filter(c => c.includes('skills')).join(', ') : 'none'}`);
|
|
305
388
|
console.log('');
|
package/dist/cli/setup.test.js
CHANGED
|
@@ -7,11 +7,11 @@ import { execFile } from 'node:child_process';
|
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const packageRoot = path.resolve(here, '..', '..');
|
|
12
|
+
const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
|
|
10
13
|
test('setup installs global skills under ~/.agents/skills/gitnexus', async () => {
|
|
11
14
|
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
12
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const packageRoot = path.resolve(here, '..', '..');
|
|
14
|
-
const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
|
|
15
15
|
try {
|
|
16
16
|
await execFileAsync(process.execPath, [cliPath, 'setup'], {
|
|
17
17
|
cwd: packageRoot,
|
|
@@ -22,10 +22,103 @@ test('setup installs global skills under ~/.agents/skills/gitnexus', async () =>
|
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
const skillPath = path.join(fakeHome, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
|
|
25
|
+
const configPath = path.join(fakeHome, '.gitnexus', 'config.json');
|
|
25
26
|
await fs.access(skillPath);
|
|
27
|
+
const configRaw = await fs.readFile(configPath, 'utf-8');
|
|
28
|
+
const config = JSON.parse(configRaw);
|
|
29
|
+
assert.equal(config.setupScope, 'global');
|
|
26
30
|
assert.ok(true);
|
|
27
31
|
}
|
|
28
32
|
finally {
|
|
29
33
|
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
30
34
|
}
|
|
31
35
|
});
|
|
36
|
+
test('setup configures Codex MCP when codex CLI is available', async () => {
|
|
37
|
+
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
38
|
+
const fakeBin = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-bin-'));
|
|
39
|
+
const codexShimPath = path.join(fakeBin, process.platform === 'win32' ? 'codex.cmd' : 'codex');
|
|
40
|
+
const shimLogic = `
|
|
41
|
+
const fs = require('node:fs');
|
|
42
|
+
const path = require('node:path');
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
if (args[0] === 'mcp' && args[1] === 'add' && args[2] === 'gitnexus') {
|
|
45
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
46
|
+
const outputPath = path.join(home, '.codex', 'gitnexus-mcp-add.json');
|
|
47
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
48
|
+
fs.writeFileSync(outputPath, JSON.stringify({ args }, null, 2));
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
if (args[0] === '--version') {
|
|
52
|
+
process.stdout.write('codex-shim 0.0.0\\n');
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
process.exit(0);
|
|
56
|
+
`;
|
|
57
|
+
try {
|
|
58
|
+
if (process.platform === 'win32') {
|
|
59
|
+
const runnerPath = path.join(fakeBin, 'codex-shim.cjs');
|
|
60
|
+
await fs.writeFile(runnerPath, shimLogic, 'utf-8');
|
|
61
|
+
await fs.writeFile(codexShimPath, `@echo off\r\nnode "${runnerPath}" %*\r\n`, 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await fs.writeFile(codexShimPath, `#!/usr/bin/env node\n${shimLogic}`, { mode: 0o755 });
|
|
65
|
+
}
|
|
66
|
+
await execFileAsync(process.execPath, [cliPath, 'setup'], {
|
|
67
|
+
cwd: packageRoot,
|
|
68
|
+
env: {
|
|
69
|
+
...process.env,
|
|
70
|
+
HOME: fakeHome,
|
|
71
|
+
USERPROFILE: fakeHome,
|
|
72
|
+
PATH: `${fakeBin}${path.delimiter}${process.env.PATH || ''}`,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
const outputPath = path.join(fakeHome, '.codex', 'gitnexus-mcp-add.json');
|
|
76
|
+
const raw = await fs.readFile(outputPath, 'utf-8');
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
assert.deepEqual(parsed.args.slice(0, 4), ['mcp', 'add', 'gitnexus', '--']);
|
|
79
|
+
assert.ok(parsed.args.includes('gitnexus@latest'));
|
|
80
|
+
assert.ok(parsed.args.includes('mcp'));
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
84
|
+
await fs.rm(fakeBin, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
test('setup with --scope project writes local MCP and repo-local skills only', async () => {
|
|
88
|
+
const fakeHome = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-home-'));
|
|
89
|
+
const fakeRepo = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-setup-repo-'));
|
|
90
|
+
try {
|
|
91
|
+
await execFileAsync('git', ['init'], {
|
|
92
|
+
cwd: fakeRepo,
|
|
93
|
+
env: {
|
|
94
|
+
...process.env,
|
|
95
|
+
HOME: fakeHome,
|
|
96
|
+
USERPROFILE: fakeHome,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
await execFileAsync(process.execPath, [cliPath, 'setup', '--scope', 'project'], {
|
|
100
|
+
cwd: fakeRepo,
|
|
101
|
+
env: {
|
|
102
|
+
...process.env,
|
|
103
|
+
HOME: fakeHome,
|
|
104
|
+
USERPROFILE: fakeHome,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
const projectMcpPath = path.join(fakeRepo, '.mcp.json');
|
|
108
|
+
const localSkillPath = path.join(fakeRepo, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
|
|
109
|
+
const globalSkillPath = path.join(fakeHome, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
|
|
110
|
+
const configPath = path.join(fakeHome, '.gitnexus', 'config.json');
|
|
111
|
+
const projectMcpRaw = await fs.readFile(projectMcpPath, 'utf-8');
|
|
112
|
+
const projectMcp = JSON.parse(projectMcpRaw);
|
|
113
|
+
const configRaw = await fs.readFile(configPath, 'utf-8');
|
|
114
|
+
const config = JSON.parse(configRaw);
|
|
115
|
+
assert.equal(projectMcp.mcpServers?.gitnexus?.command, 'npx');
|
|
116
|
+
await fs.access(localSkillPath);
|
|
117
|
+
await assert.rejects(fs.access(globalSkillPath));
|
|
118
|
+
assert.equal(config.setupScope, 'project');
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
await fs.rm(fakeHome, { recursive: true, force: true });
|
|
122
|
+
await fs.rm(fakeRepo, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
});
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MCP Server (Multi-Repo)
|
|
3
3
|
*
|
|
4
4
|
* Model Context Protocol server that runs on stdio.
|
|
5
|
-
* External AI tools (Cursor, Claude) spawn this process and
|
|
5
|
+
* External AI tools (Cursor, Claude, Codex, etc.) spawn this process and
|
|
6
6
|
* communicate via stdin/stdout using the MCP protocol.
|
|
7
7
|
*
|
|
8
8
|
* Supports multiple indexed repositories via the global registry.
|
package/dist/mcp/server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* MCP Server (Multi-Repo)
|
|
3
3
|
*
|
|
4
4
|
* Model Context Protocol server that runs on stdio.
|
|
5
|
-
* External AI tools (Cursor, Claude) spawn this process and
|
|
5
|
+
* External AI tools (Cursor, Claude, Codex, etc.) spawn this process and
|
|
6
6
|
* communicate via stdin/stdout using the MCP protocol.
|
|
7
7
|
*
|
|
8
8
|
* Supports multiple indexed repositories via the global registry.
|
package/package.json
CHANGED