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.
Files changed (169) hide show
  1. package/README.md +215 -194
  2. package/dist/cli/ai-context.d.ts +2 -1
  3. package/dist/cli/ai-context.js +117 -90
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +57 -30
  6. package/dist/cli/augment.js +1 -1
  7. package/dist/cli/eval-server.d.ts +1 -1
  8. package/dist/cli/eval-server.js +14 -6
  9. package/dist/cli/index.js +18 -25
  10. package/dist/cli/lazy-action.d.ts +6 -0
  11. package/dist/cli/lazy-action.js +18 -0
  12. package/dist/cli/mcp.js +1 -1
  13. package/dist/cli/setup.js +42 -32
  14. package/dist/cli/skill-gen.d.ts +26 -0
  15. package/dist/cli/skill-gen.js +549 -0
  16. package/dist/cli/status.js +13 -4
  17. package/dist/cli/tool.d.ts +3 -2
  18. package/dist/cli/tool.js +48 -13
  19. package/dist/cli/wiki.js +2 -2
  20. package/dist/config/ignore-service.d.ts +25 -0
  21. package/dist/config/ignore-service.js +76 -0
  22. package/dist/config/supported-languages.d.ts +1 -0
  23. package/dist/config/supported-languages.js +1 -1
  24. package/dist/core/augmentation/engine.js +99 -72
  25. package/dist/core/embeddings/embedder.d.ts +1 -1
  26. package/dist/core/embeddings/embedder.js +1 -1
  27. package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
  28. package/dist/core/embeddings/embedding-pipeline.js +74 -47
  29. package/dist/core/embeddings/types.d.ts +1 -1
  30. package/dist/core/graph/types.d.ts +5 -2
  31. package/dist/core/ingestion/ast-cache.js +3 -2
  32. package/dist/core/ingestion/call-processor.d.ts +5 -7
  33. package/dist/core/ingestion/call-processor.js +430 -283
  34. package/dist/core/ingestion/call-routing.d.ts +53 -0
  35. package/dist/core/ingestion/call-routing.js +108 -0
  36. package/dist/core/ingestion/cluster-enricher.js +16 -16
  37. package/dist/core/ingestion/constants.d.ts +16 -0
  38. package/dist/core/ingestion/constants.js +16 -0
  39. package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
  40. package/dist/core/ingestion/entry-point-scoring.js +94 -24
  41. package/dist/core/ingestion/export-detection.d.ts +18 -0
  42. package/dist/core/ingestion/export-detection.js +231 -0
  43. package/dist/core/ingestion/filesystem-walker.js +4 -3
  44. package/dist/core/ingestion/framework-detection.d.ts +5 -1
  45. package/dist/core/ingestion/framework-detection.js +48 -8
  46. package/dist/core/ingestion/heritage-processor.d.ts +13 -5
  47. package/dist/core/ingestion/heritage-processor.js +109 -55
  48. package/dist/core/ingestion/import-processor.d.ts +16 -20
  49. package/dist/core/ingestion/import-processor.js +202 -696
  50. package/dist/core/ingestion/language-config.d.ts +46 -0
  51. package/dist/core/ingestion/language-config.js +167 -0
  52. package/dist/core/ingestion/mro-processor.d.ts +45 -0
  53. package/dist/core/ingestion/mro-processor.js +369 -0
  54. package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
  55. package/dist/core/ingestion/named-binding-extraction.js +363 -0
  56. package/dist/core/ingestion/parsing-processor.d.ts +3 -11
  57. package/dist/core/ingestion/parsing-processor.js +85 -181
  58. package/dist/core/ingestion/pipeline.d.ts +5 -1
  59. package/dist/core/ingestion/pipeline.js +192 -116
  60. package/dist/core/ingestion/process-processor.js +2 -1
  61. package/dist/core/ingestion/resolution-context.d.ts +53 -0
  62. package/dist/core/ingestion/resolution-context.js +132 -0
  63. package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
  64. package/dist/core/ingestion/resolvers/csharp.js +109 -0
  65. package/dist/core/ingestion/resolvers/go.d.ts +19 -0
  66. package/dist/core/ingestion/resolvers/go.js +42 -0
  67. package/dist/core/ingestion/resolvers/index.d.ts +18 -0
  68. package/dist/core/ingestion/resolvers/index.js +13 -0
  69. package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
  70. package/dist/core/ingestion/resolvers/jvm.js +87 -0
  71. package/dist/core/ingestion/resolvers/php.d.ts +15 -0
  72. package/dist/core/ingestion/resolvers/php.js +35 -0
  73. package/dist/core/ingestion/resolvers/python.d.ts +19 -0
  74. package/dist/core/ingestion/resolvers/python.js +52 -0
  75. package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
  76. package/dist/core/ingestion/resolvers/ruby.js +15 -0
  77. package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
  78. package/dist/core/ingestion/resolvers/rust.js +73 -0
  79. package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
  80. package/dist/core/ingestion/resolvers/standard.js +123 -0
  81. package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
  82. package/dist/core/ingestion/resolvers/utils.js +122 -0
  83. package/dist/core/ingestion/symbol-table.d.ts +21 -1
  84. package/dist/core/ingestion/symbol-table.js +40 -12
  85. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -11
  86. package/dist/core/ingestion/tree-sitter-queries.js +642 -485
  87. package/dist/core/ingestion/type-env.d.ts +49 -0
  88. package/dist/core/ingestion/type-env.js +611 -0
  89. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
  90. package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
  91. package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
  92. package/dist/core/ingestion/type-extractors/csharp.js +383 -0
  93. package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
  94. package/dist/core/ingestion/type-extractors/go.js +467 -0
  95. package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
  96. package/dist/core/ingestion/type-extractors/index.js +31 -0
  97. package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
  98. package/dist/core/ingestion/type-extractors/jvm.js +681 -0
  99. package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
  100. package/dist/core/ingestion/type-extractors/php.js +549 -0
  101. package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
  102. package/dist/core/ingestion/type-extractors/python.js +406 -0
  103. package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
  104. package/dist/core/ingestion/type-extractors/ruby.js +389 -0
  105. package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
  106. package/dist/core/ingestion/type-extractors/rust.js +449 -0
  107. package/dist/core/ingestion/type-extractors/shared.d.ts +133 -0
  108. package/dist/core/ingestion/type-extractors/shared.js +703 -0
  109. package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
  110. package/dist/core/ingestion/type-extractors/swift.js +137 -0
  111. package/dist/core/ingestion/type-extractors/types.d.ts +127 -0
  112. package/dist/core/ingestion/type-extractors/types.js +1 -0
  113. package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
  114. package/dist/core/ingestion/type-extractors/typescript.js +494 -0
  115. package/dist/core/ingestion/utils.d.ts +98 -0
  116. package/dist/core/ingestion/utils.js +1064 -9
  117. package/dist/core/ingestion/workers/parse-worker.d.ts +38 -4
  118. package/dist/core/ingestion/workers/parse-worker.js +251 -359
  119. package/dist/core/ingestion/workers/worker-pool.js +8 -0
  120. package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
  121. package/dist/core/{kuzu → lbug}/csv-generator.js +20 -4
  122. package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
  123. package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +82 -82
  124. package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
  125. package/dist/core/{kuzu → lbug}/schema.js +304 -289
  126. package/dist/core/search/bm25-index.d.ts +4 -4
  127. package/dist/core/search/bm25-index.js +17 -16
  128. package/dist/core/search/hybrid-search.d.ts +2 -2
  129. package/dist/core/search/hybrid-search.js +9 -9
  130. package/dist/core/tree-sitter/parser-loader.js +9 -2
  131. package/dist/core/wiki/generator.d.ts +4 -52
  132. package/dist/core/wiki/generator.js +53 -552
  133. package/dist/core/wiki/graph-queries.d.ts +4 -46
  134. package/dist/core/wiki/graph-queries.js +103 -282
  135. package/dist/core/wiki/html-viewer.js +192 -192
  136. package/dist/core/wiki/llm-client.js +11 -73
  137. package/dist/core/wiki/prompts.d.ts +8 -52
  138. package/dist/core/wiki/prompts.js +86 -200
  139. package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
  140. package/dist/mcp/compatible-stdio-transport.js +200 -0
  141. package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -9
  142. package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +77 -79
  143. package/dist/mcp/local/local-backend.d.ts +7 -6
  144. package/dist/mcp/local/local-backend.js +176 -147
  145. package/dist/mcp/resources.js +42 -42
  146. package/dist/mcp/server.js +18 -19
  147. package/dist/mcp/tools.js +103 -104
  148. package/dist/server/api.js +12 -12
  149. package/dist/server/mcp-http.d.ts +1 -1
  150. package/dist/server/mcp-http.js +1 -1
  151. package/dist/storage/repo-manager.d.ts +20 -2
  152. package/dist/storage/repo-manager.js +55 -1
  153. package/dist/types/pipeline.d.ts +1 -1
  154. package/hooks/claude/gitnexus-hook.cjs +238 -155
  155. package/hooks/claude/pre-tool-use.sh +79 -79
  156. package/hooks/claude/session-start.sh +42 -42
  157. package/package.json +99 -96
  158. package/scripts/patch-tree-sitter-swift.cjs +74 -74
  159. package/skills/gitnexus-cli.md +82 -82
  160. package/skills/gitnexus-debugging.md +89 -89
  161. package/skills/gitnexus-exploring.md +78 -78
  162. package/skills/gitnexus-guide.md +64 -64
  163. package/skills/gitnexus-impact-analysis.md +97 -97
  164. package/skills/gitnexus-pr-review.md +163 -163
  165. package/skills/gitnexus-refactoring.md +121 -121
  166. package/vendor/leiden/index.cjs +355 -355
  167. package/vendor/leiden/utils.cjs +392 -392
  168. package/dist/core/wiki/diagrams.d.ts +0 -27
  169. 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 — use retry-after if available, otherwise short fixed delay
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 ? Math.min(retryAfter * 1000, 30_000) : 3_000;
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 short backoff
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/timeout error — retry with backoff
118
- const isRetryable = err.code === 'ECONNREFUSED'
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
- let result;
147
- try {
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
- console.log(`[sse] malformed chunk: ${data.slice(0, 200)}`);
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
- }