@duytransipher/gitnexus 1.4.6-sipher.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/LICENSE +73 -0
- package/README.md +261 -0
- package/dist/cli/ai-context.d.ts +23 -0
- package/dist/cli/ai-context.js +265 -0
- package/dist/cli/analyze.d.ts +12 -0
- package/dist/cli/analyze.js +345 -0
- package/dist/cli/augment.d.ts +13 -0
- package/dist/cli/augment.js +33 -0
- package/dist/cli/clean.d.ts +10 -0
- package/dist/cli/clean.js +60 -0
- package/dist/cli/eval-server.d.ts +37 -0
- package/dist/cli/eval-server.js +389 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +137 -0
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/list.d.ts +6 -0
- package/dist/cli/list.js +30 -0
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +36 -0
- package/dist/cli/serve.d.ts +4 -0
- package/dist/cli/serve.js +6 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +367 -0
- package/dist/cli/sipher-patched.d.ts +2 -0
- package/dist/cli/sipher-patched.js +77 -0
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.d.ts +6 -0
- package/dist/cli/status.js +36 -0
- package/dist/cli/tool.d.ts +60 -0
- package/dist/cli/tool.js +180 -0
- package/dist/cli/wiki.d.ts +15 -0
- package/dist/cli/wiki.js +365 -0
- package/dist/config/ignore-service.d.ts +26 -0
- package/dist/config/ignore-service.js +284 -0
- package/dist/config/supported-languages.d.ts +15 -0
- package/dist/config/supported-languages.js +16 -0
- package/dist/core/augmentation/engine.d.ts +26 -0
- package/dist/core/augmentation/engine.js +240 -0
- package/dist/core/embeddings/embedder.d.ts +60 -0
- package/dist/core/embeddings/embedder.js +251 -0
- package/dist/core/embeddings/embedding-pipeline.d.ts +51 -0
- package/dist/core/embeddings/embedding-pipeline.js +356 -0
- package/dist/core/embeddings/index.d.ts +9 -0
- package/dist/core/embeddings/index.js +9 -0
- package/dist/core/embeddings/text-generator.d.ts +24 -0
- package/dist/core/embeddings/text-generator.js +182 -0
- package/dist/core/embeddings/types.d.ts +87 -0
- package/dist/core/embeddings/types.js +32 -0
- package/dist/core/graph/graph.d.ts +2 -0
- package/dist/core/graph/graph.js +66 -0
- package/dist/core/graph/types.d.ts +66 -0
- package/dist/core/graph/types.js +1 -0
- package/dist/core/ingestion/ast-cache.d.ts +11 -0
- package/dist/core/ingestion/ast-cache.js +35 -0
- package/dist/core/ingestion/call-processor.d.ts +23 -0
- package/dist/core/ingestion/call-processor.js +793 -0
- package/dist/core/ingestion/call-routing.d.ts +68 -0
- package/dist/core/ingestion/call-routing.js +129 -0
- package/dist/core/ingestion/cluster-enricher.d.ts +38 -0
- package/dist/core/ingestion/cluster-enricher.js +170 -0
- package/dist/core/ingestion/community-processor.d.ts +39 -0
- package/dist/core/ingestion/community-processor.js +312 -0
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +40 -0
- package/dist/core/ingestion/entry-point-scoring.js +353 -0
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.d.ts +28 -0
- package/dist/core/ingestion/filesystem-walker.js +81 -0
- package/dist/core/ingestion/framework-detection.d.ts +54 -0
- package/dist/core/ingestion/framework-detection.js +411 -0
- package/dist/core/ingestion/heritage-processor.d.ts +28 -0
- package/dist/core/ingestion/heritage-processor.js +251 -0
- package/dist/core/ingestion/import-processor.d.ts +34 -0
- package/dist/core/ingestion/import-processor.js +398 -0
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +19 -0
- package/dist/core/ingestion/parsing-processor.js +315 -0
- package/dist/core/ingestion/pipeline.d.ts +6 -0
- package/dist/core/ingestion/pipeline.js +401 -0
- package/dist/core/ingestion/process-processor.d.ts +51 -0
- package/dist/core/ingestion/process-processor.js +315 -0
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/structure-processor.d.ts +2 -0
- package/dist/core/ingestion/structure-processor.js +36 -0
- package/dist/core/ingestion/symbol-table.d.ts +63 -0
- package/dist/core/ingestion/symbol-table.js +85 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +15 -0
- package/dist/core/ingestion/tree-sitter-queries.js +888 -0
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +613 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +383 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +467 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +681 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +549 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +455 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +456 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +145 -0
- package/dist/core/ingestion/type-extractors/shared.js +810 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
- package/dist/core/ingestion/type-extractors/types.js +1 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +494 -0
- package/dist/core/ingestion/utils.d.ts +138 -0
- package/dist/core/ingestion/utils.js +1290 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +122 -0
- package/dist/core/ingestion/workers/parse-worker.js +1126 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +16 -0
- package/dist/core/ingestion/workers/worker-pool.js +128 -0
- package/dist/core/lbug/csv-generator.d.ts +33 -0
- package/dist/core/lbug/csv-generator.js +366 -0
- package/dist/core/lbug/lbug-adapter.d.ts +103 -0
- package/dist/core/lbug/lbug-adapter.js +769 -0
- package/dist/core/lbug/schema.d.ts +53 -0
- package/dist/core/lbug/schema.js +430 -0
- package/dist/core/search/bm25-index.d.ts +23 -0
- package/dist/core/search/bm25-index.js +96 -0
- package/dist/core/search/hybrid-search.d.ts +49 -0
- package/dist/core/search/hybrid-search.js +118 -0
- package/dist/core/tree-sitter/parser-loader.d.ts +5 -0
- package/dist/core/tree-sitter/parser-loader.js +63 -0
- package/dist/core/wiki/generator.d.ts +120 -0
- package/dist/core/wiki/generator.js +939 -0
- package/dist/core/wiki/graph-queries.d.ts +80 -0
- package/dist/core/wiki/graph-queries.js +238 -0
- package/dist/core/wiki/html-viewer.d.ts +10 -0
- package/dist/core/wiki/html-viewer.js +297 -0
- package/dist/core/wiki/llm-client.d.ts +43 -0
- package/dist/core/wiki/llm-client.js +186 -0
- package/dist/core/wiki/prompts.d.ts +53 -0
- package/dist/core/wiki/prompts.js +174 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +3 -0
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/embedder.d.ts +27 -0
- package/dist/mcp/core/embedder.js +108 -0
- package/dist/mcp/core/lbug-adapter.d.ts +57 -0
- package/dist/mcp/core/lbug-adapter.js +455 -0
- package/dist/mcp/local/local-backend.d.ts +181 -0
- package/dist/mcp/local/local-backend.js +1722 -0
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +411 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +296 -0
- package/dist/mcp/staleness.d.ts +15 -0
- package/dist/mcp/staleness.js +29 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +292 -0
- package/dist/server/api.d.ts +10 -0
- package/dist/server/api.js +344 -0
- package/dist/server/mcp-http.d.ts +13 -0
- package/dist/server/mcp-http.js +100 -0
- package/dist/storage/git.d.ts +6 -0
- package/dist/storage/git.js +35 -0
- package/dist/storage/repo-manager.d.ts +138 -0
- package/dist/storage/repo-manager.js +299 -0
- package/dist/types/pipeline.d.ts +32 -0
- package/dist/types/pipeline.js +18 -0
- package/dist/unreal/bridge.d.ts +4 -0
- package/dist/unreal/bridge.js +113 -0
- package/dist/unreal/config.d.ts +6 -0
- package/dist/unreal/config.js +55 -0
- package/dist/unreal/types.d.ts +105 -0
- package/dist/unreal/types.js +1 -0
- package/hooks/claude/gitnexus-hook.cjs +238 -0
- package/hooks/claude/pre-tool-use.sh +79 -0
- package/hooks/claude/session-start.sh +42 -0
- package/package.json +100 -0
- package/scripts/ensure-cli-executable.cjs +21 -0
- package/scripts/patch-tree-sitter-swift.cjs +74 -0
- package/scripts/setup-unreal-gitnexus.ps1 +191 -0
- package/skills/gitnexus-cli.md +82 -0
- package/skills/gitnexus-debugging.md +89 -0
- package/skills/gitnexus-exploring.md +78 -0
- package/skills/gitnexus-guide.md +64 -0
- package/skills/gitnexus-impact-analysis.md +97 -0
- package/skills/gitnexus-pr-review.md +163 -0
- package/skills/gitnexus-refactoring.md +121 -0
- package/vendor/leiden/index.cjs +355 -0
- package/vendor/leiden/utils.cjs +392 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph Queries for Wiki Generation
|
|
3
|
+
*
|
|
4
|
+
* Encapsulated Cypher queries against the GitNexus knowledge graph.
|
|
5
|
+
* Uses the MCP-style pooled lbug-adapter for connection management.
|
|
6
|
+
*/
|
|
7
|
+
export interface FileWithExports {
|
|
8
|
+
filePath: string;
|
|
9
|
+
symbols: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
type: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export interface CallEdge {
|
|
15
|
+
fromFile: string;
|
|
16
|
+
fromName: string;
|
|
17
|
+
toFile: string;
|
|
18
|
+
toName: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ProcessInfo {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
type: string;
|
|
24
|
+
stepCount: number;
|
|
25
|
+
steps: Array<{
|
|
26
|
+
step: number;
|
|
27
|
+
name: string;
|
|
28
|
+
filePath: string;
|
|
29
|
+
type: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the LadybugDB connection for wiki generation.
|
|
34
|
+
*/
|
|
35
|
+
export declare function initWikiDb(lbugPath: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Close the LadybugDB connection.
|
|
38
|
+
*/
|
|
39
|
+
export declare function closeWikiDb(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Get all source files with their exported symbol names and types.
|
|
42
|
+
*/
|
|
43
|
+
export declare function getFilesWithExports(): Promise<FileWithExports[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Get all files tracked in the graph (including those with no exports).
|
|
46
|
+
*/
|
|
47
|
+
export declare function getAllFiles(): Promise<string[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Get inter-file call edges (calls between different files).
|
|
50
|
+
*/
|
|
51
|
+
export declare function getInterFileCallEdges(): Promise<CallEdge[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Get call edges between files within a specific set (intra-module).
|
|
54
|
+
*/
|
|
55
|
+
export declare function getIntraModuleCallEdges(filePaths: string[]): Promise<CallEdge[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Get call edges crossing module boundaries (external calls from/to module files).
|
|
58
|
+
*/
|
|
59
|
+
export declare function getInterModuleCallEdges(filePaths: string[]): Promise<{
|
|
60
|
+
outgoing: CallEdge[];
|
|
61
|
+
incoming: CallEdge[];
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Get processes (execution flows) that pass through a set of files.
|
|
65
|
+
* Returns top N by step count.
|
|
66
|
+
*/
|
|
67
|
+
export declare function getProcessesForFiles(filePaths: string[], limit?: number): Promise<ProcessInfo[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Get all processes in the graph (for overview page).
|
|
70
|
+
*/
|
|
71
|
+
export declare function getAllProcesses(limit?: number): Promise<ProcessInfo[]>;
|
|
72
|
+
/**
|
|
73
|
+
* Get inter-module edges for overview architecture diagram.
|
|
74
|
+
* Groups call edges by source/target module.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getInterModuleEdgesForOverview(moduleFiles: Record<string, string[]>): Promise<Array<{
|
|
77
|
+
from: string;
|
|
78
|
+
to: string;
|
|
79
|
+
count: number;
|
|
80
|
+
}>>;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph Queries for Wiki Generation
|
|
3
|
+
*
|
|
4
|
+
* Encapsulated Cypher queries against the GitNexus knowledge graph.
|
|
5
|
+
* Uses the MCP-style pooled lbug-adapter for connection management.
|
|
6
|
+
*/
|
|
7
|
+
import { initLbug, executeQuery, closeLbug } from '../../mcp/core/lbug-adapter.js';
|
|
8
|
+
const REPO_ID = '__wiki__';
|
|
9
|
+
/**
|
|
10
|
+
* Initialize the LadybugDB connection for wiki generation.
|
|
11
|
+
*/
|
|
12
|
+
export async function initWikiDb(lbugPath) {
|
|
13
|
+
await initLbug(REPO_ID, lbugPath);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Close the LadybugDB connection.
|
|
17
|
+
*/
|
|
18
|
+
export async function closeWikiDb() {
|
|
19
|
+
await closeLbug(REPO_ID);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get all source files with their exported symbol names and types.
|
|
23
|
+
*/
|
|
24
|
+
export async function getFilesWithExports() {
|
|
25
|
+
const rows = await executeQuery(REPO_ID, `
|
|
26
|
+
MATCH (f:File)-[:CodeRelation {type: 'DEFINES'}]->(n)
|
|
27
|
+
WHERE n.isExported = true
|
|
28
|
+
RETURN f.filePath AS filePath, n.name AS name, labels(n)[0] AS type
|
|
29
|
+
ORDER BY f.filePath
|
|
30
|
+
`);
|
|
31
|
+
const fileMap = new Map();
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
const fp = row.filePath || row[0];
|
|
34
|
+
const name = row.name || row[1];
|
|
35
|
+
const type = row.type || row[2];
|
|
36
|
+
let entry = fileMap.get(fp);
|
|
37
|
+
if (!entry) {
|
|
38
|
+
entry = { filePath: fp, symbols: [] };
|
|
39
|
+
fileMap.set(fp, entry);
|
|
40
|
+
}
|
|
41
|
+
entry.symbols.push({ name, type });
|
|
42
|
+
}
|
|
43
|
+
return Array.from(fileMap.values());
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get all files tracked in the graph (including those with no exports).
|
|
47
|
+
*/
|
|
48
|
+
export async function getAllFiles() {
|
|
49
|
+
const rows = await executeQuery(REPO_ID, `
|
|
50
|
+
MATCH (f:File)
|
|
51
|
+
RETURN f.filePath AS filePath
|
|
52
|
+
ORDER BY f.filePath
|
|
53
|
+
`);
|
|
54
|
+
return rows.map(r => r.filePath || r[0]);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get inter-file call edges (calls between different files).
|
|
58
|
+
*/
|
|
59
|
+
export async function getInterFileCallEdges() {
|
|
60
|
+
const rows = await executeQuery(REPO_ID, `
|
|
61
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
62
|
+
WHERE a.filePath <> b.filePath
|
|
63
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
64
|
+
b.filePath AS toFile, b.name AS toName
|
|
65
|
+
`);
|
|
66
|
+
return rows.map(r => ({
|
|
67
|
+
fromFile: r.fromFile || r[0],
|
|
68
|
+
fromName: r.fromName || r[1],
|
|
69
|
+
toFile: r.toFile || r[2],
|
|
70
|
+
toName: r.toName || r[3],
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get call edges between files within a specific set (intra-module).
|
|
75
|
+
*/
|
|
76
|
+
export async function getIntraModuleCallEdges(filePaths) {
|
|
77
|
+
if (filePaths.length === 0)
|
|
78
|
+
return [];
|
|
79
|
+
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
80
|
+
const rows = await executeQuery(REPO_ID, `
|
|
81
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
82
|
+
WHERE a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
83
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
84
|
+
b.filePath AS toFile, b.name AS toName
|
|
85
|
+
`);
|
|
86
|
+
return rows.map(r => ({
|
|
87
|
+
fromFile: r.fromFile || r[0],
|
|
88
|
+
fromName: r.fromName || r[1],
|
|
89
|
+
toFile: r.toFile || r[2],
|
|
90
|
+
toName: r.toName || r[3],
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get call edges crossing module boundaries (external calls from/to module files).
|
|
95
|
+
*/
|
|
96
|
+
export async function getInterModuleCallEdges(filePaths) {
|
|
97
|
+
if (filePaths.length === 0)
|
|
98
|
+
return { outgoing: [], incoming: [] };
|
|
99
|
+
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
100
|
+
const outRows = await executeQuery(REPO_ID, `
|
|
101
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
102
|
+
WHERE a.filePath IN [${fileList}] AND NOT b.filePath IN [${fileList}]
|
|
103
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
104
|
+
b.filePath AS toFile, b.name AS toName
|
|
105
|
+
LIMIT 30
|
|
106
|
+
`);
|
|
107
|
+
const inRows = await executeQuery(REPO_ID, `
|
|
108
|
+
MATCH (a)-[:CodeRelation {type: 'CALLS'}]->(b)
|
|
109
|
+
WHERE NOT a.filePath IN [${fileList}] AND b.filePath IN [${fileList}]
|
|
110
|
+
RETURN DISTINCT a.filePath AS fromFile, a.name AS fromName,
|
|
111
|
+
b.filePath AS toFile, b.name AS toName
|
|
112
|
+
LIMIT 30
|
|
113
|
+
`);
|
|
114
|
+
return {
|
|
115
|
+
outgoing: outRows.map(r => ({
|
|
116
|
+
fromFile: r.fromFile || r[0],
|
|
117
|
+
fromName: r.fromName || r[1],
|
|
118
|
+
toFile: r.toFile || r[2],
|
|
119
|
+
toName: r.toName || r[3],
|
|
120
|
+
})),
|
|
121
|
+
incoming: inRows.map(r => ({
|
|
122
|
+
fromFile: r.fromFile || r[0],
|
|
123
|
+
fromName: r.fromName || r[1],
|
|
124
|
+
toFile: r.toFile || r[2],
|
|
125
|
+
toName: r.toName || r[3],
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get processes (execution flows) that pass through a set of files.
|
|
131
|
+
* Returns top N by step count.
|
|
132
|
+
*/
|
|
133
|
+
export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
134
|
+
if (filePaths.length === 0)
|
|
135
|
+
return [];
|
|
136
|
+
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
137
|
+
// Find processes that have steps in the given files
|
|
138
|
+
const procRows = await executeQuery(REPO_ID, `
|
|
139
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
|
140
|
+
WHERE s.filePath IN [${fileList}]
|
|
141
|
+
RETURN DISTINCT p.id AS id, p.heuristicLabel AS label,
|
|
142
|
+
p.processType AS type, p.stepCount AS stepCount
|
|
143
|
+
ORDER BY stepCount DESC
|
|
144
|
+
LIMIT ${limit}
|
|
145
|
+
`);
|
|
146
|
+
const processes = [];
|
|
147
|
+
for (const row of procRows) {
|
|
148
|
+
const procId = row.id || row[0];
|
|
149
|
+
const label = row.label || row[1] || procId;
|
|
150
|
+
const type = row.type || row[2] || 'unknown';
|
|
151
|
+
const stepCount = row.stepCount || row[3] || 0;
|
|
152
|
+
// Get the full step trace for this process
|
|
153
|
+
const stepRows = await executeQuery(REPO_ID, `
|
|
154
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
155
|
+
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
156
|
+
ORDER BY r.step
|
|
157
|
+
`);
|
|
158
|
+
processes.push({
|
|
159
|
+
id: procId,
|
|
160
|
+
label,
|
|
161
|
+
type,
|
|
162
|
+
stepCount,
|
|
163
|
+
steps: stepRows.map(s => ({
|
|
164
|
+
step: s.step || s[3] || 0,
|
|
165
|
+
name: s.name || s[0],
|
|
166
|
+
filePath: s.filePath || s[1],
|
|
167
|
+
type: s.type || s[2],
|
|
168
|
+
})),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return processes;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get all processes in the graph (for overview page).
|
|
175
|
+
*/
|
|
176
|
+
export async function getAllProcesses(limit = 20) {
|
|
177
|
+
const procRows = await executeQuery(REPO_ID, `
|
|
178
|
+
MATCH (p:Process)
|
|
179
|
+
RETURN p.id AS id, p.heuristicLabel AS label,
|
|
180
|
+
p.processType AS type, p.stepCount AS stepCount
|
|
181
|
+
ORDER BY stepCount DESC
|
|
182
|
+
LIMIT ${limit}
|
|
183
|
+
`);
|
|
184
|
+
const processes = [];
|
|
185
|
+
for (const row of procRows) {
|
|
186
|
+
const procId = row.id || row[0];
|
|
187
|
+
const label = row.label || row[1] || procId;
|
|
188
|
+
const type = row.type || row[2] || 'unknown';
|
|
189
|
+
const stepCount = row.stepCount || row[3] || 0;
|
|
190
|
+
const stepRows = await executeQuery(REPO_ID, `
|
|
191
|
+
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process {id: '${procId.replace(/'/g, "''")}'})
|
|
192
|
+
RETURN s.name AS name, s.filePath AS filePath, labels(s)[0] AS type, r.step AS step
|
|
193
|
+
ORDER BY r.step
|
|
194
|
+
`);
|
|
195
|
+
processes.push({
|
|
196
|
+
id: procId,
|
|
197
|
+
label,
|
|
198
|
+
type,
|
|
199
|
+
stepCount,
|
|
200
|
+
steps: stepRows.map(s => ({
|
|
201
|
+
step: s.step || s[3] || 0,
|
|
202
|
+
name: s.name || s[0],
|
|
203
|
+
filePath: s.filePath || s[1],
|
|
204
|
+
type: s.type || s[2],
|
|
205
|
+
})),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return processes;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get inter-module edges for overview architecture diagram.
|
|
212
|
+
* Groups call edges by source/target module.
|
|
213
|
+
*/
|
|
214
|
+
export async function getInterModuleEdgesForOverview(moduleFiles) {
|
|
215
|
+
// Build file-to-module lookup
|
|
216
|
+
const fileToModule = new Map();
|
|
217
|
+
for (const [mod, files] of Object.entries(moduleFiles)) {
|
|
218
|
+
for (const f of files) {
|
|
219
|
+
fileToModule.set(f, mod);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const allEdges = await getInterFileCallEdges();
|
|
223
|
+
const moduleEdgeCounts = new Map();
|
|
224
|
+
for (const edge of allEdges) {
|
|
225
|
+
const fromMod = fileToModule.get(edge.fromFile);
|
|
226
|
+
const toMod = fileToModule.get(edge.toFile);
|
|
227
|
+
if (fromMod && toMod && fromMod !== toMod) {
|
|
228
|
+
const key = `${fromMod}|||${toMod}`;
|
|
229
|
+
moduleEdgeCounts.set(key, (moduleEdgeCounts.get(key) || 0) + 1);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return Array.from(moduleEdgeCounts.entries())
|
|
233
|
+
.map(([key, count]) => {
|
|
234
|
+
const [from, to] = key.split('|||');
|
|
235
|
+
return { from, to, count };
|
|
236
|
+
})
|
|
237
|
+
.sort((a, b) => b.count - a.count);
|
|
238
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Viewer Generator for Wiki
|
|
3
|
+
*
|
|
4
|
+
* Produces a self-contained index.html that embeds all markdown pages,
|
|
5
|
+
* module tree, and metadata — viewable offline in any browser.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Generate the wiki HTML viewer (index.html) from existing markdown pages.
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateHTMLViewer(wikiDir: string, projectName: string): Promise<string>;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Viewer Generator for Wiki
|
|
3
|
+
*
|
|
4
|
+
* Produces a self-contained index.html that embeds all markdown pages,
|
|
5
|
+
* module tree, and metadata — viewable offline in any browser.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
/**
|
|
10
|
+
* Generate the wiki HTML viewer (index.html) from existing markdown pages.
|
|
11
|
+
*/
|
|
12
|
+
export async function generateHTMLViewer(wikiDir, projectName) {
|
|
13
|
+
// Load module tree
|
|
14
|
+
let moduleTree = [];
|
|
15
|
+
try {
|
|
16
|
+
const raw = await fs.readFile(path.join(wikiDir, 'module_tree.json'), 'utf-8');
|
|
17
|
+
moduleTree = JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
catch { /* will show empty nav */ }
|
|
20
|
+
// Load meta
|
|
21
|
+
let meta = null;
|
|
22
|
+
try {
|
|
23
|
+
const raw = await fs.readFile(path.join(wikiDir, 'meta.json'), 'utf-8');
|
|
24
|
+
meta = JSON.parse(raw);
|
|
25
|
+
}
|
|
26
|
+
catch { /* no meta */ }
|
|
27
|
+
// Read all markdown files into a { slug: content } map
|
|
28
|
+
const pages = {};
|
|
29
|
+
const dirEntries = await fs.readdir(wikiDir);
|
|
30
|
+
for (const f of dirEntries.filter(f => f.endsWith('.md'))) {
|
|
31
|
+
const content = await fs.readFile(path.join(wikiDir, f), 'utf-8');
|
|
32
|
+
pages[f.replace(/\.md$/, '')] = content;
|
|
33
|
+
}
|
|
34
|
+
const html = buildHTML(projectName, moduleTree, pages, meta);
|
|
35
|
+
const outputPath = path.join(wikiDir, 'index.html');
|
|
36
|
+
await fs.writeFile(outputPath, html, 'utf-8');
|
|
37
|
+
return outputPath;
|
|
38
|
+
}
|
|
39
|
+
// ─── HTML Builder ───────────────────────────────────────────────────────
|
|
40
|
+
function esc(text) {
|
|
41
|
+
return text
|
|
42
|
+
.replace(/&/g, '&')
|
|
43
|
+
.replace(/</g, '<')
|
|
44
|
+
.replace(/>/g, '>')
|
|
45
|
+
.replace(/"/g, '"');
|
|
46
|
+
}
|
|
47
|
+
function buildHTML(projectName, moduleTree, pages, meta) {
|
|
48
|
+
// Embed data as JSON inside the HTML
|
|
49
|
+
const pagesJSON = JSON.stringify(pages);
|
|
50
|
+
const treeJSON = JSON.stringify(moduleTree);
|
|
51
|
+
const metaJSON = JSON.stringify(meta);
|
|
52
|
+
const parts = [];
|
|
53
|
+
// ── Head ──
|
|
54
|
+
parts.push('<!DOCTYPE html>');
|
|
55
|
+
parts.push('<html lang="en">');
|
|
56
|
+
parts.push('<head>');
|
|
57
|
+
parts.push('<meta charset="UTF-8">');
|
|
58
|
+
parts.push('<meta name="viewport" content="width=device-width, initial-scale=1.0">');
|
|
59
|
+
parts.push('<title>' + esc(projectName) + ' — Wiki</title>');
|
|
60
|
+
parts.push('<script src="https://cdn.jsdelivr.net/npm/marked@11.0.0/marked.min.js"><\/script>');
|
|
61
|
+
parts.push('<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"><\/script>');
|
|
62
|
+
parts.push('<style>');
|
|
63
|
+
parts.push(CSS);
|
|
64
|
+
parts.push('</style>');
|
|
65
|
+
parts.push('</head>');
|
|
66
|
+
// ── Body ──
|
|
67
|
+
parts.push('<body>');
|
|
68
|
+
parts.push('<button class="menu-toggle" id="menu-toggle" aria-label="Toggle menu">☰</button>');
|
|
69
|
+
parts.push('<div class="layout">');
|
|
70
|
+
// Sidebar
|
|
71
|
+
parts.push('<nav class="sidebar" id="sidebar">');
|
|
72
|
+
parts.push('<div class="sidebar-header">');
|
|
73
|
+
parts.push('<div class="sidebar-title">');
|
|
74
|
+
parts.push(BOOK_SVG);
|
|
75
|
+
parts.push(esc(projectName));
|
|
76
|
+
parts.push('</div>');
|
|
77
|
+
parts.push('<div class="sidebar-meta" id="meta-info"></div>');
|
|
78
|
+
parts.push('</div>');
|
|
79
|
+
parts.push('<div id="nav-tree"></div>');
|
|
80
|
+
parts.push('<div class="sidebar-footer">Generated by GitNexus</div>');
|
|
81
|
+
parts.push('</nav>');
|
|
82
|
+
// Content
|
|
83
|
+
parts.push('<main class="content" id="content">');
|
|
84
|
+
parts.push('<div class="empty-state"><h2>Loading…</h2></div>');
|
|
85
|
+
parts.push('</main>');
|
|
86
|
+
parts.push('</div>');
|
|
87
|
+
// ── Script ──
|
|
88
|
+
parts.push('<script>');
|
|
89
|
+
parts.push('var PAGES = ' + pagesJSON + ';');
|
|
90
|
+
parts.push('var TREE = ' + treeJSON + ';');
|
|
91
|
+
parts.push('var META = ' + metaJSON + ';');
|
|
92
|
+
parts.push(JS_APP);
|
|
93
|
+
parts.push('<\/script>');
|
|
94
|
+
parts.push('</body>');
|
|
95
|
+
parts.push('</html>');
|
|
96
|
+
return parts.join('\n');
|
|
97
|
+
}
|
|
98
|
+
// ─── Static Assets ────────────────────────────────────────────────────
|
|
99
|
+
const BOOK_SVG = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' +
|
|
100
|
+
'<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/>' +
|
|
101
|
+
'<path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/>' +
|
|
102
|
+
'</svg>';
|
|
103
|
+
const CSS = `
|
|
104
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
105
|
+
:root{
|
|
106
|
+
--bg:#ffffff;--sidebar-bg:#f8f9fb;--border:#e5e7eb;
|
|
107
|
+
--text:#1e293b;--text-muted:#64748b;--primary:#2563eb;
|
|
108
|
+
--primary-soft:#eff6ff;--hover:#f1f5f9;--code-bg:#f1f5f9;
|
|
109
|
+
--radius:8px;--shadow:0 1px 3px rgba(0,0,0,.08);
|
|
110
|
+
}
|
|
111
|
+
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
|
|
112
|
+
line-height:1.65;color:var(--text);background:var(--bg)}
|
|
113
|
+
|
|
114
|
+
.layout{display:flex;min-height:100vh}
|
|
115
|
+
.sidebar{width:280px;background:var(--sidebar-bg);border-right:1px solid var(--border);
|
|
116
|
+
position:fixed;top:0;left:0;bottom:0;overflow-y:auto;padding:24px 16px;
|
|
117
|
+
display:flex;flex-direction:column;z-index:10}
|
|
118
|
+
.content{margin-left:280px;flex:1;padding:48px 64px;max-width:960px}
|
|
119
|
+
|
|
120
|
+
.sidebar-header{margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--border)}
|
|
121
|
+
.sidebar-title{font-size:16px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}
|
|
122
|
+
.sidebar-title svg{flex-shrink:0}
|
|
123
|
+
.sidebar-meta{font-size:11px;color:var(--text-muted);margin-top:6px}
|
|
124
|
+
.nav-section{margin-bottom:2px}
|
|
125
|
+
.nav-item{display:block;padding:7px 12px;border-radius:var(--radius);cursor:pointer;
|
|
126
|
+
font-size:13px;color:var(--text);text-decoration:none;transition:all .15s;
|
|
127
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
128
|
+
.nav-item:hover{background:var(--hover)}
|
|
129
|
+
.nav-item.active{background:var(--primary-soft);color:var(--primary);font-weight:600}
|
|
130
|
+
.nav-item.overview{font-weight:600;margin-bottom:4px}
|
|
131
|
+
.nav-children{padding-left:14px;border-left:1px solid var(--border);margin-left:12px}
|
|
132
|
+
.nav-group-label{font-size:11px;font-weight:600;color:var(--text-muted);
|
|
133
|
+
text-transform:uppercase;letter-spacing:.5px;padding:12px 12px 4px;user-select:none}
|
|
134
|
+
.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);
|
|
135
|
+
font-size:11px;color:var(--text-muted);text-align:center}
|
|
136
|
+
|
|
137
|
+
.content h1{font-size:28px;font-weight:700;margin-bottom:8px;line-height:1.3}
|
|
138
|
+
.content h2{font-size:22px;font-weight:600;margin:32px 0 12px;padding-bottom:6px;border-bottom:1px solid var(--border)}
|
|
139
|
+
.content h3{font-size:17px;font-weight:600;margin:24px 0 8px}
|
|
140
|
+
.content h4{font-size:15px;font-weight:600;margin:20px 0 6px}
|
|
141
|
+
.content p{margin:12px 0}
|
|
142
|
+
.content ul,.content ol{margin:12px 0 12px 24px}
|
|
143
|
+
.content li{margin:4px 0}
|
|
144
|
+
.content a{color:var(--primary);text-decoration:none}
|
|
145
|
+
.content a:hover{text-decoration:underline}
|
|
146
|
+
.content blockquote{border-left:3px solid var(--primary);padding:8px 16px;margin:16px 0;
|
|
147
|
+
background:var(--primary-soft);border-radius:0 var(--radius) var(--radius) 0;
|
|
148
|
+
color:var(--text-muted);font-size:14px}
|
|
149
|
+
.content code{font-family:'SF Mono',Consolas,'Courier New',monospace;font-size:13px;
|
|
150
|
+
background:var(--code-bg);padding:2px 6px;border-radius:4px}
|
|
151
|
+
.content pre{background:#1e293b;color:#e2e8f0;border-radius:var(--radius);padding:16px;
|
|
152
|
+
overflow-x:auto;margin:16px 0}
|
|
153
|
+
.content pre code{background:none;padding:0;font-size:13px;line-height:1.6;color:inherit}
|
|
154
|
+
.content table{border-collapse:collapse;width:100%;margin:16px 0}
|
|
155
|
+
.content th,.content td{border:1px solid var(--border);padding:8px 12px;text-align:left;font-size:14px}
|
|
156
|
+
.content th{background:var(--sidebar-bg);font-weight:600}
|
|
157
|
+
.content img{max-width:100%;border-radius:var(--radius)}
|
|
158
|
+
.content hr{border:none;border-top:1px solid var(--border);margin:32px 0}
|
|
159
|
+
.content .mermaid{margin:20px 0;text-align:center}
|
|
160
|
+
|
|
161
|
+
.menu-toggle{display:none;position:fixed;top:12px;left:12px;z-index:20;
|
|
162
|
+
background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);
|
|
163
|
+
padding:8px 12px;cursor:pointer;font-size:18px;box-shadow:var(--shadow)}
|
|
164
|
+
@media(max-width:768px){
|
|
165
|
+
.sidebar{transform:translateX(-100%);transition:transform .2s}
|
|
166
|
+
.sidebar.open{transform:translateX(0);box-shadow:2px 0 12px rgba(0,0,0,.1)}
|
|
167
|
+
.content{margin-left:0;padding:24px 20px;padding-top:56px}
|
|
168
|
+
.menu-toggle{display:block}
|
|
169
|
+
}
|
|
170
|
+
.empty-state{text-align:center;padding:80px 20px;color:var(--text-muted)}
|
|
171
|
+
.empty-state h2{font-size:20px;margin-bottom:8px;border:none}
|
|
172
|
+
`;
|
|
173
|
+
// The client-side JS is kept as a plain string to avoid template literal conflicts
|
|
174
|
+
const JS_APP = `
|
|
175
|
+
(function() {
|
|
176
|
+
var activePage = 'overview';
|
|
177
|
+
|
|
178
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
179
|
+
mermaid.initialize({ startOnLoad: false, theme: 'neutral', securityLevel: 'loose' });
|
|
180
|
+
renderMeta();
|
|
181
|
+
renderNav();
|
|
182
|
+
document.getElementById('menu-toggle').addEventListener('click', function() {
|
|
183
|
+
document.getElementById('sidebar').classList.toggle('open');
|
|
184
|
+
});
|
|
185
|
+
if (location.hash && location.hash.length > 1) {
|
|
186
|
+
activePage = decodeURIComponent(location.hash.slice(1));
|
|
187
|
+
}
|
|
188
|
+
navigateTo(activePage);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
function renderMeta() {
|
|
192
|
+
if (!META) return;
|
|
193
|
+
var el = document.getElementById('meta-info');
|
|
194
|
+
var parts = [];
|
|
195
|
+
if (META.generatedAt) {
|
|
196
|
+
parts.push(new Date(META.generatedAt).toLocaleDateString());
|
|
197
|
+
}
|
|
198
|
+
if (META.model) parts.push(META.model);
|
|
199
|
+
if (META.fromCommit) parts.push(META.fromCommit.slice(0, 8));
|
|
200
|
+
el.textContent = parts.join(' \\u00b7 ');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderNav() {
|
|
204
|
+
var container = document.getElementById('nav-tree');
|
|
205
|
+
var html = '<div class="nav-section">';
|
|
206
|
+
html += '<a class="nav-item overview" data-page="overview" href="#overview">Overview</a>';
|
|
207
|
+
html += '</div>';
|
|
208
|
+
if (TREE.length > 0) {
|
|
209
|
+
html += '<div class="nav-group-label">Modules</div>';
|
|
210
|
+
html += buildNavTree(TREE);
|
|
211
|
+
}
|
|
212
|
+
container.innerHTML = html;
|
|
213
|
+
container.addEventListener('click', function(e) {
|
|
214
|
+
var target = e.target;
|
|
215
|
+
while (target && !target.dataset.page) { target = target.parentElement; }
|
|
216
|
+
if (target && target.dataset.page) {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
navigateTo(target.dataset.page);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function buildNavTree(nodes) {
|
|
224
|
+
var html = '';
|
|
225
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
226
|
+
var node = nodes[i];
|
|
227
|
+
html += '<div class="nav-section">';
|
|
228
|
+
html += '<a class="nav-item" data-page="' + escH(node.slug) + '" href="#' + encodeURIComponent(node.slug) + '">' + escH(node.name) + '</a>';
|
|
229
|
+
if (node.children && node.children.length > 0) {
|
|
230
|
+
html += '<div class="nav-children">' + buildNavTree(node.children) + '</div>';
|
|
231
|
+
}
|
|
232
|
+
html += '</div>';
|
|
233
|
+
}
|
|
234
|
+
return html;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function escH(s) {
|
|
238
|
+
var d = document.createElement('div');
|
|
239
|
+
d.textContent = s;
|
|
240
|
+
return d.innerHTML;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function navigateTo(page) {
|
|
244
|
+
activePage = page;
|
|
245
|
+
location.hash = encodeURIComponent(page);
|
|
246
|
+
|
|
247
|
+
var items = document.querySelectorAll('.nav-item');
|
|
248
|
+
for (var i = 0; i < items.length; i++) {
|
|
249
|
+
if (items[i].dataset.page === page) {
|
|
250
|
+
items[i].classList.add('active');
|
|
251
|
+
} else {
|
|
252
|
+
items[i].classList.remove('active');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
var contentEl = document.getElementById('content');
|
|
257
|
+
var md = PAGES[page];
|
|
258
|
+
|
|
259
|
+
if (!md) {
|
|
260
|
+
contentEl.innerHTML = '<div class="empty-state"><h2>Page not found</h2><p>' + escH(page) + '.md does not exist.</p></div>';
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
contentEl.innerHTML = marked.parse(md);
|
|
265
|
+
|
|
266
|
+
// Rewrite .md links to hash navigation
|
|
267
|
+
var links = contentEl.querySelectorAll('a[href]');
|
|
268
|
+
for (var i = 0; i < links.length; i++) {
|
|
269
|
+
var href = links[i].getAttribute('href');
|
|
270
|
+
if (href && href.endsWith('.md') && href.indexOf('://') === -1) {
|
|
271
|
+
var slug = href.replace(/\\.md$/, '');
|
|
272
|
+
links[i].setAttribute('href', '#' + encodeURIComponent(slug));
|
|
273
|
+
(function(s) {
|
|
274
|
+
links[i].addEventListener('click', function(e) {
|
|
275
|
+
e.preventDefault();
|
|
276
|
+
navigateTo(s);
|
|
277
|
+
});
|
|
278
|
+
})(slug);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Convert mermaid code blocks into mermaid divs
|
|
283
|
+
var mermaidBlocks = contentEl.querySelectorAll('pre code.language-mermaid');
|
|
284
|
+
for (var i = 0; i < mermaidBlocks.length; i++) {
|
|
285
|
+
var pre = mermaidBlocks[i].parentElement;
|
|
286
|
+
var div = document.createElement('div');
|
|
287
|
+
div.className = 'mermaid';
|
|
288
|
+
div.textContent = mermaidBlocks[i].textContent;
|
|
289
|
+
pre.parentNode.replaceChild(div, pre);
|
|
290
|
+
}
|
|
291
|
+
try { mermaid.run({ querySelector: '.mermaid' }); } catch(e) {}
|
|
292
|
+
|
|
293
|
+
window.scrollTo(0, 0);
|
|
294
|
+
document.getElementById('sidebar').classList.remove('open');
|
|
295
|
+
}
|
|
296
|
+
})();
|
|
297
|
+
`;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Client for Wiki Generation
|
|
3
|
+
*
|
|
4
|
+
* OpenAI-compatible API client using native fetch.
|
|
5
|
+
* Supports OpenAI, Azure, LiteLLM, Ollama, and any OpenAI-compatible endpoint.
|
|
6
|
+
*
|
|
7
|
+
* Config priority: CLI flags > env vars > defaults
|
|
8
|
+
*/
|
|
9
|
+
export interface LLMConfig {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
model: string;
|
|
13
|
+
maxTokens: number;
|
|
14
|
+
temperature: number;
|
|
15
|
+
gatewayApiKey?: string;
|
|
16
|
+
gatewayCredential?: string;
|
|
17
|
+
gatewayGroup?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface LLMResponse {
|
|
20
|
+
content: string;
|
|
21
|
+
promptTokens?: number;
|
|
22
|
+
completionTokens?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve LLM configuration from env vars, saved config, and optional overrides.
|
|
26
|
+
* Priority: overrides (CLI flags) > env vars > ~/.gitnexus/config.json > error
|
|
27
|
+
*
|
|
28
|
+
* If no API key is found, returns config with empty apiKey (caller should handle).
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveLLMConfig(overrides?: Partial<LLMConfig>): Promise<LLMConfig>;
|
|
31
|
+
/**
|
|
32
|
+
* Estimate token count from text (rough heuristic: ~4 chars per token).
|
|
33
|
+
*/
|
|
34
|
+
export declare function estimateTokens(text: string): number;
|
|
35
|
+
export interface CallLLMOptions {
|
|
36
|
+
onChunk?: (charsReceived: number) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Call an OpenAI-compatible LLM API.
|
|
40
|
+
* Uses streaming when onChunk callback is provided for real-time progress.
|
|
41
|
+
* Retries up to 3 times on transient failures (429, 5xx, network errors).
|
|
42
|
+
*/
|
|
43
|
+
export declare function callLLM(prompt: string, config: LLMConfig, systemPrompt?: string, options?: CallLLMOptions): Promise<LLMResponse>;
|