@bahulam/code 0.1.1

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 (278) hide show
  1. package/README.md +93 -0
  2. package/package.json +56 -0
  3. package/pulse/app/activity/page.tsx +190 -0
  4. package/pulse/app/api/activity/route.ts +138 -0
  5. package/pulse/app/api/benchmark/route.ts +113 -0
  6. package/pulse/app/api/benchmarks/route.ts +195 -0
  7. package/pulse/app/api/costs/route.ts +88 -0
  8. package/pulse/app/api/export/route.ts +77 -0
  9. package/pulse/app/api/history/route.ts +11 -0
  10. package/pulse/app/api/import/route.ts +31 -0
  11. package/pulse/app/api/memory/route.ts +50 -0
  12. package/pulse/app/api/plans/route.ts +9 -0
  13. package/pulse/app/api/projects/[slug]/route.ts +96 -0
  14. package/pulse/app/api/projects/route.ts +121 -0
  15. package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
  16. package/pulse/app/api/sessions/[id]/route.ts +31 -0
  17. package/pulse/app/api/sessions/route.ts +112 -0
  18. package/pulse/app/api/settings/route.ts +14 -0
  19. package/pulse/app/api/stats/route.ts +143 -0
  20. package/pulse/app/api/todos/route.ts +9 -0
  21. package/pulse/app/api/tools/route.ts +160 -0
  22. package/pulse/app/benchmarks/page.tsx +224 -0
  23. package/pulse/app/costs/page.tsx +179 -0
  24. package/pulse/app/export/page.tsx +465 -0
  25. package/pulse/app/favicon.ico +0 -0
  26. package/pulse/app/globals.css +263 -0
  27. package/pulse/app/help/page.tsx +143 -0
  28. package/pulse/app/history/page.tsx +157 -0
  29. package/pulse/app/layout.tsx +46 -0
  30. package/pulse/app/memory/page.tsx +365 -0
  31. package/pulse/app/overview-client.tsx +393 -0
  32. package/pulse/app/page.tsx +14 -0
  33. package/pulse/app/plans/page.tsx +308 -0
  34. package/pulse/app/projects/[slug]/page.tsx +390 -0
  35. package/pulse/app/projects/page.tsx +110 -0
  36. package/pulse/app/sessions/[id]/page.tsx +243 -0
  37. package/pulse/app/sessions/page.tsx +39 -0
  38. package/pulse/app/settings/page.tsx +188 -0
  39. package/pulse/app/todos/page.tsx +211 -0
  40. package/pulse/app/tools/page.tsx +249 -0
  41. package/pulse/cli.js +164 -0
  42. package/pulse/components/activity/day-of-week-chart.tsx +35 -0
  43. package/pulse/components/activity/streak-card.tsx +36 -0
  44. package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
  45. package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
  46. package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
  47. package/pulse/components/costs/model-token-table.tsx +60 -0
  48. package/pulse/components/global-search.tsx +193 -0
  49. package/pulse/components/keyboard-nav-provider.tsx +23 -0
  50. package/pulse/components/layout/bottom-nav.tsx +53 -0
  51. package/pulse/components/layout/client-layout.tsx +31 -0
  52. package/pulse/components/layout/sidebar-context.tsx +50 -0
  53. package/pulse/components/layout/sidebar.tsx +183 -0
  54. package/pulse/components/layout/top-bar.tsx +121 -0
  55. package/pulse/components/overview/activity-heatmap.tsx +107 -0
  56. package/pulse/components/overview/conversation-table.tsx +148 -0
  57. package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
  58. package/pulse/components/overview/peak-hours-chart.tsx +87 -0
  59. package/pulse/components/overview/project-activity-donut.tsx +96 -0
  60. package/pulse/components/overview/stat-card.tsx +102 -0
  61. package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
  62. package/pulse/components/projects/project-card.tsx +175 -0
  63. package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
  64. package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
  65. package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
  66. package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
  67. package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
  68. package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
  69. package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
  70. package/pulse/components/sessions/session-badges.tsx +49 -0
  71. package/pulse/components/sessions/session-table.tsx +299 -0
  72. package/pulse/components/theme-provider.tsx +44 -0
  73. package/pulse/components/tools/feature-adoption-table.tsx +58 -0
  74. package/pulse/components/tools/mcp-server-panel.tsx +45 -0
  75. package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
  76. package/pulse/components/tools/version-history-table.tsx +32 -0
  77. package/pulse/components/ui/alert.tsx +66 -0
  78. package/pulse/components/ui/badge.tsx +48 -0
  79. package/pulse/components/ui/breadcrumb.tsx +109 -0
  80. package/pulse/components/ui/button.tsx +64 -0
  81. package/pulse/components/ui/calendar.tsx +220 -0
  82. package/pulse/components/ui/card.tsx +92 -0
  83. package/pulse/components/ui/command.tsx +158 -0
  84. package/pulse/components/ui/dialog.tsx +158 -0
  85. package/pulse/components/ui/input.tsx +21 -0
  86. package/pulse/components/ui/popover.tsx +89 -0
  87. package/pulse/components/ui/progress.tsx +31 -0
  88. package/pulse/components/ui/select.tsx +190 -0
  89. package/pulse/components/ui/separator.tsx +28 -0
  90. package/pulse/components/ui/sheet.tsx +143 -0
  91. package/pulse/components/ui/skeleton.tsx +13 -0
  92. package/pulse/components/ui/table.tsx +116 -0
  93. package/pulse/components/ui/tabs.tsx +91 -0
  94. package/pulse/components/ui/tooltip.tsx +57 -0
  95. package/pulse/components/use-global-keyboard-nav.ts +79 -0
  96. package/pulse/components.json +23 -0
  97. package/pulse/eslint.config.mjs +18 -0
  98. package/pulse/lib/bahulam-paths.ts +23 -0
  99. package/pulse/lib/claude-reader.ts +592 -0
  100. package/pulse/lib/decode.ts +129 -0
  101. package/pulse/lib/pricing.ts +102 -0
  102. package/pulse/lib/replay-parser.ts +165 -0
  103. package/pulse/lib/tool-categories.ts +127 -0
  104. package/pulse/lib/utils.ts +6 -0
  105. package/pulse/next-env.d.ts +6 -0
  106. package/pulse/next.config.ts +16 -0
  107. package/pulse/package.json +45 -0
  108. package/pulse/postcss.config.mjs +7 -0
  109. package/pulse/public/activity.png +0 -0
  110. package/pulse/public/cc-lens.png +0 -0
  111. package/pulse/public/command-k.png +0 -0
  112. package/pulse/public/costs.png +0 -0
  113. package/pulse/public/dashboard-dark.png +0 -0
  114. package/pulse/public/dashboard-white.png +0 -0
  115. package/pulse/public/export.png +0 -0
  116. package/pulse/public/file.svg +1 -0
  117. package/pulse/public/globe.svg +1 -0
  118. package/pulse/public/next.svg +1 -0
  119. package/pulse/public/projects.png +0 -0
  120. package/pulse/public/session-chat.png +0 -0
  121. package/pulse/public/todos.png +0 -0
  122. package/pulse/public/tools.png +0 -0
  123. package/pulse/public/vercel.svg +1 -0
  124. package/pulse/public/window.svg +1 -0
  125. package/pulse/tsconfig.json +34 -0
  126. package/pulse/types/claude.ts +294 -0
  127. package/src/agents/loader.mjs +94 -0
  128. package/src/agents/multi_workflow_loader.mjs +330 -0
  129. package/src/agents/parser.mjs +205 -0
  130. package/src/agents/scaffold.mjs +223 -0
  131. package/src/agents/teams.mjs +123 -0
  132. package/src/agents/workflow_loader.mjs +122 -0
  133. package/src/agents/workflow_scaffold.mjs +249 -0
  134. package/src/auth/oauth.mjs +220 -0
  135. package/src/auth/tarang-auth.mjs +312 -0
  136. package/src/commands/agent.mjs +221 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +202 -0
  139. package/src/config/env.mjs +263 -0
  140. package/src/config/hook-runner.mjs +100 -0
  141. package/src/config/memory-loader.mjs +32 -0
  142. package/src/config/model-catalog.mjs +57 -0
  143. package/src/config/settings-loader.mjs +45 -0
  144. package/src/config/settings.mjs +132 -0
  145. package/src/context/ast-parser.mjs +298 -0
  146. package/src/context/bm25.mjs +85 -0
  147. package/src/context/prose-chunker.mjs +255 -0
  148. package/src/context/retriever.mjs +425 -0
  149. package/src/context/skeleton.mjs +134 -0
  150. package/src/context/symbol-indexer.mjs +375 -0
  151. package/src/core/agent-history.mjs +111 -0
  152. package/src/core/agent-loop.mjs +486 -0
  153. package/src/core/approval-log.mjs +145 -0
  154. package/src/core/approval.mjs +700 -0
  155. package/src/core/attachments.mjs +666 -0
  156. package/src/core/backend-url.mjs +68 -0
  157. package/src/core/bundled-runtime.mjs +418 -0
  158. package/src/core/cache-control.mjs +92 -0
  159. package/src/core/cache.mjs +105 -0
  160. package/src/core/callback-client.mjs +180 -0
  161. package/src/core/checkpoints.mjs +142 -0
  162. package/src/core/compact-history.mjs +127 -0
  163. package/src/core/context-envelope.mjs +54 -0
  164. package/src/core/context-manager.mjs +198 -0
  165. package/src/core/error-guidance.mjs +331 -0
  166. package/src/core/file-diff.mjs +217 -0
  167. package/src/core/headless.mjs +460 -0
  168. package/src/core/hooks-manager.mjs +87 -0
  169. package/src/core/jsonl-writer.mjs +449 -0
  170. package/src/core/local-agent.mjs +538 -0
  171. package/src/core/local-store.mjs +836 -0
  172. package/src/core/mode-selector.mjs +51 -0
  173. package/src/core/output-filter.mjs +177 -0
  174. package/src/core/paths.mjs +190 -0
  175. package/src/core/policy-resolver.mjs +156 -0
  176. package/src/core/pricing.mjs +336 -0
  177. package/src/core/project-artifacts.mjs +39 -0
  178. package/src/core/project-context-loader.mjs +139 -0
  179. package/src/core/providers.mjs +219 -0
  180. package/src/core/rate-limit-display.mjs +121 -0
  181. package/src/core/rate-limiter.mjs +119 -0
  182. package/src/core/resume-mode.mjs +192 -0
  183. package/src/core/risk-tier.mjs +388 -0
  184. package/src/core/safety.mjs +260 -0
  185. package/src/core/scheduler.mjs +173 -0
  186. package/src/core/session-manager.mjs +360 -0
  187. package/src/core/session.mjs +143 -0
  188. package/src/core/settings-sync.mjs +85 -0
  189. package/src/core/stagnation.mjs +57 -0
  190. package/src/core/stream-client.mjs +957 -0
  191. package/src/core/streaming.mjs +182 -0
  192. package/src/core/system-prompt.mjs +140 -0
  193. package/src/core/tasks.mjs +196 -0
  194. package/src/core/tool-executor.mjs +2231 -0
  195. package/src/core/trust.mjs +160 -0
  196. package/src/core/work-scope.mjs +248 -0
  197. package/src/hooks/engine.mjs +162 -0
  198. package/src/mcp/client.mjs +253 -0
  199. package/src/mcp/transport-shttp.mjs +130 -0
  200. package/src/mcp/transport-sse.mjs +131 -0
  201. package/src/mcp/transport-ws.mjs +134 -0
  202. package/src/onboarding/preflight.mjs +374 -0
  203. package/src/permissions/checker.mjs +57 -0
  204. package/src/permissions/command-classifier.mjs +700 -0
  205. package/src/permissions/injection-check.mjs +60 -0
  206. package/src/permissions/path-check.mjs +102 -0
  207. package/src/permissions/prompt.mjs +73 -0
  208. package/src/permissions/sandbox.mjs +112 -0
  209. package/src/plugins/loader.mjs +138 -0
  210. package/src/skills/installer.mjs +188 -0
  211. package/src/skills/loader.mjs +252 -0
  212. package/src/skills/runner.mjs +55 -0
  213. package/src/state/orbit.mjs +263 -0
  214. package/src/state/verbosity.mjs +99 -0
  215. package/src/telemetry/index.mjs +122 -0
  216. package/src/terminal/agents.mjs +353 -0
  217. package/src/terminal/analytics.mjs +292 -0
  218. package/src/terminal/ansi.mjs +695 -0
  219. package/src/terminal/init.mjs +145 -0
  220. package/src/terminal/main.mjs +310 -0
  221. package/src/terminal/repl-ask-form.mjs +120 -0
  222. package/src/terminal/repl-explore.mjs +44 -0
  223. package/src/terminal/repl-format.mjs +317 -0
  224. package/src/terminal/repl-model-form.mjs +132 -0
  225. package/src/terminal/repl-render.mjs +833 -0
  226. package/src/terminal/repl-resume.mjs +640 -0
  227. package/src/terminal/repl-state.mjs +120 -0
  228. package/src/terminal/repl-utils.mjs +34 -0
  229. package/src/terminal/repl.mjs +5032 -0
  230. package/src/terminal/skills.mjs +54 -0
  231. package/src/terminal/tool-display.mjs +392 -0
  232. package/src/tools/agent.mjs +137 -0
  233. package/src/tools/ask-user.mjs +61 -0
  234. package/src/tools/bash.mjs +231 -0
  235. package/src/tools/cron-create.mjs +120 -0
  236. package/src/tools/cron-delete.mjs +49 -0
  237. package/src/tools/cron-list.mjs +37 -0
  238. package/src/tools/edit.mjs +82 -0
  239. package/src/tools/enter-worktree.mjs +69 -0
  240. package/src/tools/exit-worktree.mjs +57 -0
  241. package/src/tools/glob.mjs +117 -0
  242. package/src/tools/grep.mjs +129 -0
  243. package/src/tools/lint.mjs +71 -0
  244. package/src/tools/ls.mjs +58 -0
  245. package/src/tools/lsp.mjs +115 -0
  246. package/src/tools/multi-edit.mjs +94 -0
  247. package/src/tools/notebook-edit.mjs +96 -0
  248. package/src/tools/project-overview.mjs +703 -0
  249. package/src/tools/read-mcp-resource.mjs +57 -0
  250. package/src/tools/read.mjs +138 -0
  251. package/src/tools/registry.mjs +116 -0
  252. package/src/tools/remote-trigger.mjs +84 -0
  253. package/src/tools/send-message.mjs +64 -0
  254. package/src/tools/skill.mjs +52 -0
  255. package/src/tools/test-runner.mjs +49 -0
  256. package/src/tools/todo-write.mjs +68 -0
  257. package/src/tools/tool-search.mjs +77 -0
  258. package/src/tools/web-fetch.mjs +65 -0
  259. package/src/tools/web-search.mjs +89 -0
  260. package/src/tools/write.mjs +55 -0
  261. package/src/ui/approval.mjs +510 -0
  262. package/src/ui/banner.mjs +232 -0
  263. package/src/ui/commands.mjs +537 -0
  264. package/src/ui/formatter.mjs +409 -0
  265. package/src/ui/icons.mjs +170 -0
  266. package/src/ui/input-dock.mjs +772 -0
  267. package/src/ui/markdown.mjs +278 -0
  268. package/src/ui/mission-report.mjs +296 -0
  269. package/src/ui/palette.mjs +189 -0
  270. package/src/ui/render-queue.mjs +500 -0
  271. package/src/ui/slash-commands.mjs +257 -0
  272. package/src/ui/spinner.mjs +116 -0
  273. package/src/ui/sub-agent.mjs +167 -0
  274. package/src/ui/term.mjs +174 -0
  275. package/src/ui/text-layout.mjs +127 -0
  276. package/src/ui/tool-card.mjs +740 -0
  277. package/src/ui/tool-details.mjs +504 -0
  278. package/src/ui/transcript-block.mjs +20 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Project Skeleton — lightweight codebase overview for LLM context.
