@bahulam/code 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +80 -0
  2. package/package.json +49 -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 +222 -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 +306 -0
  136. package/src/commands/agent.mjs +220 -0
  137. package/src/commands/workflow.mjs +581 -0
  138. package/src/config/cli-args.mjs +200 -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/settings-loader.mjs +45 -0
  143. package/src/config/settings.mjs +132 -0
  144. package/src/context/ast-parser.mjs +298 -0
  145. package/src/context/bm25.mjs +85 -0
  146. package/src/context/retriever.mjs +308 -0
  147. package/src/context/skeleton.mjs +134 -0
  148. package/src/context/symbol-indexer.mjs +375 -0
  149. package/src/core/agent-history.mjs +111 -0
  150. package/src/core/agent-loop.mjs +486 -0
  151. package/src/core/approval-log.mjs +104 -0
  152. package/src/core/approval.mjs +476 -0
  153. package/src/core/attachments.mjs +380 -0
  154. package/src/core/backend-url.mjs +55 -0
  155. package/src/core/cache-control.mjs +92 -0
  156. package/src/core/cache.mjs +105 -0
  157. package/src/core/callback-client.mjs +180 -0
  158. package/src/core/checkpoints.mjs +142 -0
  159. package/src/core/compact-history.mjs +127 -0
  160. package/src/core/context-envelope.mjs +54 -0
  161. package/src/core/context-manager.mjs +198 -0
  162. package/src/core/error-guidance.mjs +311 -0
  163. package/src/core/file-diff.mjs +217 -0
  164. package/src/core/headless.mjs +448 -0
  165. package/src/core/hooks-manager.mjs +87 -0
  166. package/src/core/jsonl-writer.mjs +449 -0
  167. package/src/core/local-agent.mjs +537 -0
  168. package/src/core/local-store.mjs +836 -0
  169. package/src/core/mode-selector.mjs +51 -0
  170. package/src/core/output-filter.mjs +177 -0
  171. package/src/core/paths.mjs +190 -0
  172. package/src/core/policy-resolver.mjs +156 -0
  173. package/src/core/pricing.mjs +336 -0
  174. package/src/core/project-artifacts.mjs +39 -0
  175. package/src/core/project-context-loader.mjs +139 -0
  176. package/src/core/providers.mjs +219 -0
  177. package/src/core/rate-limit-display.mjs +121 -0
  178. package/src/core/rate-limiter.mjs +119 -0
  179. package/src/core/resume-mode.mjs +192 -0
  180. package/src/core/risk-tier.mjs +337 -0
  181. package/src/core/safety.mjs +203 -0
  182. package/src/core/scheduler.mjs +173 -0
  183. package/src/core/session-manager.mjs +360 -0
  184. package/src/core/session.mjs +143 -0
  185. package/src/core/settings-sync.mjs +85 -0
  186. package/src/core/stagnation.mjs +57 -0
  187. package/src/core/stream-client.mjs +829 -0
  188. package/src/core/streaming.mjs +182 -0
  189. package/src/core/system-prompt.mjs +140 -0
  190. package/src/core/tasks.mjs +196 -0
  191. package/src/core/tool-executor.mjs +1950 -0
  192. package/src/core/trust.mjs +158 -0
  193. package/src/core/work-scope.mjs +248 -0
  194. package/src/hooks/engine.mjs +162 -0
  195. package/src/index.mjs +426 -0
  196. package/src/mcp/client.mjs +253 -0
  197. package/src/mcp/transport-shttp.mjs +130 -0
  198. package/src/mcp/transport-sse.mjs +131 -0
  199. package/src/mcp/transport-ws.mjs +134 -0
  200. package/src/onboarding/preflight.mjs +360 -0
  201. package/src/permissions/checker.mjs +57 -0
  202. package/src/permissions/command-classifier.mjs +652 -0
  203. package/src/permissions/injection-check.mjs +60 -0
  204. package/src/permissions/path-check.mjs +102 -0
  205. package/src/permissions/prompt.mjs +73 -0
  206. package/src/permissions/sandbox.mjs +112 -0
  207. package/src/plugins/loader.mjs +138 -0
  208. package/src/skills/installer.mjs +188 -0
  209. package/src/skills/loader.mjs +252 -0
  210. package/src/skills/runner.mjs +55 -0
  211. package/src/state/orbit.mjs +263 -0
  212. package/src/state/verbosity.mjs +99 -0
  213. package/src/telemetry/index.mjs +96 -0
  214. package/src/terminal/agents.mjs +177 -0
  215. package/src/terminal/analytics.mjs +292 -0
  216. package/src/terminal/ansi.mjs +695 -0
  217. package/src/terminal/init.mjs +145 -0
  218. package/src/terminal/main.mjs +269 -0
  219. package/src/terminal/repl-explore.mjs +35 -0
  220. package/src/terminal/repl-format.mjs +257 -0
  221. package/src/terminal/repl-render.mjs +561 -0
  222. package/src/terminal/repl-resume.mjs +625 -0
  223. package/src/terminal/repl-state.mjs +103 -0
  224. package/src/terminal/repl-utils.mjs +34 -0
  225. package/src/terminal/repl.mjs +3832 -0
  226. package/src/terminal/skills.mjs +54 -0
  227. package/src/terminal/tool-display.mjs +240 -0
  228. package/src/tools/agent.mjs +137 -0
  229. package/src/tools/ask-user.mjs +61 -0
  230. package/src/tools/bash.mjs +231 -0
  231. package/src/tools/cron-create.mjs +120 -0
  232. package/src/tools/cron-delete.mjs +49 -0
  233. package/src/tools/cron-list.mjs +37 -0
  234. package/src/tools/edit.mjs +82 -0
  235. package/src/tools/enter-worktree.mjs +69 -0
  236. package/src/tools/exit-worktree.mjs +57 -0
  237. package/src/tools/glob.mjs +117 -0
  238. package/src/tools/grep.mjs +129 -0
  239. package/src/tools/lint.mjs +71 -0
  240. package/src/tools/ls.mjs +58 -0
  241. package/src/tools/lsp.mjs +115 -0
  242. package/src/tools/multi-edit.mjs +94 -0
  243. package/src/tools/notebook-edit.mjs +96 -0
  244. package/src/tools/project-overview.mjs +641 -0
  245. package/src/tools/read-mcp-resource.mjs +57 -0
  246. package/src/tools/read.mjs +138 -0
  247. package/src/tools/registry.mjs +116 -0
  248. package/src/tools/remote-trigger.mjs +84 -0
  249. package/src/tools/send-message.mjs +64 -0
  250. package/src/tools/skill.mjs +52 -0
  251. package/src/tools/test-runner.mjs +49 -0
  252. package/src/tools/todo-write.mjs +68 -0
  253. package/src/tools/tool-search.mjs +77 -0
  254. package/src/tools/web-fetch.mjs +65 -0
  255. package/src/tools/web-search.mjs +89 -0
  256. package/src/tools/write.mjs +55 -0
  257. package/src/ui/approval.mjs +263 -0
  258. package/src/ui/banner.mjs +235 -0
  259. package/src/ui/commands.mjs +537 -0
  260. package/src/ui/formatter.mjs +409 -0
  261. package/src/ui/icons.mjs +164 -0
  262. package/src/ui/input-dock.mjs +444 -0
  263. package/src/ui/markdown.mjs +278 -0
  264. package/src/ui/mission-report.mjs +296 -0
  265. package/src/ui/palette.mjs +189 -0
  266. package/src/ui/slash-commands.mjs +245 -0
  267. package/src/ui/spinner.mjs +116 -0
  268. package/src/ui/sub-agent.mjs +152 -0
  269. package/src/ui/term.mjs +159 -0
  270. package/src/ui/text-layout.mjs +127 -0
  271. package/src/ui/tool-card.mjs +463 -0
  272. package/src/ui/tool-details.mjs +312 -0
  273. package/src/ui/transcript-block.mjs +21 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Settings chain — user/project/local/managed (from decompiled source).
