gitnexus 1.2.7 → 1.2.8
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 +186 -186
- package/dist/cli/ai-context.js +71 -71
- package/dist/cli/analyze.d.ts +1 -1
- package/dist/cli/analyze.js +59 -15
- package/dist/cli/index.js +1 -1
- package/dist/core/augmentation/engine.js +20 -20
- package/dist/core/embeddings/embedder.js +1 -0
- package/dist/core/embeddings/embedding-pipeline.js +26 -26
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- package/dist/core/ingestion/tree-sitter-queries.js +282 -282
- package/dist/core/kuzu/kuzu-adapter.js +9 -9
- package/dist/core/kuzu/schema.js +256 -256
- package/dist/core/search/bm25-index.js +5 -5
- package/dist/core/search/hybrid-search.js +3 -3
- package/dist/core/wiki/graph-queries.js +52 -52
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/prompts.js +82 -82
- package/dist/mcp/local/local-backend.js +112 -112
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +16 -16
- package/dist/mcp/tools.js +77 -77
- package/hooks/claude/gitnexus-hook.cjs +135 -135
- package/hooks/claude/pre-tool-use.sh +78 -78
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +82 -82
- package/skills/debugging.md +85 -85
- package/skills/exploring.md +75 -75
- package/skills/impact-analysis.md +94 -94
- package/skills/refactoring.md +113 -113
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
|
@@ -22,11 +22,11 @@ export async function closeWikiDb() {
|
|
|
22
22
|
* Get all source files with their exported symbol names and types.
|
|
23
23
|
*/
|
|
24
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
|
|
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
30
|
`);
|
|
31
31
|
const fileMap = new Map();
|
|
32
32
|
for (const row of rows) {
|
|
@@ -46,10 +46,10 @@ export async function getFilesWithExports() {
|
|
|
46
46
|
* Get all files tracked in the graph (including those with no exports).
|
|
47
47
|
*/
|
|
48
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
|
|
49
|
+
const rows = await executeQuery(REPO_ID, `
|
|
50
|
+
MATCH (f:File)
|
|
51
|
+
RETURN f.filePath AS filePath
|
|
52
|
+
ORDER BY f.filePath
|
|
53
53
|
`);
|
|
54
54
|
return rows.map(r => r.filePath || r[0]);
|
|
55
55
|
}
|
|
@@ -57,11 +57,11 @@ export async function getAllFiles() {
|
|
|
57
57
|
* Get inter-file call edges (calls between different files).
|
|
58
58
|
*/
|
|
59
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
|
|
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
65
|
`);
|
|
66
66
|
return rows.map(r => ({
|
|
67
67
|
fromFile: r.fromFile || r[0],
|
|
@@ -77,11 +77,11 @@ export async function getIntraModuleCallEdges(filePaths) {
|
|
|
77
77
|
if (filePaths.length === 0)
|
|
78
78
|
return [];
|
|
79
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
|
|
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
85
|
`);
|
|
86
86
|
return rows.map(r => ({
|
|
87
87
|
fromFile: r.fromFile || r[0],
|
|
@@ -97,19 +97,19 @@ export async function getInterModuleCallEdges(filePaths) {
|
|
|
97
97
|
if (filePaths.length === 0)
|
|
98
98
|
return { outgoing: [], incoming: [] };
|
|
99
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
|
|
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
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
|
|
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
113
|
`);
|
|
114
114
|
return {
|
|
115
115
|
outgoing: outRows.map(r => ({
|
|
@@ -135,13 +135,13 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
|
135
135
|
return [];
|
|
136
136
|
const fileList = filePaths.map(f => `'${f.replace(/'/g, "''")}'`).join(', ');
|
|
137
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}
|
|
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
145
|
`);
|
|
146
146
|
const processes = [];
|
|
147
147
|
for (const row of procRows) {
|
|
@@ -150,10 +150,10 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
|
150
150
|
const type = row.type || row[2] || 'unknown';
|
|
151
151
|
const stepCount = row.stepCount || row[3] || 0;
|
|
152
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
|
|
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
157
|
`);
|
|
158
158
|
processes.push({
|
|
159
159
|
id: procId,
|
|
@@ -174,12 +174,12 @@ export async function getProcessesForFiles(filePaths, limit = 5) {
|
|
|
174
174
|
* Get all processes in the graph (for overview page).
|
|
175
175
|
*/
|
|
176
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}
|
|
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
183
|
`);
|
|
184
184
|
const processes = [];
|
|
185
185
|
for (const row of procRows) {
|
|
@@ -187,10 +187,10 @@ export async function getAllProcesses(limit = 20) {
|
|
|
187
187
|
const label = row.label || row[1] || procId;
|
|
188
188
|
const type = row.type || row[2] || 'unknown';
|
|
189
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
|
|
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
194
|
`);
|
|
195
195
|
processes.push({
|
|
196
196
|
id: procId,
|
|
@@ -100,198 +100,198 @@ const BOOK_SVG = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" st
|
|
|
100
100
|
'<path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/>' +
|
|
101
101
|
'<path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/>' +
|
|
102
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}
|
|
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
172
|
`;
|
|
173
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
|
-
})();
|
|
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
297
|
`;
|