gitnexus 1.4.10 → 1.5.0
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 +6 -5
- package/dist/cli/ai-context.d.ts +4 -1
- package/dist/cli/ai-context.js +19 -11
- package/dist/cli/analyze.d.ts +6 -0
- package/dist/cli/analyze.js +105 -251
- package/dist/cli/eval-server.js +20 -11
- package/dist/cli/index-repo.js +20 -22
- package/dist/cli/index.js +8 -7
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/serve.js +29 -1
- package/dist/cli/setup.js +9 -9
- package/dist/cli/skill-gen.js +15 -9
- package/dist/cli/wiki.d.ts +2 -0
- package/dist/cli/wiki.js +141 -26
- package/dist/config/ignore-service.js +102 -22
- package/dist/config/supported-languages.d.ts +8 -42
- package/dist/config/supported-languages.js +8 -43
- package/dist/core/augmentation/engine.js +19 -7
- package/dist/core/embeddings/embedder.js +19 -15
- package/dist/core/embeddings/embedding-pipeline.js +6 -6
- package/dist/core/embeddings/http-client.js +3 -3
- package/dist/core/embeddings/text-generator.js +9 -24
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/embeddings/types.js +1 -7
- package/dist/core/graph/graph.js +6 -2
- package/dist/core/graph/types.d.ts +9 -59
- package/dist/core/ingestion/ast-cache.js +3 -3
- package/dist/core/ingestion/call-processor.d.ts +20 -2
- package/dist/core/ingestion/call-processor.js +347 -144
- package/dist/core/ingestion/call-routing.js +10 -4
- package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +10 -0
- package/dist/core/ingestion/call-sites/extract-language-call-site.js +22 -0
- package/dist/core/ingestion/call-sites/java.d.ts +9 -0
- package/dist/core/ingestion/call-sites/java.js +30 -0
- package/dist/core/ingestion/cluster-enricher.js +6 -8
- package/dist/core/ingestion/cobol/cobol-copy-expander.js +10 -3
- package/dist/core/ingestion/cobol/cobol-preprocessor.js +287 -81
- package/dist/core/ingestion/cobol/jcl-parser.js +1 -1
- package/dist/core/ingestion/cobol/jcl-processor.js +1 -1
- package/dist/core/ingestion/cobol-processor.js +102 -56
- package/dist/core/ingestion/community-processor.js +21 -15
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -1
- package/dist/core/ingestion/entry-point-scoring.js +5 -6
- package/dist/core/ingestion/export-detection.js +32 -9
- package/dist/core/ingestion/field-extractor.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/configs/c-cpp.js +8 -12
- package/dist/core/ingestion/field-extractors/configs/csharp.js +45 -2
- package/dist/core/ingestion/field-extractors/configs/dart.js +5 -3
- package/dist/core/ingestion/field-extractors/configs/go.js +3 -7
- package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -0
- package/dist/core/ingestion/field-extractors/configs/helpers.js +14 -0
- package/dist/core/ingestion/field-extractors/configs/jvm.js +7 -7
- package/dist/core/ingestion/field-extractors/configs/php.js +9 -11
- package/dist/core/ingestion/field-extractors/configs/python.js +1 -1
- package/dist/core/ingestion/field-extractors/configs/ruby.js +4 -3
- package/dist/core/ingestion/field-extractors/configs/rust.js +2 -5
- package/dist/core/ingestion/field-extractors/configs/swift.js +9 -7
- package/dist/core/ingestion/field-extractors/configs/typescript-javascript.js +2 -6
- package/dist/core/ingestion/field-extractors/generic.d.ts +5 -2
- package/dist/core/ingestion/field-extractors/generic.js +6 -0
- package/dist/core/ingestion/field-extractors/typescript.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/typescript.js +1 -1
- package/dist/core/ingestion/field-types.d.ts +4 -2
- package/dist/core/ingestion/filesystem-walker.js +3 -3
- package/dist/core/ingestion/framework-detection.d.ts +1 -1
- package/dist/core/ingestion/framework-detection.js +355 -85
- package/dist/core/ingestion/heritage-processor.d.ts +24 -0
- package/dist/core/ingestion/heritage-processor.js +99 -8
- package/dist/core/ingestion/import-processor.js +44 -15
- package/dist/core/ingestion/import-resolvers/csharp.js +7 -3
- package/dist/core/ingestion/import-resolvers/dart.js +1 -1
- package/dist/core/ingestion/import-resolvers/go.js +4 -2
- package/dist/core/ingestion/import-resolvers/jvm.js +4 -4
- package/dist/core/ingestion/import-resolvers/php.js +4 -4
- package/dist/core/ingestion/import-resolvers/python.js +1 -1
- package/dist/core/ingestion/import-resolvers/rust.js +9 -3
- package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
- package/dist/core/ingestion/import-resolvers/standard.js +6 -5
- package/dist/core/ingestion/import-resolvers/swift.js +2 -1
- package/dist/core/ingestion/import-resolvers/utils.js +26 -7
- package/dist/core/ingestion/language-config.js +5 -4
- package/dist/core/ingestion/language-provider.d.ts +7 -2
- package/dist/core/ingestion/languages/c-cpp.js +106 -21
- package/dist/core/ingestion/languages/cobol.js +1 -1
- package/dist/core/ingestion/languages/csharp.js +96 -19
- package/dist/core/ingestion/languages/dart.js +23 -7
- package/dist/core/ingestion/languages/go.js +1 -1
- package/dist/core/ingestion/languages/index.d.ts +1 -1
- package/dist/core/ingestion/languages/index.js +2 -3
- package/dist/core/ingestion/languages/java.js +4 -1
- package/dist/core/ingestion/languages/kotlin.js +60 -13
- package/dist/core/ingestion/languages/php.js +102 -25
- package/dist/core/ingestion/languages/python.js +28 -5
- package/dist/core/ingestion/languages/ruby.js +56 -14
- package/dist/core/ingestion/languages/rust.js +55 -11
- package/dist/core/ingestion/languages/swift.js +112 -27
- package/dist/core/ingestion/languages/typescript.js +95 -19
- package/dist/core/ingestion/markdown-processor.js +5 -5
- package/dist/core/ingestion/method-extractors/configs/csharp.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/csharp.js +283 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.d.ts +3 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +326 -0
- package/dist/core/ingestion/method-extractors/generic.d.ts +5 -0
- package/dist/core/ingestion/method-extractors/generic.js +137 -0
- package/dist/core/ingestion/method-types.d.ts +61 -0
- package/dist/core/ingestion/method-types.js +2 -0
- package/dist/core/ingestion/mro-processor.d.ts +1 -1
- package/dist/core/ingestion/mro-processor.js +12 -8
- package/dist/core/ingestion/named-binding-processor.js +2 -2
- package/dist/core/ingestion/named-bindings/rust.js +3 -1
- package/dist/core/ingestion/parsing-processor.js +74 -24
- package/dist/core/ingestion/pipeline.d.ts +2 -1
- package/dist/core/ingestion/pipeline.js +208 -102
- package/dist/core/ingestion/process-processor.js +12 -10
- package/dist/core/ingestion/resolution-context.js +3 -3
- package/dist/core/ingestion/route-extractors/middleware.js +31 -7
- package/dist/core/ingestion/route-extractors/php.js +2 -1
- package/dist/core/ingestion/route-extractors/response-shapes.js +8 -4
- package/dist/core/ingestion/structure-processor.d.ts +1 -1
- package/dist/core/ingestion/structure-processor.js +4 -4
- package/dist/core/ingestion/symbol-table.d.ts +1 -1
- package/dist/core/ingestion/symbol-table.js +22 -6
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +1 -1
- package/dist/core/ingestion/type-env.d.ts +2 -2
- package/dist/core/ingestion/type-env.js +75 -50
- package/dist/core/ingestion/type-extractors/c-cpp.js +33 -30
- package/dist/core/ingestion/type-extractors/csharp.js +24 -14
- package/dist/core/ingestion/type-extractors/dart.js +6 -8
- package/dist/core/ingestion/type-extractors/go.js +7 -6
- package/dist/core/ingestion/type-extractors/jvm.js +10 -21
- package/dist/core/ingestion/type-extractors/php.js +26 -13
- package/dist/core/ingestion/type-extractors/python.js +11 -15
- package/dist/core/ingestion/type-extractors/ruby.js +8 -3
- package/dist/core/ingestion/type-extractors/rust.js +6 -8
- package/dist/core/ingestion/type-extractors/shared.js +134 -50
- package/dist/core/ingestion/type-extractors/swift.js +16 -13
- package/dist/core/ingestion/type-extractors/typescript.js +23 -15
- package/dist/core/ingestion/utils/ast-helpers.d.ts +8 -8
- package/dist/core/ingestion/utils/ast-helpers.js +72 -35
- package/dist/core/ingestion/utils/call-analysis.d.ts +2 -0
- package/dist/core/ingestion/utils/call-analysis.js +96 -49
- package/dist/core/ingestion/utils/event-loop.js +1 -1
- package/dist/core/ingestion/workers/parse-worker.d.ts +7 -2
- package/dist/core/ingestion/workers/parse-worker.js +364 -84
- package/dist/core/ingestion/workers/worker-pool.js +5 -10
- package/dist/core/lbug/csv-generator.js +54 -15
- package/dist/core/lbug/lbug-adapter.d.ts +5 -0
- package/dist/core/lbug/lbug-adapter.js +86 -23
- package/dist/core/lbug/schema.d.ts +3 -6
- package/dist/core/lbug/schema.js +6 -30
- package/dist/core/run-analyze.d.ts +49 -0
- package/dist/core/run-analyze.js +257 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +1 -1
- package/dist/core/tree-sitter/parser-loader.js +1 -1
- package/dist/core/wiki/cursor-client.js +2 -7
- package/dist/core/wiki/generator.js +38 -23
- package/dist/core/wiki/graph-queries.js +10 -10
- package/dist/core/wiki/html-viewer.js +7 -3
- package/dist/core/wiki/llm-client.d.ts +23 -2
- package/dist/core/wiki/llm-client.js +96 -26
- package/dist/core/wiki/prompts.js +7 -6
- package/dist/mcp/core/embedder.js +1 -1
- package/dist/mcp/core/lbug-adapter.d.ts +4 -1
- package/dist/mcp/core/lbug-adapter.js +17 -7
- package/dist/mcp/local/local-backend.js +247 -95
- package/dist/mcp/resources.js +14 -6
- package/dist/mcp/server.js +13 -5
- package/dist/mcp/staleness.js +5 -1
- package/dist/mcp/tools.js +100 -23
- package/dist/server/analyze-job.d.ts +53 -0
- package/dist/server/analyze-job.js +146 -0
- package/dist/server/analyze-worker.d.ts +13 -0
- package/dist/server/analyze-worker.js +59 -0
- package/dist/server/api.js +795 -44
- package/dist/server/git-clone.d.ts +25 -0
- package/dist/server/git-clone.js +91 -0
- package/dist/storage/git.js +1 -3
- package/dist/storage/repo-manager.d.ts +5 -2
- package/dist/storage/repo-manager.js +4 -4
- package/dist/types/pipeline.d.ts +1 -21
- package/dist/types/pipeline.js +1 -18
- package/hooks/claude/gitnexus-hook.cjs +52 -22
- package/package.json +3 -2
- package/dist/core/ingestion/utils/language-detection.d.ts +0 -9
- package/dist/core/ingestion/utils/language-detection.js +0 -70
package/dist/cli/index-repo.js
CHANGED
|
@@ -8,25 +8,23 @@
|
|
|
8
8
|
* cloning a repo that ships its index, restoring from backup, or using a
|
|
9
9
|
* shared team index).
|
|
10
10
|
*/
|
|
11
|
-
import path from
|
|
12
|
-
import fs from
|
|
13
|
-
import { getStoragePaths, loadMeta, addToGitignore, registerRepo, } from
|
|
14
|
-
import { getGitRoot, isGitRepo } from
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs/promises';
|
|
13
|
+
import { getStoragePaths, loadMeta, addToGitignore, registerRepo, } from '../storage/repo-manager.js';
|
|
14
|
+
import { getGitRoot, isGitRepo } from '../storage/git.js';
|
|
15
15
|
export const indexCommand = async (inputPathParts, options) => {
|
|
16
|
-
console.log(
|
|
17
|
-
const inputPath = inputPathParts?.length
|
|
18
|
-
? inputPathParts.join(" ")
|
|
19
|
-
: undefined;
|
|
16
|
+
console.log('\n GitNexus Index\n');
|
|
17
|
+
const inputPath = inputPathParts?.length ? inputPathParts.join(' ') : undefined;
|
|
20
18
|
if (inputPathParts && inputPathParts.length > 1) {
|
|
21
19
|
const resolvedCombinedPath = path.resolve(inputPath);
|
|
22
20
|
try {
|
|
23
21
|
await fs.access(resolvedCombinedPath);
|
|
24
22
|
}
|
|
25
23
|
catch {
|
|
26
|
-
console.log(
|
|
27
|
-
console.log(
|
|
28
|
-
console.log(` Received multiple path parts: ${inputPathParts.join(
|
|
29
|
-
console.log(
|
|
24
|
+
console.log(' The `index` command accepts a single path only.');
|
|
25
|
+
console.log(' If your path contains spaces, wrap it in quotes.');
|
|
26
|
+
console.log(` Received multiple path parts: ${inputPathParts.join(', ')}`);
|
|
27
|
+
console.log('');
|
|
30
28
|
process.exitCode = 1;
|
|
31
29
|
return;
|
|
32
30
|
}
|
|
@@ -38,7 +36,7 @@ export const indexCommand = async (inputPathParts, options) => {
|
|
|
38
36
|
else {
|
|
39
37
|
const gitRoot = getGitRoot(process.cwd());
|
|
40
38
|
if (!gitRoot) {
|
|
41
|
-
console.log(
|
|
39
|
+
console.log(' Not inside a git repository, try to run git init\n');
|
|
42
40
|
process.exitCode = 1;
|
|
43
41
|
return;
|
|
44
42
|
}
|
|
@@ -46,8 +44,8 @@ export const indexCommand = async (inputPathParts, options) => {
|
|
|
46
44
|
}
|
|
47
45
|
if (!options?.allowNonGit && !isGitRepo(repoPath)) {
|
|
48
46
|
console.log(` Not a git repository: ${repoPath}`);
|
|
49
|
-
console.log(
|
|
50
|
-
console.log(
|
|
47
|
+
console.log(' Initialize one with `git init` or choose a valid repo path.\n');
|
|
48
|
+
console.log(' Or use --allow-non-git to register an existing .gitnexus index anyway.\n');
|
|
51
49
|
process.exitCode = 1;
|
|
52
50
|
return;
|
|
53
51
|
}
|
|
@@ -58,7 +56,7 @@ export const indexCommand = async (inputPathParts, options) => {
|
|
|
58
56
|
}
|
|
59
57
|
catch {
|
|
60
58
|
console.log(` No .gitnexus/ folder found at: ${storagePath}`);
|
|
61
|
-
console.log(
|
|
59
|
+
console.log(' Run `gitnexus analyze` to build the index first.\n');
|
|
62
60
|
process.exitCode = 1;
|
|
63
61
|
return;
|
|
64
62
|
}
|
|
@@ -68,7 +66,7 @@ export const indexCommand = async (inputPathParts, options) => {
|
|
|
68
66
|
}
|
|
69
67
|
catch {
|
|
70
68
|
console.log(` .gitnexus/ folder exists but contains no LadybugDB index.`);
|
|
71
|
-
console.log(
|
|
69
|
+
console.log(' Run `gitnexus analyze` to build the index.\n');
|
|
72
70
|
process.exitCode = 1;
|
|
73
71
|
return;
|
|
74
72
|
}
|
|
@@ -77,15 +75,15 @@ export const indexCommand = async (inputPathParts, options) => {
|
|
|
77
75
|
if (!meta) {
|
|
78
76
|
if (!options?.force) {
|
|
79
77
|
console.log(` .gitnexus/ exists but meta.json is missing.`);
|
|
80
|
-
console.log(
|
|
81
|
-
console.log(
|
|
78
|
+
console.log(' Use --force to register anyway (stats will be empty),');
|
|
79
|
+
console.log(' or run `gitnexus analyze` to rebuild properly.\n');
|
|
82
80
|
process.exitCode = 1;
|
|
83
81
|
return;
|
|
84
82
|
}
|
|
85
83
|
// --force: build a minimal meta so the repo can be registered
|
|
86
84
|
meta = {
|
|
87
85
|
repoPath,
|
|
88
|
-
lastCommit:
|
|
86
|
+
lastCommit: '',
|
|
89
87
|
indexedAt: new Date().toISOString(),
|
|
90
88
|
};
|
|
91
89
|
}
|
|
@@ -108,8 +106,8 @@ export const indexCommand = async (inputPathParts, options) => {
|
|
|
108
106
|
if (stats.processes != null)
|
|
109
107
|
parts.push(`${stats.processes} flows`);
|
|
110
108
|
if (parts.length)
|
|
111
|
-
console.log(` ${parts.join(
|
|
109
|
+
console.log(` ${parts.join(' | ')}`);
|
|
112
110
|
}
|
|
113
111
|
console.log(` ${repoPath}`);
|
|
114
|
-
console.log(
|
|
112
|
+
console.log('');
|
|
115
113
|
};
|
package/dist/cli/index.js
CHANGED
|
@@ -7,10 +7,7 @@ import { createLazyAction } from './lazy-action.js';
|
|
|
7
7
|
const _require = createRequire(import.meta.url);
|
|
8
8
|
const pkg = _require('../../package.json');
|
|
9
9
|
const program = new Command();
|
|
10
|
-
program
|
|
11
|
-
.name('gitnexus')
|
|
12
|
-
.description('GitNexus local CLI and MCP server')
|
|
13
|
-
.version(pkg.version);
|
|
10
|
+
program.name('gitnexus').description('GitNexus local CLI and MCP server').version(pkg.version);
|
|
14
11
|
program
|
|
15
12
|
.command('setup')
|
|
16
13
|
.description('One-time setup: configure MCP for Cursor, Claude Code, OpenCode, Codex')
|
|
@@ -21,6 +18,7 @@ program
|
|
|
21
18
|
.option('-f, --force', 'Force full re-index even if up to date')
|
|
22
19
|
.option('--embeddings', 'Enable embedding generation for semantic search (off by default)')
|
|
23
20
|
.option('--skills', 'Generate repo-specific skill files from detected communities')
|
|
21
|
+
.option('--skip-agents-md', 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md')
|
|
24
22
|
.option('--skip-git', 'Index a folder without requiring a .git directory')
|
|
25
23
|
.option('-v, --verbose', 'Enable verbose ingestion warnings (default: false)')
|
|
26
24
|
.addHelpText('after', '\nEnvironment variables:\n GITNEXUS_NO_GITIGNORE=1 Skip .gitignore parsing (still reads .gitnexusignore)')
|
|
@@ -60,9 +58,12 @@ program
|
|
|
60
58
|
.description('Generate repository wiki from knowledge graph')
|
|
61
59
|
.option('-f, --force', 'Force full regeneration even if up to date')
|
|
62
60
|
.option('--provider <provider>', 'LLM provider: openai or cursor (default: openai)')
|
|
63
|
-
.option('--model <model>', 'LLM model name (default
|
|
64
|
-
.option('--base-url <url>', 'LLM API base URL
|
|
65
|
-
.option('--api-key <key>', 'LLM API key (saved to ~/.gitnexus/config.json)')
|
|
61
|
+
.option('--model <model>', 'LLM model or Azure deployment name (default: minimax/minimax-m2.5)')
|
|
62
|
+
.option('--base-url <url>', 'LLM API base URL. Azure v1: https://{resource}.openai.azure.com/openai/v1')
|
|
63
|
+
.option('--api-key <key>', 'LLM API key or Azure api-key (saved to ~/.gitnexus/config.json)')
|
|
64
|
+
.option('--api-version <version>', 'Azure api-version query param, e.g. 2024-10-21 (legacy Azure API only)')
|
|
65
|
+
.option('--reasoning-model', 'Mark deployment as reasoning model (o1/o3/o4-mini) — strips temperature, uses max_completion_tokens')
|
|
66
|
+
.option('--no-reasoning-model', 'Disable reasoning model mode (overrides saved config)')
|
|
66
67
|
.option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
|
|
67
68
|
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
68
69
|
.option('-v, --verbose', 'Enable verbose output (show LLM commands and responses)')
|
package/dist/cli/mcp.js
CHANGED
|
@@ -29,7 +29,7 @@ export const mcpCommand = async () => {
|
|
|
29
29
|
console.error('GitNexus: No indexed repos yet. Run `gitnexus analyze` in a git repo — the server will pick it up automatically.');
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
32
|
-
console.error(`GitNexus: MCP server starting with ${repos.length} repo(s): ${repos.map(r => r.name).join(', ')}`);
|
|
32
|
+
console.error(`GitNexus: MCP server starting with ${repos.length} repo(s): ${repos.map((r) => r.name).join(', ')}`);
|
|
33
33
|
}
|
|
34
34
|
// Start MCP server (serves all repos, discovers new ones lazily)
|
|
35
35
|
await startMCPServer(backend);
|
package/dist/cli/serve.js
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { createServer } from '../server/api.js';
|
|
2
|
+
// Catch anything that would cause a silent exit
|
|
3
|
+
process.on('uncaughtException', (err) => {
|
|
4
|
+
console.error('\n[gitnexus serve] Uncaught exception:', err.message);
|
|
5
|
+
if (process.env.DEBUG)
|
|
6
|
+
console.error(err.stack);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
9
|
+
process.on('unhandledRejection', (reason) => {
|
|
10
|
+
console.error('\n[gitnexus serve] Unhandled rejection:', reason?.message || reason);
|
|
11
|
+
if (process.env.DEBUG)
|
|
12
|
+
console.error(reason?.stack);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
2
15
|
export const serveCommand = async (options) => {
|
|
3
16
|
const port = Number(options?.port ?? 4747);
|
|
4
17
|
const host = options?.host ?? '127.0.0.1';
|
|
5
|
-
|
|
18
|
+
try {
|
|
19
|
+
await createServer(port, host);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
console.error(`\nFailed to start GitNexus server:\n`);
|
|
23
|
+
console.error(` ${err.message || err}\n`);
|
|
24
|
+
if (err.code === 'EADDRINUSE') {
|
|
25
|
+
console.error(` Port ${port} is already in use. Either:`);
|
|
26
|
+
console.error(` 1. Stop the other process using port ${port}`);
|
|
27
|
+
console.error(` 2. Use a different port: gitnexus serve --port 4748\n`);
|
|
28
|
+
}
|
|
29
|
+
if (err.stack && process.env.DEBUG) {
|
|
30
|
+
console.error(err.stack);
|
|
31
|
+
}
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
6
34
|
};
|
package/dist/cli/setup.js
CHANGED
|
@@ -164,13 +164,13 @@ async function installClaudeCodeHooks(result) {
|
|
|
164
164
|
const hookPath = path.join(destHooksDir, 'gitnexus-hook.cjs').replace(/\\/g, '/');
|
|
165
165
|
const hookCmd = `node "${hookPath.replace(/"/g, '\\"')}"`;
|
|
166
166
|
// Merge hook config into ~/.claude/settings.json
|
|
167
|
-
const existing = await readJsonFile(settingsPath) || {};
|
|
167
|
+
const existing = (await readJsonFile(settingsPath)) || {};
|
|
168
168
|
if (!existing.hooks)
|
|
169
169
|
existing.hooks = {};
|
|
170
170
|
function ensureHookEntry(eventName, matcher, timeout, statusMessage) {
|
|
171
171
|
if (!existing.hooks[eventName])
|
|
172
172
|
existing.hooks[eventName] = [];
|
|
173
|
-
const hasHook = existing.hooks[eventName].some((h) => h.hooks?.some(hh => hh.command?.includes('gitnexus-hook')));
|
|
173
|
+
const hasHook = existing.hooks[eventName].some((h) => h.hooks?.some((hh) => hh.command?.includes('gitnexus-hook')));
|
|
174
174
|
if (!hasHook) {
|
|
175
175
|
existing.hooks[eventName].push({
|
|
176
176
|
matcher,
|
|
@@ -213,7 +213,7 @@ async function setupOpenCode(result) {
|
|
|
213
213
|
function getCodexMcpTomlSection() {
|
|
214
214
|
const entry = getMcpEntry();
|
|
215
215
|
const command = JSON.stringify(entry.command);
|
|
216
|
-
const args = `[${entry.args.map(arg => JSON.stringify(arg)).join(', ')}]`;
|
|
216
|
+
const args = `[${entry.args.map((arg) => JSON.stringify(arg)).join(', ')}]`;
|
|
217
217
|
return `[mcp_servers.gitnexus]\ncommand = ${command}\nargs = ${args}\n`;
|
|
218
218
|
}
|
|
219
219
|
/**
|
|
@@ -231,9 +231,7 @@ async function upsertCodexConfigToml(configPath) {
|
|
|
231
231
|
return;
|
|
232
232
|
}
|
|
233
233
|
const section = getCodexMcpTomlSection();
|
|
234
|
-
const nextContent = existing.trim().length > 0
|
|
235
|
-
? `${existing.trimEnd()}\n\n${section}`
|
|
236
|
-
: section;
|
|
234
|
+
const nextContent = existing.trim().length > 0 ? `${existing.trimEnd()}\n\n${section}` : section;
|
|
237
235
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
238
236
|
await fs.writeFile(configPath, `${nextContent.trimEnd()}\n`, 'utf-8');
|
|
239
237
|
}
|
|
@@ -245,7 +243,9 @@ async function setupCodex(result) {
|
|
|
245
243
|
}
|
|
246
244
|
try {
|
|
247
245
|
const entry = getMcpEntry();
|
|
248
|
-
await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], {
|
|
246
|
+
await execFileAsync('codex', ['mcp', 'add', 'gitnexus', '--', entry.command, ...entry.args], {
|
|
247
|
+
shell: process.platform === 'win32',
|
|
248
|
+
});
|
|
249
249
|
result.configured.push('Codex');
|
|
250
250
|
return;
|
|
251
251
|
}
|
|
@@ -436,8 +436,8 @@ export const setupCommand = async () => {
|
|
|
436
436
|
}
|
|
437
437
|
console.log('');
|
|
438
438
|
console.log(' Summary:');
|
|
439
|
-
console.log(` MCP configured for: ${result.configured.filter(c => !c.includes('skills')).join(', ') || 'none'}`);
|
|
440
|
-
console.log(` Skills installed to: ${result.configured.filter(c => c.includes('skills')).length > 0 ? result.configured.filter(c => c.includes('skills')).join(', ') : 'none'}`);
|
|
439
|
+
console.log(` MCP configured for: ${result.configured.filter((c) => !c.includes('skills')).join(', ') || 'none'}`);
|
|
440
|
+
console.log(` Skills installed to: ${result.configured.filter((c) => c.includes('skills')).length > 0 ? result.configured.filter((c) => c.includes('skills')).join(', ') : 'none'}`);
|
|
441
441
|
console.log('');
|
|
442
442
|
console.log(' Next steps:');
|
|
443
443
|
console.log(' 1. cd into any git repo');
|
package/dist/cli/skill-gen.js
CHANGED
|
@@ -37,7 +37,7 @@ export const generateSkillFiles = async (repoPath, projectName, pipelineResult)
|
|
|
37
37
|
// Step 2: Filter to significant communities
|
|
38
38
|
// Keep communities with >= 3 symbols after aggregation.
|
|
39
39
|
const significant = aggregated
|
|
40
|
-
.filter(c => c.symbolCount >= 3)
|
|
40
|
+
.filter((c) => c.symbolCount >= 3)
|
|
41
41
|
.sort((a, b) => b.symbolCount - a.symbolCount)
|
|
42
42
|
.slice(0, 20);
|
|
43
43
|
if (significant.length === 0) {
|
|
@@ -51,7 +51,9 @@ export const generateSkillFiles = async (repoPath, projectName, pipelineResult)
|
|
|
51
51
|
try {
|
|
52
52
|
await fs.rm(outputDir, { recursive: true, force: true });
|
|
53
53
|
}
|
|
54
|
-
catch {
|
|
54
|
+
catch {
|
|
55
|
+
/* may not exist */
|
|
56
|
+
}
|
|
55
57
|
await fs.mkdir(outputDir, { recursive: true });
|
|
56
58
|
// Step 5: Generate skill files
|
|
57
59
|
const skills = [];
|
|
@@ -146,7 +148,7 @@ const buildCommunitiesFromMemberships = (memberships, graph, repoPath) => {
|
|
|
146
148
|
const nodeSet = new Set(nodeIds);
|
|
147
149
|
let internalEdges = 0;
|
|
148
150
|
let totalEdges = 0;
|
|
149
|
-
graph.forEachRelationship(rel => {
|
|
151
|
+
graph.forEachRelationship((rel) => {
|
|
150
152
|
if (nodeSet.has(rel.sourceId)) {
|
|
151
153
|
totalEdges++;
|
|
152
154
|
if (nodeSet.has(rel.targetId))
|
|
@@ -311,7 +313,7 @@ const gatherEntryPoints = (members) => {
|
|
|
311
313
|
Interface: 3,
|
|
312
314
|
};
|
|
313
315
|
return members
|
|
314
|
-
.filter(m => m.isExported)
|
|
316
|
+
.filter((m) => m.isExported)
|
|
315
317
|
.sort((a, b) => {
|
|
316
318
|
const pa = typePriority[a.label] ?? 99;
|
|
317
319
|
const pb = typePriority[b.label] ?? 99;
|
|
@@ -327,7 +329,7 @@ const gatherEntryPoints = (members) => {
|
|
|
327
329
|
const gatherFlows = (rawIds, processes) => {
|
|
328
330
|
const rawIdSet = new Set(rawIds);
|
|
329
331
|
return processes
|
|
330
|
-
.filter(proc => proc.communities.some(cid => rawIdSet.has(cid)))
|
|
332
|
+
.filter((proc) => proc.communities.some((cid) => rawIdSet.has(cid)))
|
|
331
333
|
.sort((a, b) => b.stepCount - a.stepCount);
|
|
332
334
|
};
|
|
333
335
|
/**
|
|
@@ -350,7 +352,7 @@ const gatherCrossConnections = (rawIds, ownLabel, membershipsByComm, nodeIdToCom
|
|
|
350
352
|
}
|
|
351
353
|
// Count outgoing CALLS to nodes in different communities
|
|
352
354
|
const targetCounts = new Map();
|
|
353
|
-
graph.forEachRelationship(rel => {
|
|
355
|
+
graph.forEachRelationship((rel) => {
|
|
354
356
|
if (rel.type !== 'CALLS')
|
|
355
357
|
return;
|
|
356
358
|
if (!ownNodeIds.has(rel.sourceId))
|
|
@@ -386,10 +388,10 @@ const renderSkillMarkdown = (community, projectName, members, files, entryPoints
|
|
|
386
388
|
// Dominant directory: most common top-level directory
|
|
387
389
|
const dominantDir = getDominantDirectory(files);
|
|
388
390
|
// Top symbol names for "When to Use"
|
|
389
|
-
const topNames = entryPoints.slice(0, 3).map(e => e.name);
|
|
391
|
+
const topNames = entryPoints.slice(0, 3).map((e) => e.name);
|
|
390
392
|
if (topNames.length === 0) {
|
|
391
393
|
// Fallback to any members
|
|
392
|
-
topNames.push(...members.slice(0, 3).map(m => m.name));
|
|
394
|
+
topNames.push(...members.slice(0, 3).map((m) => m.name));
|
|
393
395
|
}
|
|
394
396
|
const lines = [];
|
|
395
397
|
// Frontmatter
|
|
@@ -473,7 +475,11 @@ const renderSkillMarkdown = (community, projectName, members, files, entryPoints
|
|
|
473
475
|
lines.push('');
|
|
474
476
|
}
|
|
475
477
|
// How to Explore
|
|
476
|
-
const firstEntry = entryPoints.length > 0
|
|
478
|
+
const firstEntry = entryPoints.length > 0
|
|
479
|
+
? entryPoints[0].name
|
|
480
|
+
: members.length > 0
|
|
481
|
+
? members[0].name
|
|
482
|
+
: community.label;
|
|
477
483
|
lines.push('## How to Explore');
|
|
478
484
|
lines.push('');
|
|
479
485
|
lines.push(`1. \`gitnexus_context({name: "${firstEntry}"})\` \u2014 see callers and callees`);
|
package/dist/cli/wiki.d.ts
CHANGED
package/dist/cli/wiki.js
CHANGED
|
@@ -9,7 +9,7 @@ import readline from 'readline';
|
|
|
9
9
|
import { execSync, execFileSync } from 'child_process';
|
|
10
10
|
import cliProgress from 'cli-progress';
|
|
11
11
|
import { getGitRoot, isGitRepo } from '../storage/git.js';
|
|
12
|
-
import { getStoragePaths, loadMeta, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
|
|
12
|
+
import { getStoragePaths, loadMeta, loadCLIConfig, saveCLIConfig, } from '../storage/repo-manager.js';
|
|
13
13
|
import { WikiGenerator } from '../core/wiki/generator.js';
|
|
14
14
|
import { resolveLLMConfig } from '../core/wiki/llm-client.js';
|
|
15
15
|
import { detectCursorCLI } from '../core/wiki/cursor-client.js';
|
|
@@ -101,7 +101,12 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
101
101
|
}
|
|
102
102
|
// ── Resolve LLM config (with interactive fallback) ─────────────────
|
|
103
103
|
// Save any CLI overrides immediately
|
|
104
|
-
if (options?.apiKey ||
|
|
104
|
+
if (options?.apiKey ||
|
|
105
|
+
options?.model ||
|
|
106
|
+
options?.baseUrl ||
|
|
107
|
+
options?.provider ||
|
|
108
|
+
options?.apiVersion ||
|
|
109
|
+
options?.reasoningModel !== undefined) {
|
|
105
110
|
const existing = await loadCLIConfig();
|
|
106
111
|
const updates = {};
|
|
107
112
|
if (options.apiKey)
|
|
@@ -110,6 +115,10 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
110
115
|
updates.baseUrl = options.baseUrl;
|
|
111
116
|
if (options.provider)
|
|
112
117
|
updates.provider = options.provider;
|
|
118
|
+
if (options.apiVersion)
|
|
119
|
+
updates.apiVersion = options.apiVersion;
|
|
120
|
+
if (options.reasoningModel !== undefined)
|
|
121
|
+
updates.isReasoningModel = options.reasoningModel;
|
|
113
122
|
// Save model to appropriate field based on provider
|
|
114
123
|
if (options.model) {
|
|
115
124
|
if (options.provider === 'cursor') {
|
|
@@ -123,13 +132,21 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
123
132
|
console.log(' Config saved to ~/.gitnexus/config.json\n');
|
|
124
133
|
}
|
|
125
134
|
const savedConfig = await loadCLIConfig();
|
|
126
|
-
const hasSavedConfig = !!(savedConfig.provider === 'cursor' ||
|
|
127
|
-
|
|
135
|
+
const hasSavedConfig = !!(savedConfig.provider === 'cursor' ||
|
|
136
|
+
(savedConfig.apiKey && savedConfig.baseUrl));
|
|
137
|
+
const hasCLIOverrides = !!(options?.apiKey ||
|
|
138
|
+
options?.model ||
|
|
139
|
+
options?.baseUrl ||
|
|
140
|
+
options?.provider ||
|
|
141
|
+
options?.apiVersion ||
|
|
142
|
+
options?.reasoningModel !== undefined);
|
|
128
143
|
let llmConfig = await resolveLLMConfig({
|
|
129
144
|
model: options?.model,
|
|
130
145
|
baseUrl: options?.baseUrl,
|
|
131
146
|
apiKey: options?.apiKey,
|
|
132
147
|
provider: options?.provider,
|
|
148
|
+
apiVersion: options?.apiVersion,
|
|
149
|
+
isReasoningModel: options?.reasoningModel,
|
|
133
150
|
});
|
|
134
151
|
// Run interactive setup if no saved config and no CLI flags provided
|
|
135
152
|
// (even if env vars exist — let user explicitly choose their provider)
|
|
@@ -146,25 +163,26 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
146
163
|
// Non-interactive with env var or cursor — just use it
|
|
147
164
|
}
|
|
148
165
|
else {
|
|
149
|
-
console.log(
|
|
150
|
-
console.log(' Supports OpenAI, OpenRouter, any OpenAI-compatible API, or Cursor CLI.\n');
|
|
166
|
+
console.log(" No LLM configured. Let's set it up.\n");
|
|
167
|
+
console.log(' Supports OpenAI, OpenRouter, Azure, any OpenAI-compatible API, or Cursor CLI.\n');
|
|
151
168
|
// Check if Cursor CLI is available
|
|
152
169
|
const hasCursor = detectCursorCLI();
|
|
153
170
|
// Provider selection
|
|
154
171
|
console.log(' [1] OpenAI (api.openai.com)');
|
|
155
172
|
console.log(' [2] OpenRouter (openrouter.ai)');
|
|
156
|
-
console.log(' [3]
|
|
173
|
+
console.log(' [3] Azure OpenAI');
|
|
174
|
+
console.log(' [4] Custom endpoint');
|
|
157
175
|
if (hasCursor) {
|
|
158
|
-
console.log(' [
|
|
176
|
+
console.log(' [5] Cursor CLI (local, uses your Cursor subscription)');
|
|
159
177
|
}
|
|
160
178
|
console.log('');
|
|
161
|
-
const maxChoice = hasCursor ? '
|
|
179
|
+
const maxChoice = hasCursor ? '5' : '4';
|
|
162
180
|
const choice = await prompt(` Select provider (1/${maxChoice}): `);
|
|
163
181
|
let baseUrl;
|
|
164
182
|
let defaultModel;
|
|
165
183
|
let provider = 'openai';
|
|
166
184
|
let key = '';
|
|
167
|
-
if (choice === '
|
|
185
|
+
if (choice === '5' && hasCursor) {
|
|
168
186
|
// Cursor CLI selected - model defaults to 'auto' (Cursor's default)
|
|
169
187
|
provider = 'cursor';
|
|
170
188
|
baseUrl = '';
|
|
@@ -178,13 +196,98 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
178
196
|
console.log(' Config saved to ~/.gitnexus/config.json\n');
|
|
179
197
|
llmConfig = { ...llmConfig, provider: 'cursor', model, apiKey: '', baseUrl: '' };
|
|
180
198
|
}
|
|
199
|
+
else if (choice === '3') {
|
|
200
|
+
// Azure OpenAI guided setup
|
|
201
|
+
console.log('\n Azure OpenAI setup.');
|
|
202
|
+
console.log(' You need: your resource name, deployment name, and API key from the Azure portal.\n');
|
|
203
|
+
const resourceName = (await prompt(' Azure resource name (e.g. my-openai-resource): ')).trim();
|
|
204
|
+
if (!resourceName) {
|
|
205
|
+
console.log('\n No resource name provided. Aborting.\n');
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const deploymentName = (await prompt(' Deployment name (the name you gave your model deployment): ')).trim();
|
|
210
|
+
if (!deploymentName) {
|
|
211
|
+
console.log('\n No deployment name provided. Aborting.\n');
|
|
212
|
+
process.exitCode = 1;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Offer v1 or legacy URL
|
|
216
|
+
console.log('\n API format:');
|
|
217
|
+
console.log(' [1] v1 API — recommended (no api-version needed)');
|
|
218
|
+
console.log(' [2] Legacy — uses api-version query param\n');
|
|
219
|
+
const apiFormat = await prompt(' Select format (1/2, default: 1): ');
|
|
220
|
+
let azureApiVersion;
|
|
221
|
+
let azureBaseUrl;
|
|
222
|
+
if (apiFormat === '2') {
|
|
223
|
+
const versionInput = await prompt(' api-version (default: 2024-10-21): ');
|
|
224
|
+
azureApiVersion = versionInput || '2024-10-21';
|
|
225
|
+
azureBaseUrl = `https://${resourceName}.openai.azure.com/openai/deployments/${deploymentName}`;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
azureBaseUrl = `https://${resourceName}.openai.azure.com/openai/v1`;
|
|
229
|
+
azureApiVersion = undefined;
|
|
230
|
+
}
|
|
231
|
+
defaultModel = deploymentName;
|
|
232
|
+
// Ask if this is a reasoning model deployment
|
|
233
|
+
const reasoningAnswer = await prompt(' Is this a reasoning model (o1, o3, o4-mini)? (y/N): ');
|
|
234
|
+
const isReasoningModelDeployment = ['y', 'yes'].includes(reasoningAnswer.toLowerCase());
|
|
235
|
+
if (isReasoningModelDeployment) {
|
|
236
|
+
console.log(' Note: temperature and max_tokens will be omitted for this deployment (Azure reasoning model requirement).\n');
|
|
237
|
+
}
|
|
238
|
+
const modelInput = await prompt(` Model / deployment name (default: ${defaultModel}): `);
|
|
239
|
+
const model = modelInput || defaultModel;
|
|
240
|
+
// API key
|
|
241
|
+
const envKey = process.env.GITNEXUS_API_KEY || process.env.OPENAI_API_KEY || '';
|
|
242
|
+
let azureKey;
|
|
243
|
+
if (envKey) {
|
|
244
|
+
const masked = envKey.slice(0, 6) + '...' + envKey.slice(-4);
|
|
245
|
+
const useEnv = await prompt(` Use existing env key (${masked})? (Y/n): `);
|
|
246
|
+
if (!useEnv || useEnv.toLowerCase() === 'y' || useEnv.toLowerCase() === 'yes') {
|
|
247
|
+
azureKey = envKey;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
azureKey = await prompt(' API key: ', true);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
azureKey = await prompt(' API key: ', true);
|
|
255
|
+
}
|
|
256
|
+
if (!azureKey) {
|
|
257
|
+
console.log('\n No key provided. Aborting.\n');
|
|
258
|
+
process.exitCode = 1;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// Save Azure config including optional apiVersion and isReasoningModel
|
|
262
|
+
const azureConfig = {
|
|
263
|
+
apiKey: azureKey,
|
|
264
|
+
baseUrl: azureBaseUrl,
|
|
265
|
+
model,
|
|
266
|
+
provider: 'azure',
|
|
267
|
+
isReasoningModel: isReasoningModelDeployment,
|
|
268
|
+
};
|
|
269
|
+
if (azureApiVersion)
|
|
270
|
+
azureConfig.apiVersion = azureApiVersion;
|
|
271
|
+
await saveCLIConfig(azureConfig);
|
|
272
|
+
console.log(' Config saved to ~/.gitnexus/config.json\n');
|
|
273
|
+
llmConfig = {
|
|
274
|
+
...llmConfig,
|
|
275
|
+
apiKey: azureKey,
|
|
276
|
+
baseUrl: azureBaseUrl,
|
|
277
|
+
model,
|
|
278
|
+
provider: 'azure',
|
|
279
|
+
apiVersion: azureApiVersion,
|
|
280
|
+
isReasoningModel: isReasoningModelDeployment,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
181
283
|
else {
|
|
182
|
-
// OpenAI-compatible provider
|
|
284
|
+
// OpenAI-compatible provider (OpenAI, OpenRouter, Custom)
|
|
183
285
|
if (choice === '2') {
|
|
184
286
|
baseUrl = 'https://openrouter.ai/api/v1';
|
|
185
287
|
defaultModel = 'minimax/minimax-m2.5';
|
|
288
|
+
provider = 'openrouter';
|
|
186
289
|
}
|
|
187
|
-
else if (choice === '
|
|
290
|
+
else if (choice === '4') {
|
|
188
291
|
baseUrl = await prompt(' Base URL (e.g. http://localhost:11434/v1): ');
|
|
189
292
|
if (!baseUrl) {
|
|
190
293
|
console.log('\n No URL provided. Aborting.\n');
|
|
@@ -192,10 +295,12 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
192
295
|
return;
|
|
193
296
|
}
|
|
194
297
|
defaultModel = 'gpt-4o-mini';
|
|
298
|
+
provider = 'custom';
|
|
195
299
|
}
|
|
196
300
|
else {
|
|
197
301
|
baseUrl = 'https://api.openai.com/v1';
|
|
198
302
|
defaultModel = 'gpt-4o-mini';
|
|
303
|
+
provider = 'openai';
|
|
199
304
|
}
|
|
200
305
|
// Model
|
|
201
306
|
const modelInput = await prompt(` Model (default: ${defaultModel}): `);
|
|
@@ -221,9 +326,9 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
221
326
|
return;
|
|
222
327
|
}
|
|
223
328
|
// Save
|
|
224
|
-
await saveCLIConfig({ apiKey: key, baseUrl, model, provider
|
|
329
|
+
await saveCLIConfig({ apiKey: key, baseUrl, model, provider });
|
|
225
330
|
console.log(' Config saved to ~/.gitnexus/config.json\n');
|
|
226
|
-
llmConfig = { ...llmConfig, apiKey: key, baseUrl, model, provider
|
|
331
|
+
llmConfig = { ...llmConfig, apiKey: key, baseUrl, model, provider };
|
|
227
332
|
}
|
|
228
333
|
}
|
|
229
334
|
}
|
|
@@ -282,7 +387,11 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
282
387
|
const prefix = ' '.repeat(indent + 2);
|
|
283
388
|
const fileCount = node.files?.length || 0;
|
|
284
389
|
const childCount = node.children?.length || 0;
|
|
285
|
-
const suffix = fileCount > 0
|
|
390
|
+
const suffix = fileCount > 0
|
|
391
|
+
? ` (${fileCount} files)`
|
|
392
|
+
: childCount > 0
|
|
393
|
+
? ` (${childCount} children)`
|
|
394
|
+
: '';
|
|
286
395
|
console.log(`${prefix}- ${node.name}${suffix}`);
|
|
287
396
|
if (node.children && node.children.length > 0) {
|
|
288
397
|
printTree(node.children, indent + 1);
|
|
@@ -310,7 +419,7 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
310
419
|
console.log(`\n Opening ${treeFile} in ${editor}...`);
|
|
311
420
|
console.log(' Save and close the editor when done.\n');
|
|
312
421
|
try {
|
|
313
|
-
|
|
422
|
+
execFileSync(editor, [treeFile], { stdio: 'inherit' });
|
|
314
423
|
}
|
|
315
424
|
catch {
|
|
316
425
|
console.log(` Could not open editor. Please edit manually:\n ${treeFile}\n`);
|
|
@@ -381,16 +490,26 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
381
490
|
if (err.message?.includes('No source files')) {
|
|
382
491
|
console.log(`\n ${err.message}\n`);
|
|
383
492
|
}
|
|
493
|
+
else if (err.message?.includes('content filter')) {
|
|
494
|
+
// Content filter block — actionable message
|
|
495
|
+
console.log(`\n Content Filter: ${err.message}\n`);
|
|
496
|
+
console.log(' To resolve: rephrase your prompt or adjust the content filter policy for your deployment.\n');
|
|
497
|
+
}
|
|
384
498
|
else if (err.message?.includes('API key') || err.message?.includes('API error')) {
|
|
385
499
|
console.log(`\n LLM Error: ${err.message}\n`);
|
|
386
500
|
// Offer to reconfigure on auth-related failures
|
|
387
|
-
const isAuthError = err.message?.includes('401') ||
|
|
388
|
-
|
|
389
|
-
|
|
501
|
+
const isAuthError = err.message?.includes('401') ||
|
|
502
|
+
err.message?.includes('403') ||
|
|
503
|
+
err.message?.includes('502') ||
|
|
504
|
+
err.message?.includes('authenticate') ||
|
|
505
|
+
err.message?.includes('Unauthorized');
|
|
390
506
|
if (isAuthError && process.stdin.isTTY) {
|
|
391
507
|
const answer = await new Promise((resolve) => {
|
|
392
508
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
393
|
-
rl.question(' Reconfigure LLM settings? (Y/n): ', (ans) => {
|
|
509
|
+
rl.question(' Reconfigure LLM settings? (Y/n): ', (ans) => {
|
|
510
|
+
rl.close();
|
|
511
|
+
resolve(ans.trim().toLowerCase());
|
|
512
|
+
});
|
|
394
513
|
});
|
|
395
514
|
if (!answer || answer === 'y' || answer === 'yes') {
|
|
396
515
|
// Clear saved config so next run triggers interactive setup
|
|
@@ -420,14 +539,10 @@ function hasGhCLI() {
|
|
|
420
539
|
}
|
|
421
540
|
function publishGist(htmlPath) {
|
|
422
541
|
try {
|
|
423
|
-
const output = execFileSync('gh', [
|
|
424
|
-
'gist', 'create', htmlPath,
|
|
425
|
-
'--desc', 'Repository Wiki — generated by GitNexus',
|
|
426
|
-
'--public',
|
|
427
|
-
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
542
|
+
const output = execFileSync('gh', ['gist', 'create', htmlPath, '--desc', 'Repository Wiki — generated by GitNexus', '--public'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
428
543
|
// gh gist create prints the gist URL as the last line
|
|
429
544
|
const lines = output.split('\n');
|
|
430
|
-
const gistUrl = lines.find(l => l.includes('gist.github.com')) || lines[lines.length - 1];
|
|
545
|
+
const gistUrl = lines.find((l) => l.includes('gist.github.com')) || lines[lines.length - 1];
|
|
431
546
|
if (!gistUrl || !gistUrl.includes('gist.github.com'))
|
|
432
547
|
return null;
|
|
433
548
|
// Build a raw viewer URL via gist.githack.com
|