gitnexus 1.4.1 → 1.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -194
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +117 -90
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +57 -30
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +1 -1
- package/dist/cli/eval-server.js +14 -6
- package/dist/cli/index.js +18 -25
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/setup.js +42 -32
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +3 -2
- package/dist/cli/tool.js +48 -13
- package/dist/cli/wiki.js +2 -2
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +1 -0
- package/dist/config/supported-languages.js +1 -1
- package/dist/core/augmentation/engine.js +99 -72
- package/dist/core/embeddings/embedder.d.ts +1 -1
- package/dist/core/embeddings/embedder.js +1 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
- package/dist/core/embeddings/embedding-pipeline.js +74 -47
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +5 -2
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/call-processor.d.ts +5 -7
- package/dist/core/ingestion/call-processor.js +430 -283
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- 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 +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +94 -24
- 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.js +4 -3
- package/dist/core/ingestion/framework-detection.d.ts +5 -1
- package/dist/core/ingestion/framework-detection.js +48 -8
- package/dist/core/ingestion/heritage-processor.d.ts +13 -5
- package/dist/core/ingestion/heritage-processor.js +109 -55
- package/dist/core/ingestion/import-processor.d.ts +16 -20
- package/dist/core/ingestion/import-processor.js +202 -696
- 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 +3 -11
- package/dist/core/ingestion/parsing-processor.js +85 -181
- package/dist/core/ingestion/pipeline.d.ts +5 -1
- package/dist/core/ingestion/pipeline.js +192 -116
- package/dist/core/ingestion/process-processor.js +2 -1
- 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/symbol-table.d.ts +21 -1
- package/dist/core/ingestion/symbol-table.js +40 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -11
- package/dist/core/ingestion/tree-sitter-queries.js +642 -485
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +611 -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 +406 -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 +449 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
- package/dist/core/ingestion/type-extractors/shared.js +703 -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 +98 -0
- package/dist/core/ingestion/utils.js +1064 -9
- package/dist/core/ingestion/workers/parse-worker.d.ts +38 -4
- package/dist/core/ingestion/workers/parse-worker.js +251 -359
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/csv-generator.js +20 -4
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +82 -82
- package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
- package/dist/core/{kuzu → lbug}/schema.js +304 -289
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +17 -16
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +9 -9
- package/dist/core/tree-sitter/parser-loader.js +9 -2
- package/dist/core/wiki/generator.d.ts +4 -52
- package/dist/core/wiki/generator.js +53 -552
- package/dist/core/wiki/graph-queries.d.ts +4 -46
- package/dist/core/wiki/graph-queries.js +103 -282
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/llm-client.js +11 -73
- package/dist/core/wiki/prompts.d.ts +8 -52
- package/dist/core/wiki/prompts.js +86 -200
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -9
- package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +77 -79
- package/dist/mcp/local/local-backend.d.ts +7 -6
- package/dist/mcp/local/local-backend.js +176 -147
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +18 -19
- package/dist/mcp/tools.js +103 -104
- package/dist/server/api.js +12 -12
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +1 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +55 -1
- package/dist/types/pipeline.d.ts +1 -1
- package/hooks/claude/gitnexus-hook.cjs +238 -155
- package/hooks/claude/pre-tool-use.sh +79 -79
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +99 -96
- package/scripts/patch-tree-sitter-swift.cjs +74 -74
- package/skills/gitnexus-cli.md +82 -82
- package/skills/gitnexus-debugging.md +89 -89
- package/skills/gitnexus-exploring.md +78 -78
- package/skills/gitnexus-guide.md +64 -64
- package/skills/gitnexus-impact-analysis.md +97 -97
- package/skills/gitnexus-pr-review.md +163 -163
- package/skills/gitnexus-refactoring.md +121 -121
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
- package/dist/core/wiki/diagrams.d.ts +0 -27
- package/dist/core/wiki/diagrams.js +0 -163
|
@@ -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
|
`;
|
|
@@ -62,11 +62,8 @@ export async function callLLM(prompt, config, systemPrompt, options) {
|
|
|
62
62
|
if (useStream)
|
|
63
63
|
body.stream = true;
|
|
64
64
|
const MAX_RETRIES = 3;
|
|
65
|
-
const FETCH_TIMEOUT_MS = 60_000; // 60s max to get response headers
|
|
66
65
|
let lastError = null;
|
|
67
66
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
68
|
-
const controller = new AbortController();
|
|
69
|
-
const fetchTimer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
70
67
|
try {
|
|
71
68
|
const response = await fetch(url, {
|
|
72
69
|
method: 'POST',
|
|
@@ -75,20 +72,17 @@ export async function callLLM(prompt, config, systemPrompt, options) {
|
|
|
75
72
|
'Authorization': `Bearer ${config.apiKey}`,
|
|
76
73
|
},
|
|
77
74
|
body: JSON.stringify(body),
|
|
78
|
-
signal: controller.signal,
|
|
79
75
|
});
|
|
80
|
-
clearTimeout(fetchTimer);
|
|
81
76
|
if (!response.ok) {
|
|
82
77
|
const errorText = await response.text().catch(() => 'unknown error');
|
|
83
|
-
// Rate limit —
|
|
84
|
-
// (the caller's runParallel handles concurrency reduction + re-queuing)
|
|
78
|
+
// Rate limit — wait with exponential backoff and retry
|
|
85
79
|
if (response.status === 429 && attempt < MAX_RETRIES - 1) {
|
|
86
80
|
const retryAfter = parseInt(response.headers.get('retry-after') || '0', 10);
|
|
87
|
-
const delay = retryAfter > 0 ?
|
|
81
|
+
const delay = retryAfter > 0 ? retryAfter * 1000 : (2 ** attempt) * 3000;
|
|
88
82
|
await sleep(delay);
|
|
89
83
|
continue;
|
|
90
84
|
}
|
|
91
|
-
// Server error — retry with
|
|
85
|
+
// Server error — retry with backoff
|
|
92
86
|
if (response.status >= 500 && attempt < MAX_RETRIES - 1) {
|
|
93
87
|
await sleep((attempt + 1) * 2000);
|
|
94
88
|
continue;
|
|
@@ -112,16 +106,9 @@ export async function callLLM(prompt, config, systemPrompt, options) {
|
|
|
112
106
|
};
|
|
113
107
|
}
|
|
114
108
|
catch (err) {
|
|
115
|
-
clearTimeout(fetchTimer);
|
|
116
109
|
lastError = err;
|
|
117
|
-
// Network
|
|
118
|
-
|
|
119
|
-
|| err.code === 'ETIMEDOUT'
|
|
120
|
-
|| err.name === 'AbortError'
|
|
121
|
-
|| err.message?.includes('abort')
|
|
122
|
-
|| err.message?.includes('fetch');
|
|
123
|
-
if (attempt < MAX_RETRIES - 1 && isRetryable) {
|
|
124
|
-
console.warn(`[llm] Attempt ${attempt + 1} failed (${err.message?.slice(0, 100)}), retrying...`);
|
|
110
|
+
// Network error — retry with backoff
|
|
111
|
+
if (attempt < MAX_RETRIES - 1 && (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT' || err.message?.includes('fetch'))) {
|
|
125
112
|
await sleep((attempt + 1) * 3000);
|
|
126
113
|
continue;
|
|
127
114
|
}
|
|
@@ -132,79 +119,38 @@ export async function callLLM(prompt, config, systemPrompt, options) {
|
|
|
132
119
|
}
|
|
133
120
|
/**
|
|
134
121
|
* Read an SSE stream from an OpenAI-compatible streaming response.
|
|
135
|
-
* Aborts if no chunk is received within STREAM_CHUNK_TIMEOUT_MS.
|
|
136
122
|
*/
|
|
137
|
-
const STREAM_READ_TIMEOUT_MS = 90_000; // 90s max wait for any bytes from connection
|
|
138
|
-
const CONTENT_STALL_TIMEOUT_MS = 120_000; // 120s max without new content tokens
|
|
139
123
|
async function readSSEStream(body, onChunk) {
|
|
140
124
|
const decoder = new TextDecoder();
|
|
141
125
|
const reader = body.getReader();
|
|
142
126
|
let content = '';
|
|
143
127
|
let buffer = '';
|
|
144
|
-
let lastContentTime = Date.now();
|
|
145
128
|
while (true) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
result = await withTimeout(reader.read(), STREAM_READ_TIMEOUT_MS, `LLM stream stalled — no data received for ${STREAM_READ_TIMEOUT_MS / 1000}s ` +
|
|
149
|
-
`(received ${content.length} chars so far). ` +
|
|
150
|
-
'This may indicate the model is overloaded or the connection was dropped.');
|
|
151
|
-
}
|
|
152
|
-
catch (err) {
|
|
153
|
-
reader.cancel().catch(() => { });
|
|
154
|
-
throw err;
|
|
155
|
-
}
|
|
156
|
-
const { done, value } = result;
|
|
157
|
-
if (done) {
|
|
158
|
-
const elapsed = ((Date.now() - lastContentTime) / 1000).toFixed(1);
|
|
159
|
-
console.log(`[sse] stream done — ${content.length} chars total, ${elapsed}s since last content`);
|
|
129
|
+
const { done, value } = await reader.read();
|
|
130
|
+
if (done)
|
|
160
131
|
break;
|
|
161
|
-
}
|
|
162
|
-
const rawChunk = decoder.decode(value, { stream: true });
|
|
163
|
-
buffer += rawChunk;
|
|
132
|
+
buffer += decoder.decode(value, { stream: true });
|
|
164
133
|
const lines = buffer.split('\n');
|
|
165
134
|
buffer = lines.pop() || '';
|
|
166
|
-
let gotContent = false;
|
|
167
135
|
for (const line of lines) {
|
|
168
136
|
const trimmed = line.trim();
|
|
169
|
-
if (!trimmed)
|
|
170
|
-
continue;
|
|
171
|
-
// Log non-data lines (keep-alives, comments, errors)
|
|
172
|
-
if (!trimmed.startsWith('data: ')) {
|
|
173
|
-
console.log(`[sse] non-data line: ${trimmed.slice(0, 200)}`);
|
|
137
|
+
if (!trimmed || !trimmed.startsWith('data: '))
|
|
174
138
|
continue;
|
|
175
|
-
}
|
|
176
139
|
const data = trimmed.slice(6);
|
|
177
|
-
if (data === '[DONE]')
|
|
178
|
-
console.log(`[sse] received [DONE] — ${content.length} chars total`);
|
|
140
|
+
if (data === '[DONE]')
|
|
179
141
|
continue;
|
|
180
|
-
}
|
|
181
142
|
try {
|
|
182
143
|
const parsed = JSON.parse(data);
|
|
183
144
|
const delta = parsed.choices?.[0]?.delta?.content;
|
|
184
|
-
const finishReason = parsed.choices?.[0]?.finish_reason;
|
|
185
|
-
if (finishReason) {
|
|
186
|
-
console.log(`[sse] finish_reason: ${finishReason} — ${content.length} chars`);
|
|
187
|
-
}
|
|
188
145
|
if (delta) {
|
|
189
146
|
content += delta;
|
|
190
|
-
gotContent = true;
|
|
191
147
|
onChunk(content.length);
|
|
192
148
|
}
|
|
193
149
|
}
|
|
194
150
|
catch {
|
|
195
|
-
|
|
151
|
+
// Skip malformed SSE chunks
|
|
196
152
|
}
|
|
197
153
|
}
|
|
198
|
-
if (gotContent) {
|
|
199
|
-
lastContentTime = Date.now();
|
|
200
|
-
}
|
|
201
|
-
else if (Date.now() - lastContentTime > CONTENT_STALL_TIMEOUT_MS) {
|
|
202
|
-
// Connection is alive (keep-alives arriving) but no actual tokens for too long
|
|
203
|
-
reader.cancel().catch(() => { });
|
|
204
|
-
throw new Error(`LLM stream content stalled — receiving keep-alives but no new tokens for ` +
|
|
205
|
-
`${CONTENT_STALL_TIMEOUT_MS / 1000}s (received ${content.length} chars total). ` +
|
|
206
|
-
'The model may have hit an internal limit or the provider dropped the generation.');
|
|
207
|
-
}
|
|
208
154
|
}
|
|
209
155
|
if (!content) {
|
|
210
156
|
throw new Error('LLM returned empty streaming response');
|
|
@@ -214,11 +160,3 @@ async function readSSEStream(body, onChunk) {
|
|
|
214
160
|
function sleep(ms) {
|
|
215
161
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
216
162
|
}
|
|
217
|
-
/** Race a promise against a timeout. Cleans up the timer on resolve to avoid leaks. */
|
|
218
|
-
function withTimeout(promise, ms, message) {
|
|
219
|
-
let timer;
|
|
220
|
-
const timeout = new Promise((_, reject) => {
|
|
221
|
-
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
222
|
-
});
|
|
223
|
-
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
224
|
-
}
|