botholomew 0.16.4 → 0.18.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 (98) hide show
  1. package/README.md +46 -41
  2. package/package.json +4 -9
  3. package/src/chat/agent.ts +37 -40
  4. package/src/chat/session.ts +10 -10
  5. package/src/cli.ts +0 -2
  6. package/src/commands/capabilities.ts +35 -33
  7. package/src/commands/context.ts +133 -221
  8. package/src/commands/init.ts +22 -1
  9. package/src/commands/mcpx.ts +21 -8
  10. package/src/commands/nuke.ts +52 -15
  11. package/src/commands/prepare.ts +16 -13
  12. package/src/config/loader.ts +1 -8
  13. package/src/config/schemas.ts +6 -0
  14. package/src/constants.ts +16 -32
  15. package/src/init/index.ts +52 -27
  16. package/src/mcpx/client.ts +21 -5
  17. package/src/mem/client.ts +33 -0
  18. package/src/{context → prompts}/capabilities.ts +11 -7
  19. package/src/schedules/store.ts +1 -1
  20. package/src/tasks/store.ts +1 -1
  21. package/src/threads/store.ts +1 -1
  22. package/src/tools/capabilities/refresh.ts +1 -1
  23. package/src/tools/membot/adapter.ts +111 -0
  24. package/src/tools/membot/copy.ts +59 -0
  25. package/src/tools/membot/count_lines.ts +53 -0
  26. package/src/tools/membot/edit.ts +72 -0
  27. package/src/tools/membot/exists.ts +54 -0
  28. package/src/tools/membot/index.ts +26 -0
  29. package/src/tools/{context → membot}/pipe.ts +34 -32
  30. package/src/tools/registry.ts +6 -37
  31. package/src/tools/tool.ts +6 -8
  32. package/src/tui/App.tsx +3 -4
  33. package/src/tui/components/ContextPanel.tsx +109 -226
  34. package/src/tui/components/HelpPanel.tsx +2 -2
  35. package/src/tui/components/StatusBar.tsx +0 -6
  36. package/src/tui/components/ThreadPanel.tsx +8 -7
  37. package/src/tui/wrapDetail.ts +11 -0
  38. package/src/worker/heartbeat.ts +0 -20
  39. package/src/worker/index.ts +13 -13
  40. package/src/worker/llm.ts +7 -9
  41. package/src/worker/prompt.ts +25 -13
  42. package/src/worker/spawn.ts +1 -1
  43. package/src/worker/tick.ts +10 -9
  44. package/src/commands/db.ts +0 -119
  45. package/src/commands/with-db.ts +0 -22
  46. package/src/context/chunker.ts +0 -275
  47. package/src/context/embedder-impl.ts +0 -100
  48. package/src/context/embedder.ts +0 -9
  49. package/src/context/fetcher-errors.ts +0 -8
  50. package/src/context/fetcher.ts +0 -515
  51. package/src/context/locks.ts +0 -146
  52. package/src/context/markdown-converter.ts +0 -186
  53. package/src/context/reindex.ts +0 -198
  54. package/src/context/store.ts +0 -841
  55. package/src/context/url-utils.ts +0 -25
  56. package/src/db/connection.ts +0 -255
  57. package/src/db/doctor.ts +0 -235
  58. package/src/db/embeddings.ts +0 -317
  59. package/src/db/query.ts +0 -56
  60. package/src/db/schema.ts +0 -93
  61. package/src/db/sql/1-core_tables.sql +0 -53
  62. package/src/db/sql/10-dedupe_context_items.sql +0 -26
  63. package/src/db/sql/11-rebuild_hnsw.sql +0 -8
  64. package/src/db/sql/12-workers.sql +0 -66
  65. package/src/db/sql/13-drive-paths.sql +0 -47
  66. package/src/db/sql/14-drop_hnsw_index.sql +0 -8
  67. package/src/db/sql/15-fts_index.sql +0 -8
  68. package/src/db/sql/16-source_url.sql +0 -7
  69. package/src/db/sql/17-worker_log_path.sql +0 -3
  70. package/src/db/sql/18-reset_embeddings_for_local.sql +0 -39
  71. package/src/db/sql/19-disk_backed_index.sql +0 -36
  72. package/src/db/sql/2-logging_tables.sql +0 -24
  73. package/src/db/sql/20-drop_db_tables_for_files.sql +0 -19
  74. package/src/db/sql/3-daemon_state.sql +0 -5
  75. package/src/db/sql/4-unique_context_path.sql +0 -1
  76. package/src/db/sql/5-reset_embeddings_for_openai.sql +0 -1
  77. package/src/db/sql/6-vss_index.sql +0 -7
  78. package/src/db/sql/7-drop_embeddings_fk.sql +0 -23
  79. package/src/db/sql/8-task_output.sql +0 -1
  80. package/src/db/sql/9-source-type.sql +0 -1
  81. package/src/tools/context/read-large-result.ts +0 -33
  82. package/src/tools/dir/create.ts +0 -47
  83. package/src/tools/dir/size.ts +0 -77
  84. package/src/tools/dir/tree.ts +0 -124
  85. package/src/tools/file/copy.ts +0 -73
  86. package/src/tools/file/count-lines.ts +0 -54
  87. package/src/tools/file/delete.ts +0 -83
  88. package/src/tools/file/edit.ts +0 -76
  89. package/src/tools/file/exists.ts +0 -33
  90. package/src/tools/file/info.ts +0 -66
  91. package/src/tools/file/move.ts +0 -66
  92. package/src/tools/file/read.ts +0 -67
  93. package/src/tools/file/write.ts +0 -58
  94. package/src/tools/search/fuse.ts +0 -96
  95. package/src/tools/search/index.ts +0 -127
  96. package/src/tools/search/regexp.ts +0 -82
  97. package/src/tools/search/semantic.ts +0 -167
  98. /package/src/{db → utils}/uuid.ts +0 -0
