gitnexus 1.6.6-rc.4 → 1.6.6-rc.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/dist/cli/analyze.js +54 -3
- package/dist/cli/index.js +1 -0
- package/dist/cli/wiki.d.ts +1 -0
- package/dist/cli/wiki.js +1 -0
- package/dist/core/wiki/generator.d.ts +13 -0
- package/dist/core/wiki/generator.js +45 -4
- package/package.json +1 -1
package/dist/cli/analyze.js
CHANGED
|
@@ -58,12 +58,55 @@ const installFatalHandlers = () => {
|
|
|
58
58
|
process.exit(1);
|
|
59
59
|
});
|
|
60
60
|
};
|
|
61
|
-
const HEAP_MB =
|
|
62
|
-
const
|
|
61
|
+
const HEAP_MB = 16384;
|
|
62
|
+
const TEST_RESPAWN_HEAP_MB = Number(process.env.GITNEXUS_TEST_RESPAWN_HEAP_MB);
|
|
63
|
+
const RESPAWN_HEAP_MB = Number.isFinite(TEST_RESPAWN_HEAP_MB) && TEST_RESPAWN_HEAP_MB > 0
|
|
64
|
+
? Math.floor(TEST_RESPAWN_HEAP_MB)
|
|
65
|
+
: HEAP_MB;
|
|
66
|
+
const HEAP_FLAG = `--max-old-space-size=${RESPAWN_HEAP_MB}`;
|
|
63
67
|
/** Increase default stack size (KB) to prevent stack overflow on deep class hierarchies. */
|
|
64
68
|
const STACK_KB = 4096;
|
|
65
69
|
const STACK_FLAG = `--stack-size=${STACK_KB}`;
|
|
66
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Heuristic for "child re-exec likely died from V8 OOM".
|
|
72
|
+
*
|
|
73
|
+
* Platform-independent detection is best-effort: V8/Node usually emit
|
|
74
|
+
* stable heap-exhaustion phrases in stderr/message across Linux/macOS/Windows
|
|
75
|
+
* (for example "JavaScript heap out of memory" or "Reached heap limit"),
|
|
76
|
+
* while some environments only expose status/signal (e.g. 134/SIGABRT).
|
|
77
|
+
* We combine both text signatures and process-exit signatures.
|
|
78
|
+
*/
|
|
79
|
+
const childProcessLikelyOom = (err) => {
|
|
80
|
+
if (!err || typeof err !== 'object')
|
|
81
|
+
return false;
|
|
82
|
+
const e = err;
|
|
83
|
+
const hasHeapOomSignature = (v) => {
|
|
84
|
+
const text = (Buffer.isBuffer(v) ? v.toString('utf8') : typeof v === 'string' ? v : '').toLowerCase();
|
|
85
|
+
if (!text)
|
|
86
|
+
return false;
|
|
87
|
+
return (text.includes('javascript heap out of memory') ||
|
|
88
|
+
text.includes('reached heap limit') ||
|
|
89
|
+
text.includes('allocation failed - javascript heap out of memory') ||
|
|
90
|
+
text.includes('fatalprocessoutofmemory'));
|
|
91
|
+
};
|
|
92
|
+
const fields = [e.message, e.stderr, e.stdout];
|
|
93
|
+
if (fields.some((v) => hasHeapOomSignature(v)))
|
|
94
|
+
return true;
|
|
95
|
+
const hasAnyChildOutput = [e.stderr, e.stdout].some((v) => (Buffer.isBuffer(v) && v.length > 0) || (typeof v === 'string' && v.length > 0));
|
|
96
|
+
if (hasAnyChildOutput)
|
|
97
|
+
return false;
|
|
98
|
+
return e.status === 134 || e.signal === 'SIGABRT';
|
|
99
|
+
};
|
|
100
|
+
const forceHeapOOMForTestIfEnabled = () => {
|
|
101
|
+
if (process.env.GITNEXUS_TEST_FORCE_HEAP_OOM !== '1')
|
|
102
|
+
return;
|
|
103
|
+
// Allocate JS strings (not Buffers) so pressure lands on V8 heap itself.
|
|
104
|
+
// Buffers can allocate off-heap, which makes OOM triggering less reliable.
|
|
105
|
+
const chunks = [];
|
|
106
|
+
for (;;)
|
|
107
|
+
chunks.push('x'.repeat(1024 * 1024));
|
|
108
|
+
};
|
|
109
|
+
/** Re-exec the process with a 16GB heap and larger stack if we're currently below that. */
|
|
67
110
|
function ensureHeap() {
|
|
68
111
|
const nodeOpts = process.env.NODE_OPTIONS || '';
|
|
69
112
|
if (nodeOpts.includes('--max-old-space-size'))
|
|
@@ -83,6 +126,13 @@ function ensureHeap() {
|
|
|
83
126
|
});
|
|
84
127
|
}
|
|
85
128
|
catch (e) {
|
|
129
|
+
if (childProcessLikelyOom(e)) {
|
|
130
|
+
cliError(` Analysis likely ran out of memory.\n` +
|
|
131
|
+
` Retry with a larger heap if your machine allows it:\n` +
|
|
132
|
+
` NODE_OPTIONS="--max-old-space-size=24576" gitnexus analyze [your-args]\n` +
|
|
133
|
+
` (Windows: set NODE_OPTIONS=--max-old-space-size=24576 && gitnexus analyze [your-args])\n` +
|
|
134
|
+
` If this persists, it may be a native crash unrelated to heap size.\n`, { recoveryHint: 'heap-oom-respawn' });
|
|
135
|
+
}
|
|
86
136
|
process.exitCode = e.status ?? 1;
|
|
87
137
|
}
|
|
88
138
|
return true;
|
|
@@ -104,6 +154,7 @@ export const shouldGenerateCommunitySkillFiles = (options, pipelineResult) => Bo
|
|
|
104
154
|
export const analyzeCommand = async (inputPath, options) => {
|
|
105
155
|
if (ensureHeap())
|
|
106
156
|
return;
|
|
157
|
+
forceHeapOOMForTestIfEnabled();
|
|
107
158
|
// Install fatal handlers immediately after re-exec resolution so any
|
|
108
159
|
// async error that escapes the try/catch below (#1169) surfaces with
|
|
109
160
|
// a stack trace and a non-zero exit code instead of a silent exit 0.
|
package/dist/cli/index.js
CHANGED
|
@@ -108,6 +108,7 @@ program
|
|
|
108
108
|
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
109
109
|
.option('-v, --verbose', 'Enable verbose output (show LLM commands and responses)')
|
|
110
110
|
.option('--review', 'Stop after grouping to review module structure before generating pages')
|
|
111
|
+
.option('--lang <lang>', 'Output language for generated documentation (e.g. english, chinese, spanish, japanese)')
|
|
111
112
|
.action(createLazyAction(() => import('./wiki.js'), 'wikiCommand'));
|
|
112
113
|
program
|
|
113
114
|
.command('augment <pattern>')
|
package/dist/cli/wiki.d.ts
CHANGED
package/dist/cli/wiki.js
CHANGED
|
@@ -364,6 +364,7 @@ export const wikiCommand = async (inputPath, options) => {
|
|
|
364
364
|
force: options?.force,
|
|
365
365
|
concurrency: options?.concurrency ? parseInt(options.concurrency, 10) : undefined,
|
|
366
366
|
reviewOnly: options?.review,
|
|
367
|
+
lang: options?.lang,
|
|
367
368
|
};
|
|
368
369
|
const generator = new WikiGenerator(repoPath, storagePath, lbugPath, llmConfig, wikiOptions, (phase, percent, detail) => {
|
|
369
370
|
const label = detail || phase;
|
|
@@ -16,11 +16,14 @@ export interface WikiOptions {
|
|
|
16
16
|
concurrency?: number;
|
|
17
17
|
/** If true, stop after building module tree for user review */
|
|
18
18
|
reviewOnly?: boolean;
|
|
19
|
+
/** Output language for generated documentation (e.g. 'english', 'chinese', 'spanish') */
|
|
20
|
+
lang?: string;
|
|
19
21
|
}
|
|
20
22
|
export interface WikiMeta {
|
|
21
23
|
fromCommit: string;
|
|
22
24
|
generatedAt: string;
|
|
23
25
|
model: string;
|
|
26
|
+
lang: string;
|
|
24
27
|
moduleFiles: Record<string, string[]>;
|
|
25
28
|
moduleTree: ModuleTreeNode[];
|
|
26
29
|
}
|
|
@@ -62,6 +65,16 @@ export declare class WikiGenerator {
|
|
|
62
65
|
* Also touches the DB connection periodically to prevent idle timeout.
|
|
63
66
|
*/
|
|
64
67
|
private streamOpts;
|
|
68
|
+
/**
|
|
69
|
+
* Return the effective lang string: strip control characters, trim, cap at 50 chars,
|
|
70
|
+
* then validate against a character allowlist. Returns '' if the value is absent or invalid.
|
|
71
|
+
* Used for both prompt construction and meta storage/comparison so they are always in sync.
|
|
72
|
+
*/
|
|
73
|
+
private effectiveLang;
|
|
74
|
+
/**
|
|
75
|
+
* Append an output-language instruction to a system prompt when --lang is set.
|
|
76
|
+
*/
|
|
77
|
+
private buildSystemPrompt;
|
|
65
78
|
/**
|
|
66
79
|
* Route LLM call to the appropriate provider (OpenAI-compatible or Cursor CLI).
|
|
67
80
|
*/
|
|
@@ -89,6 +89,27 @@ export class WikiGenerator {
|
|
|
89
89
|
},
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* Return the effective lang string: strip control characters, trim, cap at 50 chars,
|
|
94
|
+
* then validate against a character allowlist. Returns '' if the value is absent or invalid.
|
|
95
|
+
* Used for both prompt construction and meta storage/comparison so they are always in sync.
|
|
96
|
+
*/
|
|
97
|
+
effectiveLang() {
|
|
98
|
+
const lang = (this.options.lang ?? '')
|
|
99
|
+
.replace(/[\x00-\x1F\x7F]/g, '')
|
|
100
|
+
.trim()
|
|
101
|
+
.slice(0, 50);
|
|
102
|
+
return /^[a-zA-Z -]+$/.test(lang) ? lang : '';
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Append an output-language instruction to a system prompt when --lang is set.
|
|
106
|
+
*/
|
|
107
|
+
buildSystemPrompt(base) {
|
|
108
|
+
const lang = this.effectiveLang();
|
|
109
|
+
if (!lang)
|
|
110
|
+
return base;
|
|
111
|
+
return `${base}\n\nIMPORTANT: Write ALL documentation content in ${lang}. This includes prose, code comments in examples, and diagram labels. Note: page titles (H1 headings) are generated separately and will remain in English.`;
|
|
112
|
+
}
|
|
92
113
|
/**
|
|
93
114
|
* Route LLM call to the appropriate provider (OpenAI-compatible or Cursor CLI).
|
|
94
115
|
*/
|
|
@@ -112,6 +133,13 @@ export class WikiGenerator {
|
|
|
112
133
|
const forceMode = this.options.force;
|
|
113
134
|
// Up-to-date check (skip if --force)
|
|
114
135
|
if (!forceMode && existingMeta && existingMeta.fromCommit === currentCommit) {
|
|
136
|
+
const currentLang = this.effectiveLang();
|
|
137
|
+
const metaLang = existingMeta.lang ?? '';
|
|
138
|
+
if (currentLang !== metaLang) {
|
|
139
|
+
const prevDisplay = metaLang || 'english (default)';
|
|
140
|
+
const nextDisplay = currentLang || 'english (default)';
|
|
141
|
+
throw new Error(`Wiki was generated in ${prevDisplay}; use --force to regenerate in ${nextDisplay}.`);
|
|
142
|
+
}
|
|
115
143
|
// Still regenerate the HTML viewer in case it's missing
|
|
116
144
|
await this.ensureHTMLViewer();
|
|
117
145
|
return { pagesGenerated: 0, mode: 'up-to-date', failedModules: [] };
|
|
@@ -139,6 +167,13 @@ export class WikiGenerator {
|
|
|
139
167
|
let result;
|
|
140
168
|
try {
|
|
141
169
|
if (!forceMode && existingMeta && existingMeta.fromCommit) {
|
|
170
|
+
const currentLang = this.effectiveLang();
|
|
171
|
+
const metaLang = existingMeta.lang ?? '';
|
|
172
|
+
if (currentLang !== metaLang) {
|
|
173
|
+
const prevDisplay = metaLang || 'english (default)';
|
|
174
|
+
const nextDisplay = currentLang || 'english (default)';
|
|
175
|
+
throw new Error(`Wiki was generated in ${prevDisplay}; use --force to regenerate in ${nextDisplay}.`);
|
|
176
|
+
}
|
|
142
177
|
result = await this.incrementalUpdate(existingMeta, currentCommit);
|
|
143
178
|
}
|
|
144
179
|
else {
|
|
@@ -257,6 +292,7 @@ export class WikiGenerator {
|
|
|
257
292
|
fromCommit: currentCommit,
|
|
258
293
|
generatedAt: new Date().toISOString(),
|
|
259
294
|
model: this.llmConfig.model,
|
|
295
|
+
lang: this.effectiveLang(),
|
|
260
296
|
moduleFiles,
|
|
261
297
|
moduleTree,
|
|
262
298
|
});
|
|
@@ -298,6 +334,9 @@ export class WikiGenerator {
|
|
|
298
334
|
FILE_LIST: fileList,
|
|
299
335
|
DIRECTORY_TREE: dirTree,
|
|
300
336
|
});
|
|
337
|
+
// Grouping is a structured-data phase (JSON output), not documentation.
|
|
338
|
+
// Do NOT apply buildSystemPrompt here — a language instruction would risk
|
|
339
|
+
// translating module-name keys, breaking slug stability and JSON parsing.
|
|
301
340
|
const response = await this.invokeLLM(prompt, GROUPING_SYSTEM_PROMPT, this.streamOpts('Grouping files', 15, 13));
|
|
302
341
|
const grouping = this.parseGroupingResponse(response.content, files);
|
|
303
342
|
// Convert to tree nodes
|
|
@@ -444,8 +483,8 @@ export class WikiGenerator {
|
|
|
444
483
|
INCOMING_CALLS: formatCallEdges(interCalls.incoming),
|
|
445
484
|
PROCESSES: formatProcesses(processes),
|
|
446
485
|
});
|
|
447
|
-
const response = await this.invokeLLM(prompt, MODULE_SYSTEM_PROMPT, this.streamOpts(node.name));
|
|
448
|
-
//
|
|
486
|
+
const response = await this.invokeLLM(prompt, this.buildSystemPrompt(MODULE_SYSTEM_PROMPT), this.streamOpts(node.name));
|
|
487
|
+
// H1 uses the English module name (stable slug source); body is LLM-translated.
|
|
449
488
|
const pageContent = sanitizeMermaidMarkdown(`# ${node.name}\n\n${response.content}`);
|
|
450
489
|
await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
|
|
451
490
|
}
|
|
@@ -480,7 +519,7 @@ export class WikiGenerator {
|
|
|
480
519
|
CROSS_MODULE_CALLS: formatCallEdges(crossCalls),
|
|
481
520
|
CROSS_PROCESSES: formatProcesses(processes),
|
|
482
521
|
});
|
|
483
|
-
const response = await this.invokeLLM(prompt, PARENT_SYSTEM_PROMPT, this.streamOpts(node.name));
|
|
522
|
+
const response = await this.invokeLLM(prompt, this.buildSystemPrompt(PARENT_SYSTEM_PROMPT), this.streamOpts(node.name));
|
|
484
523
|
const pageContent = sanitizeMermaidMarkdown(`# ${node.name}\n\n${response.content}`);
|
|
485
524
|
await fs.writeFile(path.join(this.wikiDir, `${node.slug}.md`), pageContent, 'utf-8');
|
|
486
525
|
}
|
|
@@ -516,7 +555,7 @@ export class WikiGenerator {
|
|
|
516
555
|
MODULE_EDGES: edgesText,
|
|
517
556
|
TOP_PROCESSES: formatProcesses(topProcesses),
|
|
518
557
|
});
|
|
519
|
-
const response = await this.invokeLLM(prompt, OVERVIEW_SYSTEM_PROMPT, this.streamOpts('Generating overview', 88));
|
|
558
|
+
const response = await this.invokeLLM(prompt, this.buildSystemPrompt(OVERVIEW_SYSTEM_PROMPT), this.streamOpts('Generating overview', 88));
|
|
520
559
|
const pageContent = sanitizeMermaidMarkdown(`# ${path.basename(this.repoPath)} — Wiki\n\n${response.content}`);
|
|
521
560
|
await fs.writeFile(path.join(this.wikiDir, 'overview.md'), pageContent, 'utf-8');
|
|
522
561
|
}
|
|
@@ -538,6 +577,7 @@ export class WikiGenerator {
|
|
|
538
577
|
...existingMeta,
|
|
539
578
|
fromCommit: currentCommit,
|
|
540
579
|
generatedAt: new Date().toISOString(),
|
|
580
|
+
lang: this.effectiveLang(),
|
|
541
581
|
});
|
|
542
582
|
return { pagesGenerated: 0, mode: 'incremental', failedModules: [] };
|
|
543
583
|
}
|
|
@@ -627,6 +667,7 @@ export class WikiGenerator {
|
|
|
627
667
|
fromCommit: currentCommit,
|
|
628
668
|
generatedAt: new Date().toISOString(),
|
|
629
669
|
model: this.llmConfig.model,
|
|
670
|
+
lang: this.effectiveLang(),
|
|
630
671
|
});
|
|
631
672
|
this.onProgress('done', 100, 'Incremental update complete');
|
|
632
673
|
return { pagesGenerated, mode: 'incremental', failedModules: [...this.failedModules] };
|
package/package.json
CHANGED