3
+ *
4
+ * Supports all 76 properties from Claude Code's settings schema.
5
+ * Five-layer hierarchy: user > project > local > managed > feature flags.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ import os from 'os';
10
+
11
+ /**
12
+ * Full settings schema with defaults.
13
+ */
14
+ export const SETTINGS_SCHEMA = {
15
+ permissions: {
16
+ defaultMode: 'default',
17
+ allowRules: [],
18
+ denyRules: [],
19
+ allowedTools: [],
20
+ deniedTools: [],
21
+ sandbox: true,
22
+ sandboxAllowPaths: [],
23
+ },
24
+ hooks: {
25
+ PreToolUse: [],
26
+ PostToolUse: [],
27
+ PreToolUseFailure: [],
28
+ PostToolUseFailure: [],
29
+ Notification: [],
30
+ Stop: [],
31
+ SessionStart: [],
32
+ },
33
+ model: 'claude-sonnet-4-6',
34
+ subagentModel: null,
35
+ fastModel: 'claude-haiku-4-5',
36
+ fastMode: false,
37
+ alwaysThinkingEnabled: false,
38
+ autoCompactEnabled: true,
39
+ fileCheckpointingEnabled: true,
40
+ promptSuggestionEnabled: true,
41
+ briefMode: false,
42
+ maxContextTokens: 180000,
43
+ maxOutputTokens: 16384,
44
+ maxTokens: 16384,
45
+ thinkingBudget: 10000,
46
+ compactThreshold: 0.8,
47
+ stream: true,
48
+ stagnationDetection: false,
49
+ stagnationThreshold: 3,
50
+ mcpServers: {},
51
+ theme: 'auto',
52
+ showThinking: false,
53
+ showToolResults: false,
54
+ showTokenUsage: true,
55
+ vimMode: false,
56
+ terminalBell: false,
57
+ telemetryEnabled: false,
58
+ debugMode: false,
59
+ enableTeams: false,
60
+ agentId: null,
61
+ cronEnabled: true,
62
+ featureFlags: {},
63
+ };
64
+
65
+ export async function loadSettings() {
66
+ const chain = [
67
+ path.join(os.homedir(), '.claude', 'settings.json'),
68
+ path.join(process.cwd(), '.claude', 'settings.json'),
69
+ path.join(process.cwd(), '.claude', 'settings.local.json'),
70
+ ];
71
+
72
+ let merged = deepClone(SETTINGS_SCHEMA);
73
+
74
+ for (const file of chain) {
75
+ try {
76
+ const data = JSON.parse(fs.readFileSync(file, 'utf-8'));
77
+ merged = deepMerge(merged, data);
78
+ } catch {
79
+ // File not found or invalid
80
+ }
81
+ }
82
+
83
+ applyEnvOverrides(merged);
84
+ return merged;
85
+ }
86
+
87
+ function deepClone(obj) {
88
+ return JSON.parse(JSON.stringify(obj));
89
+ }
90
+
91
+ function deepMerge(target, source) {
92
+ const result = { ...target };
93
+ for (const [key, value] of Object.entries(source)) {
94
+ if (value && typeof value === 'object' && !Array.isArray(value) && typeof result[key] === 'object' && !Array.isArray(result[key])) {
95
+ result[key] = deepMerge(result[key] || {}, value);
96
+ } else {
97
+ result[key] = value;
98
+ }
99
+ }
100
+ return result;
101
+ }
102
+
103
+ function applyEnvOverrides(settings) {
104
+ if (process.env.ANTHROPIC_MODEL) settings.model = process.env.ANTHROPIC_MODEL;
105
+ if (process.env.CLAUDE_CODE_SUBAGENT_MODEL) settings.subagentModel = process.env.CLAUDE_CODE_SUBAGENT_MODEL;
106
+ if (process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
107
+ const n = parseInt(process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS, 10);
108
+ if (!isNaN(n)) { settings.maxOutputTokens = n; settings.maxTokens = n; }
109
+ }
110
+ if (process.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS) {
111
+ const n = parseInt(process.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS, 10);
112
+ if (!isNaN(n)) settings.maxContextTokens = n;
113
+ }
114
+ if (process.env.CLAUDE_CODE_BRIEF === '1') settings.briefMode = true;
115
+ if (process.env.CLAUDE_CODE_DEBUG === '1') settings.debugMode = true;
116
+ if (process.env.CLAUDE_CODE_PERMISSION_MODE) settings.permissions.defaultMode = process.env.CLAUDE_CODE_PERMISSION_MODE;
117
+ if (process.env.CLAUDE_CODE_STREAMING === '0') settings.stream = false;
118
+ if (process.env.CLAUDE_CODE_THINKING === '1') settings.alwaysThinkingEnabled = true;
119
+ if (process.env.CLAUDE_CODE_DISABLE_CRON === '1') settings.cronEnabled = false;
120
+ if (process.env.CLAUDE_CODE_ENABLE_TASKS === '1') settings.enableTeams = true;
121
+ const stagnationEnabled = process.env.KEPLER_STAGNATION_DETECTION;
122
+ if (stagnationEnabled !== undefined) {
123
+ settings.stagnationDetection = ['1', 'true', 'yes', 'on'].includes(
124
+ stagnationEnabled.toLowerCase(),
125
+ );
126
+ }
127
+ const stagnationThreshold = process.env.KEPLER_STAGNATION_THRESHOLD;
128
+ if (stagnationThreshold) {
129
+ const n = parseInt(stagnationThreshold, 10);
130
+ if (Number.isInteger(n) && n >= 2) settings.stagnationThreshold = n;
131
+ }
132
+ }
@@ -0,0 +1,298 @@
1
+ /**
2
+ * AST Parser — Extract structured code knowledge from source files.
3
+ *
4
+ * Multi-language regex-based extraction (no tree-sitter dependency).
5
+ * Returns function signatures, class definitions, imports, exports.
6
+ *
7
+ * This is Phase 3 of the Investigative Funnel:
8
+ * Raw code (2000 tokens) → Structured summary (200 tokens)
9
+ */
10
+
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+
14
+ /**
15
+ * Analyze a source file and return structured knowledge.
16
+ * @param {string} filePath - absolute path
17
+ * @param {Object} [options]
18
+ * @param {number} [options.startLine] - optional line range
19
+ * @param {number} [options.endLine]
20
+ * @returns {{ success: boolean, summary: string, structure: Object }}
21
+ */
22
+ export function analyzeCode(filePath, { startLine, endLine } = {}) {
23
+ try {
24
+ let content = fs.readFileSync(filePath, 'utf-8');
25
+ const totalLines = content.split('\n').length;
26
+
27
+ if (startLine || endLine) {
28
+ const lines = content.split('\n');
29
+ const start = (startLine || 1) - 1;
30
+ const end = endLine || lines.length;
31
+ content = lines.slice(start, end).join('\n');
32
+ }
33
+
34
+ const ext = path.extname(filePath).toLowerCase();
35
+ const relPath = filePath; // caller should make relative if needed
36
+
37
+ let structure;
38
+ if (['.ts', '.tsx', '.js', '.jsx', '.mjs'].includes(ext)) {
39
+ structure = parseJavaScriptTypeScript(content);
40
+ } else if (ext === '.py') {
41
+ structure = parsePython(content);
42
+ } else if (ext === '.go') {
43
+ structure = parseGo(content);
44
+ } else if (['.rs'].includes(ext)) {
45
+ structure = parseRust(content);
46
+ } else {
47
+ structure = parseGeneric(content);
48
+ }
49
+
50
+ structure.file = path.basename(filePath);
51
+ structure.lines = totalLines;
52
+ structure.language = ext.replace('.', '');
53
+
54
+ // Build concise summary
55
+ const summary = buildSummary(structure);
56
+
57
+ return { success: true, summary, structure };
58
+ } catch (err) {
59
+ return { success: false, summary: `Error analyzing ${filePath}: ${err.message}`, structure: {} };
60
+ }
61
+ }
62
+
63
+ // ── JavaScript / TypeScript ────────────────────────────────
64
+
65
+ function parseJavaScriptTypeScript(content) {
66
+ const imports = [];
67
+ const exports = [];
68
+ const functions = [];
69
+ const classes = [];
70
+ const interfaces = [];
71
+ const types = [];
72
+ const lines = content.split('\n');
73
+
74
+ for (let i = 0; i < lines.length; i++) {
75
+ const trimmed = lines[i].trim();
76
+ const lineNum = i + 1;
77
+
78
+ // Imports
79
+ const importMatch = trimmed.match(/^import\s+(?:\{([^}]+)\}|(\w+))\s+from\s+['"]([^'"]+)['"]/);
80
+ if (importMatch) {
81
+ imports.push({ names: (importMatch[1] || importMatch[2] || '').trim(), from: importMatch[3] });
82
+ continue;
83
+ }
84
+
85
+ // Exports
86
+ if (trimmed.startsWith('export default')) {
87
+ exports.push('default');
88
+ } else if (trimmed.match(/^export\s+(const|let|var|function|class|interface|type|async)/)) {
89
+ const nameMatch = trimmed.match(/^export\s+(?:async\s+)?(?:const|let|var|function|class|interface|type)\s+(\w+)/);
90
+ if (nameMatch) exports.push(nameMatch[1]);
91
+ }
92
+
93
+ // Functions
94
+ const fnMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/);
95
+ if (fnMatch) {
96
+ functions.push({ name: fnMatch[1], params: fnMatch[2].trim(), line: lineNum });
97
+ continue;
98
+ }
99
+
100
+ // Arrow functions
101
+ const arrowMatch = trimmed.match(/^(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*\w+)?\s*=>/);
102
+ if (arrowMatch) {
103
+ functions.push({ name: arrowMatch[1], params: arrowMatch[2].trim(), line: lineNum });
104
+ continue;
105
+ }
106
+
107
+ // Classes
108
+ const classMatch = trimmed.match(/^(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?/);
109
+ if (classMatch) {
110
+ classes.push({ name: classMatch[1], extends: classMatch[2] || null, line: lineNum });
111
+ continue;
112
+ }
113
+
114
+ // Methods
115
+ const methodMatch = trimmed.match(/^(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*\w+)?\s*\{/);
116
+ if (methodMatch && !['if', 'for', 'while', 'switch', 'catch'].includes(methodMatch[1])) {
117
+ functions.push({ name: methodMatch[1], params: methodMatch[2].trim(), line: lineNum, isMethod: true });
118
+ }
119
+
120
+ // Interfaces
121
+ const ifaceMatch = trimmed.match(/^(?:export\s+)?interface\s+(\w+)/);
122
+ if (ifaceMatch) interfaces.push({ name: ifaceMatch[1], line: lineNum });
123
+
124
+ // Types
125
+ const typeMatch = trimmed.match(/^(?:export\s+)?type\s+(\w+)\s*=/);
126
+ if (typeMatch) types.push({ name: typeMatch[1], line: lineNum });
127
+ }
128
+
129
+ return { imports, exports, functions, classes, interfaces, types };
130
+ }
131
+
132
+ // ── Python ─────────────────────────────────────────────────
133
+
134
+ function parsePython(content) {
135
+ const imports = [];
136
+ const functions = [];
137
+ const classes = [];
138
+ const decorators = [];
139
+ const lines = content.split('\n');
140
+
141
+ for (let i = 0; i < lines.length; i++) {
142
+ const trimmed = lines[i].trim();
143
+ const lineNum = i + 1;
144
+
145
+ const importMatch = trimmed.match(/^(?:from\s+(\S+)\s+)?import\s+(.+)/);
146
+ if (importMatch) {
147
+ imports.push({ from: importMatch[1] || '', names: importMatch[2].trim() });
148
+ continue;
149
+ }
150
+
151
+ if (trimmed.startsWith('@')) {
152
+ decorators.push(trimmed);
153
+ continue;
154
+ }
155
+
156
+ const fnMatch = trimmed.match(/^(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)/);
157
+ if (fnMatch) {
158
+ functions.push({ name: fnMatch[1], params: fnMatch[2].trim(), line: lineNum });
159
+ continue;
160
+ }
161
+
162
+ const classMatch = trimmed.match(/^class\s+(\w+)(?:\(([^)]*)\))?/);
163
+ if (classMatch) {
164
+ classes.push({ name: classMatch[1], bases: classMatch[2] || '', line: lineNum });
165
+ }
166
+ }
167
+
168
+ return { imports, functions, classes, decorators };
169
+ }
170
+
171
+ // ── Go ─────────────────────────────────────────────────────
172
+
173
+ function parseGo(content) {
174
+ const imports = [];
175
+ const functions = [];
176
+ const structs = [];
177
+
178
+ for (const line of content.split('\n')) {
179
+ const trimmed = line.trim();
180
+
181
+ const importMatch = trimmed.match(/^import\s+"([^"]+)"/);
182
+ if (importMatch) imports.push(importMatch[1]);
183
+
184
+ const fnMatch = trimmed.match(/^func\s+(?:\((\w+)\s+\*?(\w+)\)\s+)?(\w+)\s*\(([^)]*)\)/);
185
+ if (fnMatch) {
186
+ functions.push({
187
+ name: fnMatch[3],
188
+ params: fnMatch[4]?.trim() || '',
189
+ receiver: fnMatch[2] || null,
190
+ });
191
+ }
192
+
193
+ const structMatch = trimmed.match(/^type\s+(\w+)\s+struct/);
194
+ if (structMatch) structs.push(structMatch[1]);
195
+ }
196
+
197
+ return { imports, functions, structs };
198
+ }
199
+
200
+ // ── Rust ───────────────────────────────────────────────────
201
+
202
+ function parseRust(content) {
203
+ const functions = [];
204
+ const structs = [];
205
+ const impls = [];
206
+
207
+ for (const line of content.split('\n')) {
208
+ const trimmed = line.trim();
209
+
210
+ const fnMatch = trimmed.match(/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*\(([^)]*)\)/);
211
+ if (fnMatch) functions.push({ name: fnMatch[1], params: fnMatch[2].trim() });
212
+
213
+ const structMatch = trimmed.match(/^(?:pub\s+)?struct\s+(\w+)/);
214
+ if (structMatch) structs.push(structMatch[1]);
215
+
216
+ const implMatch = trimmed.match(/^impl\s+(?:<[^>]+>\s+)?(\w+)/);
217
+ if (implMatch) impls.push(implMatch[1]);
218
+ }
219
+
220
+ return { functions, structs, impls };
221
+ }
222
+
223
+ // ── Generic fallback ───────────────────────────────────────
224
+
225
+ function parseGeneric(content) {
226
+ const functions = [];
227
+ const lines = content.split('\n');
228
+
229
+ for (const line of lines) {
230
+ const trimmed = line.trim();
231
+ // Try common patterns
232
+ const fnMatch = trimmed.match(/^(?:(?:pub|public|private|protected|static|async|export)\s+)*(?:function|def|fn|func)\s+(\w+)/);
233
+ if (fnMatch) functions.push({ name: fnMatch[1] });
234
+ }
235
+
236
+ return { functions };
237
+ }
238
+
239
+ // ── Summary Builder ────────────────────────────────────────
240
+
241
+ function buildSummary(structure) {
242
+ const parts = [];
243
+ parts.push(`${structure.file} (${structure.lines} lines, ${structure.language})`);
244
+ parts.push(`Use read_file with start_line/end_line to read specific sections.`);
245
+ parts.push('');
246
+
247
+ if (structure.classes?.length) {
248
+ for (const cls of structure.classes) {
249
+ const ext = cls.extends ? ` extends ${cls.extends}` : (cls.bases ? `(${cls.bases})` : '');
250
+ const ln = cls.line ? ` [line ${cls.line}]` : '';
251
+ parts.push(` class ${cls.name}${ext}${ln}`);
252
+ }
253
+ }
254
+
255
+ if (structure.interfaces?.length) {
256
+ const ifaceList = structure.interfaces.map(i =>
257
+ typeof i === 'string' ? i : `${i.name} [line ${i.line}]`
258
+ ).join(', ');
259
+ parts.push(` interfaces: ${ifaceList}`);
260
+ }
261
+
262
+ if (structure.types?.length) {
263
+ const typeList = structure.types.map(t =>
264
+ typeof t === 'string' ? t : `${t.name} [line ${t.line}]`
265
+ ).join(', ');
266
+ parts.push(` types: ${typeList}`);
267
+ }
268
+
269
+ if (structure.functions?.length) {
270
+ for (const fn of structure.functions.slice(0, 20)) {
271
+ const receiver = fn.receiver ? `(${fn.receiver}) ` : '';
272
+ const method = fn.isMethod ? ' ' : '';
273
+ const ln = fn.line ? ` [line ${fn.line}]` : '';
274
+ parts.push(`${method} ${receiver}${fn.name}(${fn.params || ''})${ln}`);
275
+ }
276
+ if (structure.functions.length > 20) {
277
+ parts.push(` ... and ${structure.functions.length - 20} more functions`);
278
+ }
279
+ }
280
+
281
+ if (structure.structs?.length) {
282
+ parts.push(` structs: ${structure.structs.join(', ')}`);
283
+ }
284
+
285
+ if (structure.imports?.length) {
286
+ const importSummary = structure.imports.slice(0, 5).map(i =>
287
+ i.from ? `${i.from}` : (typeof i === 'string' ? i : i.names)
288
+ ).join(', ');
289
+ const more = structure.imports.length > 5 ? ` +${structure.imports.length - 5} more` : '';
290
+ parts.push(` imports: ${importSummary}${more}`);
291
+ }
292
+
293
+ if (structure.exports?.length) {
294
+ parts.push(` exports: ${structure.exports.join(', ')}`);
295
+ }
296
+
297
+ return parts.join('\n');
298
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * BM25 Search — T20: Lightweight text search index (~100 LOC).
3
+ * Parameters: k1=1.2, b=0.75 (standard BM25 tuning).
4
+ */
5
+
6
+ export class BM25Index {
7
+ constructor(k1 = 1.2, b = 0.75) {
8
+ this.k1 = k1;
9
+ this.b = b;
10
+ this.docs = []; // [{ id, tokens, length }]
11
+ this.df = new Map(); // term → doc frequency
12
+ this.avgDl = 0;
13
+ this.N = 0;
14
+ }
15
+
16
+ /** Tokenize text into lowercase terms. */
17
+ static tokenize(text) {
18
+ return text.toLowerCase().match(/[a-z_][a-z0-9_]*/g) || [];
19
+ }
20
+
21
+ /** Add a document to the index. */
22
+ addDocument(id, text) {
23
+ const tokens = BM25Index.tokenize(text);
24
+ const tf = new Map();
25
+ for (const t of tokens) tf.set(t, (tf.get(t) || 0) + 1);
26
+ this.docs.push({ id, tf, length: tokens.length });
27
+
28
+ for (const term of tf.keys()) {
29
+ this.df.set(term, (this.df.get(term) || 0) + 1);
30
+ }
31
+ this.N = this.docs.length;
32
+ this.avgDl = this.docs.reduce((s, d) => s + d.length, 0) / this.N;
33
+ }
34
+
35
+ /** Build index from array of { id, text } objects. */
36
+ buildIndex(documents) {
37
+ this.docs = [];
38
+ this.df = new Map();
39
+ for (const doc of documents) {
40
+ this.addDocument(doc.id, doc.text);
41
+ }
42
+ }
43
+
44
+ /** Search for query, return top-K results sorted by BM25 score. */
45
+ search(query, topK = 10) {
46
+ const queryTokens = BM25Index.tokenize(query);
47
+ if (queryTokens.length === 0) return [];
48
+
49
+ const scores = [];
50
+
51
+ for (const doc of this.docs) {
52
+ let score = 0;
53
+ for (const qt of queryTokens) {
54
+ const tf = doc.tf.get(qt) || 0;
55
+ if (tf === 0) continue;
56
+ const dfVal = this.df.get(qt) || 0;
57
+ const idf = Math.log((this.N - dfVal + 0.5) / (dfVal + 0.5) + 1);
58
+ const tfNorm = (tf * (this.k1 + 1)) / (tf + this.k1 * (1 - this.b + this.b * doc.length / this.avgDl));
59
+ score += idf * tfNorm;
60
+ }
61
+ if (score > 0) scores.push({ id: doc.id, score });
62
+ }
63
+
64
+ return scores.sort((a, b) => b.score - a.score).slice(0, topK);
65
+ }
66
+
67
+ /** Serialize index to JSON. */
68
+ toJSON() {
69
+ return {
70
+ k1: this.k1, b: this.b, N: this.N, avgDl: this.avgDl,
71
+ docs: this.docs.map(d => ({ id: d.id, tf: Object.fromEntries(d.tf), length: d.length })),
72
+ df: Object.fromEntries(this.df),
73
+ };
74
+ }
75
+
76
+ /** Load index from JSON. */
77
+ static fromJSON(data) {
78
+ const idx = new BM25Index(data.k1, data.b);
79
+ idx.N = data.N;
80
+ idx.avgDl = data.avgDl;
81
+ idx.df = new Map(Object.entries(data.df));
82
+ idx.docs = data.docs.map(d => ({ id: d.id, tf: new Map(Object.entries(d.tf)), length: d.length }));
83
+ return idx;
84
+ }
85
+ }