3
+ *
4
+ * Generates a compact representation of the project:
5
+ * - File tree (directories + file names)
6
+ * - Function/class signatures extracted via regex (not full AST)
7
+ *
8
+ * Designed to be ~500-1000 tokens — gives the model a "map" so it knows
9
+ * where to look without reading every file.
10
+ */
11
+
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+
15
+ const IGNORED_DIRS = new Set(['.git', 'node_modules', '.bahulam', '__pycache__', '.venv', 'venv', 'dist', 'build', '.next', '.cache', 'coverage', '.tox']);
16
+ const CODE_EXTS = new Set(['.js', '.mjs', '.ts', '.tsx', '.py', '.go', '.rs', '.java', '.rb', '.c', '.cpp', '.h']);
17
+ const MAX_FILE_SIZE = 200_000;
18
+
19
+ // Regex patterns for function/class signatures (multi-language)
20
+ const SIGNATURE_PATTERNS = [
21
+ // Python: def/class/async def
22
+ /^(?:async\s+)?(?:def|class)\s+(\w+)\s*[\(:].*$/gm,
23
+ // JS/TS: function, class, export function, const fn =
24
+ /^(?:export\s+)?(?:async\s+)?(?:function\s+(\w+)|class\s+(\w+))/gm,
25
+ // Go: func
26
+ /^func\s+(?:\(.*?\)\s+)?(\w+)\s*\(/gm,
27
+ // Rust: fn, struct, impl
28
+ /^(?:pub\s+)?(?:fn|struct|impl)\s+(\w+)/gm,
29
+ ];
30
+
31
+ /**
32
+ * Build a project skeleton — file tree + key signatures.
33
+ * @param {string} projectDir
34
+ * @param {Object} [options]
35
+ * @param {number} [options.maxFiles=200] — max files to include
36
+ * @param {number} [options.maxChars=4000] — max total chars (~1000 tokens)
37
+ * @returns {string} — skeleton text for LLM context
38
+ */
39
+ export function buildProjectSkeleton(projectDir, { maxFiles = 200, maxChars = 4000 } = {}) {
40
+ const files = scanFiles(projectDir, 0, maxFiles);
41
+ if (files.length === 0) return '';
42
+
43
+ const parts = [];
44
+ parts.push(`Project: ${path.basename(projectDir)} (${files.length} source files)`);
45
+ parts.push('');
46
+
47
+ // Group by directory
48
+ const dirs = new Map();
49
+ for (const f of files) {
50
+ const rel = path.relative(projectDir, f);
51
+ const dir = path.dirname(rel);
52
+ if (!dirs.has(dir)) dirs.set(dir, []);
53
+ dirs.get(dir).push(rel);
54
+ }
55
+
56
+ // File tree
57
+ parts.push('## File Tree');
58
+ for (const [dir, dirFiles] of dirs) {
59
+ parts.push(`${dir}/`);
60
+ for (const f of dirFiles) {
61
+ parts.push(` ${path.basename(f)}`);
62
+ }
63
+ }
64
+
65
+ // Key signatures (top-level functions/classes)
66
+ parts.push('');
67
+ parts.push('## Key Signatures');
68
+
69
+ let sigCount = 0;
70
+ for (const f of files) {
71
+ if (sigCount > 50) break; // Cap signatures
72
+ try {
73
+ const content = fs.readFileSync(f, 'utf-8');
74
+ const rel = path.relative(projectDir, f);
75
+ const sigs = extractSignatures(content);
76
+ if (sigs.length > 0) {
77
+ parts.push(`${rel}: ${sigs.join(', ')}`);
78
+ sigCount += sigs.length;
79
+ }
80
+ } catch { /* skip */ }
81
+ }
82
+
83
+ let skeleton = parts.join('\n');
84
+ if (skeleton.length > maxChars) {
85
+ skeleton = skeleton.slice(0, maxChars) + '\n... (truncated)';
86
+ }
87
+ return skeleton;
88
+ }
89
+
90
+ /**
91
+ * Extract function/class names from source code via regex.
92
+ */
93
+ function extractSignatures(content) {
94
+ const names = new Set();
95
+ for (const pattern of SIGNATURE_PATTERNS) {
96
+ pattern.lastIndex = 0;
97
+ let match;
98
+ while ((match = pattern.exec(content)) !== null) {
99
+ // Take the first non-null capture group
100
+ const name = match[1] || match[2] || match[3];
101
+ if (name && !name.startsWith('_')) names.add(name);
102
+ }
103
+ }
104
+ return Array.from(names).slice(0, 10);
105
+ }
106
+
107
+ /**
108
+ * Scan project for source files.
109
+ */
110
+ function scanFiles(dir, depth = 0, maxFiles = 200) {
111
+ if (depth > 10) return [];
112
+ const results = [];
113
+ let entries;
114
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return []; }
115
+
116
+ for (const entry of entries) {
117
+ if (results.length >= maxFiles) break;
118
+ if (entry.name.startsWith('.') || IGNORED_DIRS.has(entry.name)) continue;
119
+
120
+ const fullPath = path.join(dir, entry.name);
121
+ if (entry.isDirectory()) {
122
+ results.push(...scanFiles(fullPath, depth + 1, maxFiles - results.length));
123
+ } else if (entry.isFile()) {
124
+ const ext = path.extname(entry.name);
125
+ if (!CODE_EXTS.has(ext)) continue;
126
+ try {
127
+ const stat = fs.statSync(fullPath);
128
+ if (stat.size > MAX_FILE_SIZE) continue;
129
+ } catch { continue; }
130
+ results.push(fullPath);
131
+ }
132
+ }
133
+ return results;
134
+ }
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Symbol Indexer — AST-based code search using tree-sitter.
3
+ *
4
+ * Parses source files into symbols (functions, classes, methods) with
5
+ * signatures and line numbers. Indexes symbols in BM25 for search.
6
+ *
7
+ * Memory efficient: stores symbol signatures (~50 chars) not file chunks
8
+ * (~2000 chars). One tree-sitter parse per file, O(n) on file size.
9
+ *
10
+ * Usage:
11
+ * const indexer = new SymbolIndexer();
12
+ * await indexer.init(); // load WASM grammars once
13
+ * indexer.indexFile('/path/to/file.py', content);
14
+ * const results = indexer.search('find_ordering_name');
15
+ */
16
+
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ import { BM25Index } from './bm25.mjs';
20
+
21
+ const GRAMMAR_DIR = new URL('./grammars/', import.meta.url).pathname;
22
+
23
+ const LANG_MAP = {
24
+ '.py': 'python',
25
+ '.js': 'javascript',
26
+ '.mjs': 'javascript',
27
+ '.jsx': 'javascript',
28
+ '.ts': 'typescript',
29
+ '.tsx': 'typescript',
30
+ };
31
+
32
+ /**
33
+ * @typedef {Object} Symbol
34
+ * @property {string} name
35
+ * @property {string} kind - 'function' | 'class' | 'method'
36
+ * @property {string} file - relative path
37
+ * @property {number} line
38
+ * @property {number} endLine
39
+ * @property {string} signature - e.g., "def find_ordering_name(self, name, opts)"
40
+ * @property {string} [parent] - parent class name if method
41
+ * @property {string} [docstring] - first line of docstring
42
+ */
43
+
44
+ export class SymbolIndexer {
45
+ constructor() {
46
+ this._Parser = null;
47
+ this._languages = {}; // ext → Language
48
+ this._symbols = []; // all extracted symbols
49
+ this._symbolMap = new Map(); // id → Symbol
50
+ this._bm25 = new BM25Index();
51
+ this._initialized = false;
52
+ }
53
+
54
+ /**
55
+ * Load tree-sitter WASM runtime + grammars. Call once per session.
56
+ * Lazy — only loads grammars for languages actually encountered.
57
+ */
58
+ async init() {
59
+ if (this._initialized) return;
60
+ try {
61
+ const TreeSitter = (await import('web-tree-sitter')).default;
62
+ await TreeSitter.init();
63
+ this._Parser = new TreeSitter();
64
+ this._TreeSitter = TreeSitter;
65
+ this._initialized = true;
66
+ } catch (e) {
67
+ // Fallback: tree-sitter not available, use regex parser
68
+ this._initialized = false;
69
+ }
70
+ }
71
+
72
+ async _getLanguage(ext) {
73
+ if (this._languages[ext]) return this._languages[ext];
74
+ const langName = LANG_MAP[ext];
75
+ if (!langName || !this._TreeSitter) return null;
76
+
77
+ // Try bundled WASM from tree-sitter-wasms package
78
+ const wasmPaths = [
79
+ path.join(GRAMMAR_DIR, `tree-sitter-${langName}.wasm`),
80
+ ];
81
+
82
+ // Also check node_modules
83
+ try {
84
+ const modPath = new URL(`../../node_modules/tree-sitter-wasms/out/tree-sitter-${langName}.wasm`, import.meta.url).pathname;
85
+ wasmPaths.push(modPath);
86
+ } catch { /* ignore */ }
87
+
88
+ for (const p of wasmPaths) {
89
+ try {
90
+ if (fs.existsSync(p)) {
91
+ const lang = await this._TreeSitter.Language.load(p);
92
+ this._languages[ext] = lang;
93
+ return lang;
94
+ }
95
+ } catch { /* try next */ }
96
+ }
97
+ return null;
98
+ }
99
+
100
+ /**
101
+ * Index a single file. Extracts symbols and adds to BM25.
102
+ * @param {string} relPath - relative path (used as ID)
103
+ * @param {string} content - file content
104
+ */
105
+ async indexFile(relPath, content) {
106
+ const ext = path.extname(relPath).toLowerCase();
107
+ let symbols;
108
+
109
+ const lang = await this._getLanguage(ext);
110
+ if (lang && this._Parser) {
111
+ this._Parser.setLanguage(lang);
112
+ const tree = this._Parser.parse(content);
113
+ symbols = this._extractSymbols(tree.rootNode, relPath, ext);
114
+ tree.delete();
115
+ } else {
116
+ symbols = this._regexExtract(relPath, content, ext);
117
+ }
118
+
119
+ for (const sym of symbols) {
120
+ const id = `${sym.file}:${sym.line}:${sym.name}`;
121
+ this._symbols.push(sym);
122
+ this._symbolMap.set(id, sym);
123
+
124
+ // BM25 document: name + signature + parent + docstring
125
+ const text = [
126
+ sym.name,
127
+ sym.signature || '',
128
+ sym.parent ? `${sym.parent}.${sym.name}` : '',
129
+ sym.docstring || '',
130
+ sym.file,
131
+ ].join(' ');
132
+ this._bm25.addDocument(id, text);
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Search for symbols matching a query.
138
+ * @param {string} query
139
+ * @param {number} [topK=10]
140
+ * @returns {Array<{symbol: Symbol, score: number}>}
141
+ */
142
+ search(query, topK = 10) {
143
+ const results = this._bm25.search(query, topK);
144
+ return results.map(r => ({
145
+ symbol: this._symbolMap.get(r.id),
146
+ score: r.score,
147
+ id: r.id,
148
+ })).filter(r => r.symbol);
149
+ }
150
+
151
+ /**
152
+ * Format search results for the agent.
153
+ */
154
+ formatResults(results) {
155
+ if (!results.length) return '';
156
+ return results.map(r => {
157
+ const s = r.symbol;
158
+ const parent = s.parent ? `${s.parent}.` : '';
159
+ const doc = s.docstring ? ` "${s.docstring}"` : '';
160
+ return `${s.file}:${s.line} ${parent}${s.signature || s.name}${doc}`;
161
+ }).join('\n');
162
+ }
163
+
164
+ get symbolCount() { return this._symbols.length; }
165
+
166
+ // ── Tree-sitter extraction ──
167
+
168
+ _extractSymbols(node, file, ext) {
169
+ const symbols = [];
170
+ const langName = LANG_MAP[ext];
171
+
172
+ if (langName === 'python') {
173
+ this._walkPython(node, file, symbols, null);
174
+ } else if (langName === 'javascript' || langName === 'typescript') {
175
+ this._walkJS(node, file, symbols, null);
176
+ }
177
+ return symbols;
178
+ }
179
+
180
+ _walkPython(node, file, symbols, parentClass) {
181
+ for (let i = 0; i < node.childCount; i++) {
182
+ const child = node.child(i);
183
+ const type = child.type;
184
+
185
+ if (type === 'class_definition') {
186
+ const nameNode = child.childForFieldName('name');
187
+ const name = nameNode?.text || '';
188
+ const bases = child.childForFieldName('superclasses')?.text || '';
189
+ symbols.push({
190
+ name, kind: 'class', file,
191
+ line: child.startPosition.row + 1,
192
+ endLine: child.endPosition.row + 1,
193
+ signature: `class ${name}${bases ? `(${bases})` : ''}`,
194
+ docstring: this._pyDocstring(child),
195
+ });
196
+ // Recurse into class body for methods
197
+ const body = child.childForFieldName('body');
198
+ if (body) this._walkPython(body, file, symbols, name);
199
+ }
200
+
201
+ else if (type === 'function_definition') {
202
+ const nameNode = child.childForFieldName('name');
203
+ const name = nameNode?.text || '';
204
+ const params = child.childForFieldName('parameters')?.text || '()';
205
+ const returnType = child.childForFieldName('return_type')?.text || '';
206
+ const sig = `def ${name}${params}${returnType ? ' -> ' + returnType : ''}`;
207
+ symbols.push({
208
+ name,
209
+ kind: parentClass ? 'method' : 'function',
210
+ file,
211
+ line: child.startPosition.row + 1,
212
+ endLine: child.endPosition.row + 1,
213
+ signature: sig,
214
+ parent: parentClass || undefined,
215
+ docstring: this._pyDocstring(child),
216
+ });
217
+ }
218
+
219
+ else if (type === 'decorated_definition') {
220
+ // Unwrap decorator to get the actual definition
221
+ for (let j = 0; j < child.childCount; j++) {
222
+ const inner = child.child(j);
223
+ if (inner.type === 'function_definition' || inner.type === 'class_definition') {
224
+ this._walkPython(child, file, symbols, parentClass);
225
+ break;
226
+ }
227
+ }
228
+ }
229
+
230
+ else {
231
+ // Recurse for module-level statements
232
+ if (!parentClass && child.childCount > 0) {
233
+ this._walkPython(child, file, symbols, parentClass);
234
+ }
235
+ }
236
+ }
237
+ }
238
+
239
+ _pyDocstring(defNode) {
240
+ const body = defNode.childForFieldName('body');
241
+ if (!body || body.childCount === 0) return '';
242
+ const first = body.child(0);
243
+ if (first?.type === 'expression_statement') {
244
+ const expr = first.child(0);
245
+ if (expr?.type === 'string' || expr?.type === 'concatenated_string') {
246
+ const raw = expr.text;
247
+ // Extract first line of docstring
248
+ const content = raw.replace(/^['"`]{1,3}/, '').replace(/['"`]{1,3}$/, '');
249
+ const firstLine = content.split('\n')[0].trim();
250
+ return firstLine.slice(0, 120);
251
+ }
252
+ }
253
+ return '';
254
+ }
255
+
256
+ _walkJS(node, file, symbols, parentClass) {
257
+ for (let i = 0; i < node.childCount; i++) {
258
+ const child = node.child(i);
259
+ const type = child.type;
260
+
261
+ if (type === 'class_declaration' || type === 'class') {
262
+ const nameNode = child.childForFieldName('name');
263
+ const name = nameNode?.text || '';
264
+ symbols.push({
265
+ name, kind: 'class', file,
266
+ line: child.startPosition.row + 1,
267
+ endLine: child.endPosition.row + 1,
268
+ signature: `class ${name}`,
269
+ });
270
+ const body = child.childForFieldName('body');
271
+ if (body) this._walkJS(body, file, symbols, name);
272
+ }
273
+
274
+ else if (type === 'function_declaration' || type === 'method_definition') {
275
+ const nameNode = child.childForFieldName('name');
276
+ const name = nameNode?.text || '';
277
+ const params = child.childForFieldName('parameters')?.text || '()';
278
+ symbols.push({
279
+ name,
280
+ kind: parentClass ? 'method' : 'function',
281
+ file,
282
+ line: child.startPosition.row + 1,
283
+ endLine: child.endPosition.row + 1,
284
+ signature: `${parentClass ? '' : 'function '}${name}${params}`,
285
+ parent: parentClass || undefined,
286
+ });
287
+ }
288
+
289
+ else if (type === 'export_statement' || type === 'lexical_declaration') {
290
+ this._walkJS(child, file, symbols, parentClass);
291
+ }
292
+
293
+ else if (child.childCount > 0 && !parentClass) {
294
+ this._walkJS(child, file, symbols, parentClass);
295
+ }
296
+ }
297
+ }
298
+
299
+ // ── Regex fallback (no tree-sitter) ──
300
+
301
+ _regexExtract(file, content, ext) {
302
+ const symbols = [];
303
+ const lines = content.split('\n');
304
+ let currentClass = null;
305
+
306
+ for (let i = 0; i < lines.length; i++) {
307
+ const line = lines[i];
308
+ const trimmed = line.trim();
309
+ const lineNum = i + 1;
310
+ const indent = line.length - line.trimStart().length;
311
+
312
+ // Python
313
+ if (ext === '.py') {
314
+ const classMatch = trimmed.match(/^class\s+(\w+)(?:\(([^)]*)\))?/);
315
+ if (classMatch) {
316
+ currentClass = classMatch[1];
317
+ symbols.push({
318
+ name: currentClass, kind: 'class', file, line: lineNum,
319
+ signature: `class ${currentClass}${classMatch[2] ? `(${classMatch[2]})` : ''}`,
320
+ });
321
+ continue;
322
+ }
323
+ const fnMatch = trimmed.match(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/);
324
+ if (fnMatch) {
325
+ const isMethod = indent >= 4 && currentClass;
326
+ symbols.push({
327
+ name: fnMatch[1],
328
+ kind: isMethod ? 'method' : 'function',
329
+ file, line: lineNum,
330
+ signature: `def ${fnMatch[1]}(${fnMatch[2]})`,
331
+ parent: isMethod ? currentClass : undefined,
332
+ });
333
+ continue;
334
+ }
335
+ if (indent === 0 && !trimmed.startsWith('#') && trimmed) {
336
+ currentClass = null;
337
+ }
338
+ }
339
+
340
+ // JS/TS
341
+ if (['.js', '.mjs', '.ts', '.tsx', '.jsx'].includes(ext)) {
342
+ const fnMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/);
343
+ if (fnMatch) {
344
+ symbols.push({ name: fnMatch[1], kind: 'function', file, line: lineNum, signature: `function ${fnMatch[1]}(${fnMatch[2]})` });
345
+ }
346
+ const classMatch = trimmed.match(/^(?:export\s+)?class\s+(\w+)/);
347
+ if (classMatch) {
348
+ symbols.push({ name: classMatch[1], kind: 'class', file, line: lineNum, signature: `class ${classMatch[1]}` });
349
+ }
350
+ }
351
+ }
352
+ return symbols;
353
+ }
354
+
355
+ // ── Serialization ──
356
+
357
+ toJSON() {
358
+ return {
359
+ symbols: this._symbols,
360
+ bm25: this._bm25.toJSON(),
361
+ };
362
+ }
363
+
364
+ static fromJSON(data) {
365
+ const indexer = new SymbolIndexer();
366
+ indexer._initialized = true; // don't need tree-sitter for search
367
+ indexer._symbols = data.symbols || [];
368
+ indexer._bm25 = BM25Index.fromJSON(data.bm25);
369
+ for (const sym of indexer._symbols) {
370
+ const id = `${sym.file}:${sym.line}:${sym.name}`;
371
+ indexer._symbolMap.set(id, sym);
372
+ }
373
+ return indexer;
374
+ }
375
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Structured live history for backend continuity.
3
+ *
4
+ * The terminal display history is intentionally human-friendly. This builder
5
+ * keeps the backend payload provider-shaped: assistant text/tool_use blocks,
6
+ * followed by user tool_result blocks, appended in the order they happened.
7
+ */
8
+
9
+ const DEFAULT_MAX_TOOL_RESULT_CHARS = 200_000;
10
+
11
+ function asString(value) {
12
+ if (value == null) return '';
13
+ if (typeof value === 'string') return value;
14
+ try {
15
+ return JSON.stringify(value);
16
+ } catch {
17
+ return String(value);
18
+ }
19
+ }
20
+
21
+ function maybeTruncate(text, maxChars) {
22
+ const value = asString(text);
23
+ if (!maxChars || value.length <= maxChars) return value;
24
+ return `${value.slice(0, maxChars)}\n\n[Bahulam Code truncated this tool result from ${value.length} to ${maxChars} characters for live session continuity.]`;
25
+ }
26
+
27
+ function mergeTextBlock(blocks, text) {
28
+ if (!text) return;
29
+ const last = blocks[blocks.length - 1];
30
+ if (last?.type === 'text') {
31
+ last.text += text;
32
+ } else {
33
+ blocks.push({ type: 'text', text });
34
+ }
35
+ }
36
+
37
+ export class AgentHistoryTurnBuilder {
38
+ constructor({ maxToolResultChars = DEFAULT_MAX_TOOL_RESULT_CHARS } = {}) {
39
+ this.maxToolResultChars = maxToolResultChars;
40
+ this.messages = [];
41
+ this.assistantBlocks = [];
42
+ this.toolUseIds = new Set();
43
+ this.toolResultIds = new Set();
44
+ }
45
+
46
+ addAssistantText(text) {
47
+ mergeTextBlock(this.assistantBlocks, asString(text));
48
+ }
49
+
50
+ addToolUse(data = {}) {
51
+ if (data.internal || data.sub_agent) return false;
52
+ const id = data.call_id || data.request_id || data.id;
53
+ const name = data.tool || data.name;
54
+ if (!id || !name) return false;
55
+ this.assistantBlocks.push({
56
+ type: 'tool_use',
57
+ id,
58
+ name,
59
+ input: data.args || data.input || {},
60
+ });
61
+ this.toolUseIds.add(id);
62
+ return true;
63
+ }
64
+
65
+ addToolResult(data = {}) {
66
+ if (data.internal || data.sub_agent) return false;
67
+ const id = data.call_id || data._callId || data.request_id || data.id || data.tool_use_id;
68
+ if (this.toolResultIds.has(id)) return false;
69
+ if (!id) return false;
70
+ if (!this.toolUseIds.has(id)) {
71
+ const name = data.tool || data.name;
72
+ if (!name) return false;
73
+ this.assistantBlocks.push({
74
+ type: 'tool_use',
75
+ id,
76
+ name,
77
+ input: data.args || data.input || {},
78
+ });
79
+ this.toolUseIds.add(id);
80
+ }
81
+
82
+ this.flushAssistant();
83
+ this.messages.push({
84
+ role: 'user',
85
+ content: [{
86
+ type: 'tool_result',
87
+ tool_use_id: id,
88
+ content: maybeTruncate(data.llm_content ?? data.output ?? data.result ?? data.message ?? '', this.maxToolResultChars),
89
+ ...(data.success === false || data.is_error ? { is_error: true } : {}),
90
+ }],
91
+ });
92
+ this.toolResultIds.add(id);
93
+ return true;
94
+ }
95
+
96
+ flushAssistant() {
97
+ const blocks = this.assistantBlocks.filter(block => {
98
+ if (block.type === 'text') return Boolean(block.text);
99
+ return true;
100
+ });
101
+ if (!blocks.length) return false;
102
+ this.messages.push({ role: 'assistant', content: blocks });
103
+ this.assistantBlocks = [];
104
+ return true;
105
+ }
106
+
107
+ finish() {
108
+ this.flushAssistant();
109
+ return this.messages;
110
+ }
111
+ }