@@ -1,167 +0,0 @@
1
- import type { BotholomewConfig } from "../../config/schemas.ts";
2
- import { embed, embedSingle } from "../../context/embedder.ts";
3
- import { listContextDir, readContextFile } from "../../context/store.ts";
4
- import { withDb } from "../../db/connection.ts";
5
- import { indexStats, searchSemantic } from "../../db/embeddings.ts";
6
- import { globToRegex } from "./regexp.ts";
7
-
8
- export interface SemanticHit {
9
- path: string;
10
- chunk_index: number;
11
- chunk_content: string;
12
- score: number;
13
- }
14
-
15
- export interface SemanticOptions {
16
- query: string;
17
- scope?: string;
18
- glob?: string;
19
- limit?: number;
20
- }
21
-
22
- // On-the-fly fallback (used when the index sidecar is empty / stale).
23
- // One chunk per file truncated to MAX_CHARS; the indexed path is much faster
24
- // and supports proper chunking via `botholomew context reindex`.
25
- const MAX_CHARS = 4_000;
26
- const MAX_FILES_TO_EMBED = 200;
27
-
28
- /**
29
- * Semantic search over `context/`. Prefers the persistent index sidecar
30
- * (`context_index` table, populated by `botholomew context reindex`) when
31
- * it has rows. Falls back to embedding files on the fly so a fresh project
32
- * still gets useful results before the user has reindexed once.
33
- */
34
- export async function runSemantic(
35
- projectDir: string,
36
- config: Required<BotholomewConfig>,
37
- dbPath: string | null,
38
- options: SemanticOptions,
39
- ): Promise<SemanticHit[]> {
40
- if (dbPath) {
41
- const indexed = await tryIndexedSearch(dbPath, config, options);
42
- if (indexed) return indexed;
43
- }
44
- return runOnTheFly(projectDir, config, options);
45
- }
46
-
47
- async function tryIndexedSearch(
48
- dbPath: string,
49
- config: Required<BotholomewConfig>,
50
- options: SemanticOptions,
51
- ): Promise<SemanticHit[] | null> {
52
- let stats: Awaited<ReturnType<typeof indexStats>>;
53
- try {
54
- stats = await withDb(dbPath, indexStats);
55
- } catch {
56
- return null;
57
- }
58
- if (stats.embedded === 0) return null;
59
-
60
- const queryVec = await embedSingle(options.query, config);
61
- const limit = options.limit ?? 100;
62
- const rows = await withDb(dbPath, (conn) =>
63
- searchSemantic(conn, queryVec, limit * 4),
64
- );
65
-
66
- const globRegex = options.glob ? globToRegex(options.glob) : null;
67
- const scope = options.scope
68
- ? options.scope.endsWith("/")
69
- ? options.scope
70
- : `${options.scope}/`
71
- : null;
72
-
73
- const filtered: SemanticHit[] = [];
74
- for (const r of rows) {
75
- if (scope && !r.path.startsWith(scope) && r.path !== options.scope) {
76
- continue;
77
- }
78
- if (globRegex) {
79
- const filename = r.path.split("/").pop() ?? "";
80
- if (!globRegex.test(filename)) continue;
81
- }
82
- filtered.push({
83
- path: r.path,
84
- chunk_index: r.chunk_index,
85
- chunk_content: r.chunk_content,
86
- score: r.score,
87
- });
88
- if (filtered.length >= limit) break;
89
- }
90
- return filtered;
91
- }
92
-
93
- async function runOnTheFly(
94
- projectDir: string,
95
- config: Required<BotholomewConfig>,
96
- options: SemanticOptions,
97
- ): Promise<SemanticHit[]> {
98
- const entries = await listContextDir(projectDir, options.scope ?? "", {
99
- recursive: true,
100
- });
101
- const globRegex = options.glob ? globToRegex(options.glob) : null;
102
-
103
- const candidates: Array<{ path: string; content: string }> = [];
104
- for (const entry of entries) {
105
- if (entry.is_directory) continue;
106
- if (!entry.is_textual) continue;
107
- if (globRegex) {
108
- const filename = entry.path.split("/").pop() ?? "";
109
- if (!globRegex.test(filename)) continue;
110
- }
111
- let content: string;
112
- try {
113
- content = await readContextFile(projectDir, entry.path);
114
- } catch {
115
- continue;
116
- }
117
- if (content.trim().length === 0) continue;
118
- candidates.push({
119
- path: entry.path,
120
- content: content.slice(0, MAX_CHARS),
121
- });
122
- if (candidates.length >= MAX_FILES_TO_EMBED) break;
123
- }
124
-
125
- if (candidates.length === 0) return [];
126
-
127
- const [queryVec, fileVecs] = await Promise.all([
128
- embedSingle(options.query, config),
129
- embed(
130
- candidates.map((c) => c.content),
131
- config,
132
- ),
133
- ]);
134
-
135
- const limit = options.limit ?? 100;
136
- const scored: SemanticHit[] = [];
137
- for (let i = 0; i < candidates.length; i++) {
138
- const c = candidates[i];
139
- const v = fileVecs[i];
140
- if (!c || !v) continue;
141
- const score = cosine(queryVec, v);
142
- scored.push({
143
- path: c.path,
144
- chunk_index: 0,
145
- chunk_content: c.content,
146
- score,
147
- });
148
- }
149
- scored.sort((a, b) => b.score - a.score);
150
- return scored.slice(0, limit);
151
- }
152
-
153
- function cosine(a: number[], b: number[]): number {
154
- let dot = 0;
155
- let na = 0;
156
- let nb = 0;
157
- const len = Math.min(a.length, b.length);
158
- for (let i = 0; i < len; i++) {
159
- const ai = a[i] ?? 0;
160
- const bi = b[i] ?? 0;
161
- dot += ai * bi;
162
- na += ai * ai;
163
- nb += bi * bi;
164
- }
165
- const denom = Math.sqrt(na) * Math.sqrt(nb);
166
- return denom === 0 ? 0 : dot / denom;
167
- }
File without changes