devsmind-mcp 2.3.0 → 3.0.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 (130) hide show
  1. package/README.md +326 -234
  2. package/dist/cli/activity.d.ts +9 -0
  3. package/dist/cli/activity.js +69 -0
  4. package/dist/cli/activity.js.map +1 -0
  5. package/dist/cli/describe.d.ts +31 -0
  6. package/dist/cli/describe.js +201 -0
  7. package/dist/cli/describe.js.map +1 -0
  8. package/dist/cli/diff.d.ts +14 -0
  9. package/dist/cli/diff.js +161 -0
  10. package/dist/cli/diff.js.map +1 -0
  11. package/dist/cli/embed.d.ts +6 -0
  12. package/dist/cli/embed.js +109 -0
  13. package/dist/cli/embed.js.map +1 -0
  14. package/dist/cli/extract-agent.d.ts +66 -0
  15. package/dist/cli/extract-agent.js +192 -0
  16. package/dist/cli/extract-agent.js.map +1 -0
  17. package/dist/cli/feedback.d.ts +11 -0
  18. package/dist/cli/feedback.js +90 -0
  19. package/dist/cli/feedback.js.map +1 -0
  20. package/dist/cli/index.js +128 -5
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/cli/init.d.ts +30 -0
  23. package/dist/cli/init.js +98 -18
  24. package/dist/cli/init.js.map +1 -1
  25. package/dist/cli/integrations/memory-topics.d.ts +44 -0
  26. package/dist/cli/integrations/memory-topics.js +295 -0
  27. package/dist/cli/integrations/memory-topics.js.map +1 -0
  28. package/dist/cli/integrations/memory.d.ts +11 -5
  29. package/dist/cli/integrations/memory.js +132 -36
  30. package/dist/cli/integrations/memory.js.map +1 -1
  31. package/dist/cli/integrations/prompt.d.ts +8 -0
  32. package/dist/cli/integrations/prompt.js +24 -3
  33. package/dist/cli/integrations/prompt.js.map +1 -1
  34. package/dist/cli/integrations/registry.d.ts +19 -5
  35. package/dist/cli/integrations/registry.js +23 -13
  36. package/dist/cli/integrations/registry.js.map +1 -1
  37. package/dist/cli/llm-client.d.ts +116 -0
  38. package/dist/cli/llm-client.js +322 -0
  39. package/dist/cli/llm-client.js.map +1 -0
  40. package/dist/cli/prune.js +4 -3
  41. package/dist/cli/prune.js.map +1 -1
  42. package/dist/cli/rule.d.ts +22 -2
  43. package/dist/cli/rule.js +116 -56
  44. package/dist/cli/rule.js.map +1 -1
  45. package/dist/cli/runner.d.ts +21 -0
  46. package/dist/cli/runner.js +94 -276
  47. package/dist/cli/runner.js.map +1 -1
  48. package/dist/cli/sync-progress.d.ts +9 -0
  49. package/dist/cli/sync-progress.js +41 -0
  50. package/dist/cli/sync-progress.js.map +1 -0
  51. package/dist/cli/sync.js +5 -2
  52. package/dist/cli/sync.js.map +1 -1
  53. package/dist/cli/view.js +3 -2
  54. package/dist/cli/view.js.map +1 -1
  55. package/dist/cli/workflow.js +26 -20
  56. package/dist/cli/workflow.js.map +1 -1
  57. package/dist/db/activity.d.ts +200 -0
  58. package/dist/db/activity.js +347 -0
  59. package/dist/db/activity.js.map +1 -0
  60. package/dist/db/analyze.js +25 -9
  61. package/dist/db/analyze.js.map +1 -1
  62. package/dist/db/database.d.ts +607 -79
  63. package/dist/db/database.js +1936 -488
  64. package/dist/db/database.js.map +1 -1
  65. package/dist/db/edges.d.ts +24 -0
  66. package/dist/db/edges.js +84 -0
  67. package/dist/db/edges.js.map +1 -1
  68. package/dist/db/embedder.d.ts +39 -0
  69. package/dist/db/embedder.js +0 -0
  70. package/dist/db/embedder.js.map +1 -0
  71. package/dist/db/feedback.d.ts +128 -0
  72. package/dist/db/feedback.js +182 -0
  73. package/dist/db/feedback.js.map +1 -0
  74. package/dist/db/file-diff.d.ts +32 -0
  75. package/dist/db/file-diff.js +110 -0
  76. package/dist/db/file-diff.js.map +1 -0
  77. package/dist/db/grep.d.ts +78 -0
  78. package/dist/db/grep.js +475 -0
  79. package/dist/db/grep.js.map +1 -0
  80. package/dist/db/message-revert.d.ts +63 -0
  81. package/dist/db/message-revert.js +258 -0
  82. package/dist/db/message-revert.js.map +1 -0
  83. package/dist/db/revert.d.ts +31 -0
  84. package/dist/db/revert.js +108 -0
  85. package/dist/db/revert.js.map +1 -0
  86. package/dist/db/schema.d.ts +49 -4
  87. package/dist/db/schema.js +125 -73
  88. package/dist/db/schema.js.map +1 -1
  89. package/dist/db/search-index.d.ts +65 -0
  90. package/dist/db/search-index.js +74 -0
  91. package/dist/db/search-index.js.map +1 -0
  92. package/dist/db/staging.d.ts +90 -5
  93. package/dist/db/staging.js +148 -22
  94. package/dist/db/staging.js.map +1 -1
  95. package/dist/mcp/server.d.ts +29 -7
  96. package/dist/mcp/server.js +2755 -745
  97. package/dist/mcp/server.js.map +1 -1
  98. package/dist/mcp/vendor/3d-force-graph.min.js +5 -0
  99. package/dist/mcp/vendor/force-graph.min.js +5 -0
  100. package/dist/mcp/vendor/model/model_int8.onnx +0 -0
  101. package/dist/mcp/vendor/model/vocab.txt +30522 -0
  102. package/dist/mcp/vendor/three.min.js +7 -0
  103. package/dist/mcp/view.css +419 -0
  104. package/dist/mcp/view.html +161 -0
  105. package/dist/mcp/view.js +245 -0
  106. package/dist/mcp/view_chat.js +382 -0
  107. package/dist/mcp/view_graph.js +576 -0
  108. package/dist/mcp/visualizer.d.ts +25 -2
  109. package/dist/mcp/visualizer.js +31 -4
  110. package/dist/mcp/visualizer.js.map +1 -1
  111. package/dist/utils/ast.d.ts +187 -0
  112. package/dist/utils/ast.js +640 -20
  113. package/dist/utils/ast.js.map +1 -1
  114. package/dist/utils/diff.d.ts +44 -0
  115. package/dist/utils/diff.js +78 -0
  116. package/dist/utils/diff.js.map +1 -0
  117. package/dist/utils/edit.d.ts +47 -0
  118. package/dist/utils/edit.js +196 -0
  119. package/dist/utils/edit.js.map +1 -0
  120. package/dist/utils/scanner.js +12 -4
  121. package/dist/utils/scanner.js.map +1 -1
  122. package/dist/utils/tokenize.d.ts +45 -0
  123. package/dist/utils/tokenize.js +129 -0
  124. package/dist/utils/tokenize.js.map +1 -0
  125. package/dist/utils/version.d.ts +14 -0
  126. package/dist/utils/version.js +61 -0
  127. package/dist/utils/version.js.map +1 -0
  128. package/package.json +18 -5
  129. package/dist/mcp/visualizer_2d.html +0 -635
  130. package/dist/mcp/visualizer_3d.html +0 -613
package/dist/db/schema.js CHANGED
@@ -1,78 +1,130 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.INIT_SCHEMA_SQL = void 0;
4
- exports.INIT_SCHEMA_SQL = `
5
- CREATE TABLE IF NOT EXISTS nodes (
6
- id TEXT PRIMARY KEY,
7
- type TEXT NOT NULL,
8
- name TEXT NOT NULL,
9
- file_path TEXT NOT NULL,
10
- signature TEXT,
11
- deprecated INTEGER DEFAULT 0,
12
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP
13
- );
14
-
15
- CREATE TABLE IF NOT EXISTS node_connections (
16
- source_node_id TEXT,
17
- target_node_id TEXT,
18
- PRIMARY KEY (source_node_id, target_node_id),
19
- FOREIGN KEY (source_node_id) REFERENCES nodes (id) ON DELETE CASCADE,
20
- FOREIGN KEY (target_node_id) REFERENCES nodes (id) ON DELETE CASCADE
21
- );
22
-
23
- CREATE TABLE IF NOT EXISTS history (
24
- id TEXT PRIMARY KEY,
25
- node_id TEXT NOT NULL,
26
- session_id TEXT NOT NULL,
27
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
28
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
29
- code_snapshot TEXT NOT NULL,
30
- reasoning TEXT NOT NULL,
31
- FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE
32
- );
33
-
34
- CREATE TABLE IF NOT EXISTS system_meta (
35
- key TEXT PRIMARY KEY,
36
- value TEXT NOT NULL,
37
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
38
- );
39
-
40
- CREATE TABLE IF NOT EXISTS workflows (
41
- id TEXT PRIMARY KEY,
42
- name TEXT NOT NULL,
43
- description TEXT NOT NULL,
44
- status TEXT NOT NULL DEFAULT 'active',
45
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
46
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
47
- );
48
-
49
- CREATE TABLE IF NOT EXISTS workflow_steps (
50
- id TEXT PRIMARY KEY,
51
- workflow_id TEXT NOT NULL,
52
- step_index INTEGER NOT NULL,
53
- summary TEXT NOT NULL,
54
- pending_tasks TEXT,
55
- history_ids TEXT,
56
- session_id TEXT,
57
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
58
- FOREIGN KEY (workflow_id) REFERENCES workflows (id) ON DELETE CASCADE
59
- );
60
-
61
- CREATE TABLE IF NOT EXISTS workflow_artifacts (
62
- id TEXT PRIMARY KEY,
63
- workflow_id TEXT NOT NULL,
64
- step_id TEXT,
65
- type TEXT NOT NULL,
66
- source_name TEXT NOT NULL,
67
- file_path TEXT NOT NULL,
68
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
69
- FOREIGN KEY (workflow_id) REFERENCES workflows (id) ON DELETE CASCADE
70
- );
71
-
72
- -- Index for searching nodes by name and type
73
- CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes (name);
74
- CREATE INDEX IF NOT EXISTS idx_history_node_id ON history (node_id);
75
- CREATE INDEX IF NOT EXISTS idx_workflow_steps_workflow_id ON workflow_steps (workflow_id);
76
- CREATE INDEX IF NOT EXISTS idx_workflow_artifacts_workflow_id ON workflow_artifacts (workflow_id);
4
+ exports.INIT_SCHEMA_SQL = `
5
+ CREATE TABLE IF NOT EXISTS nodes (
6
+ id TEXT PRIMARY KEY,
7
+ type TEXT NOT NULL,
8
+ name TEXT NOT NULL,
9
+ file_path TEXT NOT NULL,
10
+ signature TEXT,
11
+ description TEXT,
12
+ aliases TEXT DEFAULT '[]',
13
+ deprecated INTEGER DEFAULT 0,
14
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
15
+ );
16
+
17
+ CREATE TABLE IF NOT EXISTS node_connections (
18
+ source_node_id TEXT,
19
+ target_node_id TEXT,
20
+ PRIMARY KEY (source_node_id, target_node_id),
21
+ FOREIGN KEY (source_node_id) REFERENCES nodes (id) ON DELETE CASCADE,
22
+ FOREIGN KEY (target_node_id) REFERENCES nodes (id) ON DELETE CASCADE
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS history (
26
+ id TEXT PRIMARY KEY,
27
+ node_id TEXT NOT NULL,
28
+ session_id TEXT NOT NULL,
29
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
30
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
31
+ code_snapshot TEXT NOT NULL,
32
+ reasoning TEXT NOT NULL,
33
+ FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE
34
+ );
35
+
36
+ CREATE TABLE IF NOT EXISTS system_meta (
37
+ key TEXT PRIMARY KEY,
38
+ value TEXT NOT NULL,
39
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
40
+ );
41
+
42
+ -- Local, derived search index — never synced or committed, purely a cache rebuilt from
43
+ -- nodes+history whenever search-index.ts's fingerprint check finds it stale. Same category as
44
+ -- brain.db itself: safe to wipe and rebuild from the source of truth at any time.
45
+ CREATE TABLE IF NOT EXISTS node_tokens (
46
+ node_id TEXT NOT NULL,
47
+ token TEXT NOT NULL,
48
+ field TEXT NOT NULL,
49
+ tf INTEGER NOT NULL DEFAULT 1,
50
+ PRIMARY KEY (node_id, token, field)
51
+ );
52
+ CREATE INDEX IF NOT EXISTS idx_node_tokens_token ON node_tokens (token);
53
+
54
+ -- Semantic (dense) vectors, one per node, from the vendored ONNX embedder (src/db/embedder.ts).
55
+ -- Unlike node_tokens, these ARE synced via git (vectors/**/*.json, mirroring graph/**/*.json) —
56
+ -- deliberately, per an explicit product decision to trade repo size for teammates skipping local
57
+ -- inference. No foreign key to nodes(id): syncFromDisk() runs its destructive graph pass under
58
+ -- foreign_keys=OFF, so a cascade here would silently never fire; orphans are swept explicitly
59
+ -- instead (see syncFromDisk's vectors pass). model_id guards the git-sync hazard — vectors from
60
+ -- a different embedding model are structurally valid but numerically meaningless, so any reader
61
+ -- MUST check model_id before trusting a row, not just check for its presence.
62
+ CREATE TABLE IF NOT EXISTS node_vectors (
63
+ node_id TEXT PRIMARY KEY,
64
+ model_id TEXT NOT NULL,
65
+ dim INTEGER NOT NULL,
66
+ description_hash TEXT NOT NULL,
67
+ vector BLOB NOT NULL
68
+ );
69
+
70
+ -- status is vestigial: still declared (NOT NULL DEFAULT makes an INSERT that omits it succeed)
71
+ -- but no longer read or written. archived replaces it -- see DbWorkflow.
72
+ CREATE TABLE IF NOT EXISTS workflows (
73
+ id TEXT PRIMARY KEY,
74
+ name TEXT NOT NULL,
75
+ description TEXT NOT NULL,
76
+ status TEXT NOT NULL DEFAULT 'active',
77
+ archived INTEGER NOT NULL DEFAULT 0,
78
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
79
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
80
+ );
81
+
82
+ -- pending_tasks and history_ids are vestigial: never written or read by current code, and kept
83
+ -- only so an older CLI opening the same brain.db does not hit "no such column". They are dropped
84
+ -- from DbWorkflowStep, so nothing in TypeScript can reach them by accident.
85
+ CREATE TABLE IF NOT EXISTS workflow_steps (
86
+ id TEXT PRIMARY KEY,
87
+ workflow_id TEXT NOT NULL,
88
+ step_index INTEGER NOT NULL,
89
+ summary TEXT NOT NULL,
90
+ reasoning TEXT,
91
+ node_ids TEXT,
92
+ doc_paths TEXT,
93
+ pending_tasks TEXT,
94
+ history_ids TEXT,
95
+ session_id TEXT,
96
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
97
+ FOREIGN KEY (workflow_id) REFERENCES workflows (id) ON DELETE CASCADE
98
+ );
99
+
100
+ CREATE TABLE IF NOT EXISTS workflow_artifacts (
101
+ id TEXT PRIMARY KEY,
102
+ workflow_id TEXT NOT NULL,
103
+ step_id TEXT,
104
+ type TEXT NOT NULL,
105
+ source_name TEXT NOT NULL,
106
+ file_path TEXT NOT NULL,
107
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
108
+ FOREIGN KEY (workflow_id) REFERENCES workflows (id) ON DELETE CASCADE
109
+ );
110
+
111
+ -- Index for searching nodes by name and type
112
+ CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes (name);
113
+ -- Matches getNodesByFilePath's own normalization (REPLACE(LOWER(file_path), '\', '/')) exactly —
114
+ -- without this, that call is a full table scan, and get_node_code's file_outline now puts it on
115
+ -- the hot path of the most-called read tool (once per call, to cross-reference outline entries
116
+ -- against indexed nodes). Idempotent: existing brains pick this up on next open, no migration.
117
+ CREATE INDEX IF NOT EXISTS idx_nodes_file_path ON nodes (REPLACE(LOWER(file_path), '\', '/'));
118
+ -- node_connections' PRIMARY KEY is (source_node_id, target_node_id), so its autoindex serves
119
+ -- "what does X use" but NOT the reverse. Every "who uses X" lookup — the inbound half of getGraph's
120
+ -- BFS, getConnections' used_by, getConnectionCounts — was therefore a full table scan, and the BFS
121
+ -- runs one PER NODE VISITED (up to 500 in a single call). The trailing source_node_id makes this
122
+ -- covering, so the inbound query is answered from the index alone and comes back already ordered
123
+ -- (which is what makes getGraph's ORDER BY free rather than a sort on top of a scan).
124
+ -- Idempotent: existing brains pick this up on next open, no migration.
125
+ CREATE INDEX IF NOT EXISTS idx_node_connections_target ON node_connections (target_node_id, source_node_id);
126
+ CREATE INDEX IF NOT EXISTS idx_history_node_id ON history (node_id);
127
+ CREATE INDEX IF NOT EXISTS idx_workflow_steps_workflow_id ON workflow_steps (workflow_id);
128
+ CREATE INDEX IF NOT EXISTS idx_workflow_artifacts_workflow_id ON workflow_artifacts (workflow_id);
77
129
  `;
78
130
  //# sourceMappingURL=schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":";;;AAuDa,QAAA,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyE9B,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":";;;AAqGa,QAAA,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6H9B,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The searchable "surface" of a node, split into fields so each can carry its own relevance
3
+ * weight. `description` is the whole point of this module: without it, natural-language search
4
+ * has almost nothing to match against — an identifier and a file path are a handful of words.
5
+ */
6
+ export type TokenField = 'identifier' | 'path' | 'description' | 'reasoning';
7
+ export interface TokenRow {
8
+ token: string;
9
+ field: TokenField;
10
+ /** Term frequency — how many times this token appears in this field, for this node. */
11
+ tf: number;
12
+ }
13
+ /**
14
+ * Tokenizes one field's text into deduplicated (token, tf) pairs, ready to write into
15
+ * `node_tokens`. `identifier`/`path` fields use the identifier splitter (camelCase/snake/kebab
16
+ * boundaries) since that's what they actually are; `description`/`reasoning` use the plain
17
+ * natural-language tokenizer since they're already prose.
18
+ */
19
+ export declare function tokenizeNodeField(text: string | null | undefined, field: TokenField): TokenRow[];
20
+ export interface FieldWeights {
21
+ identifier: number;
22
+ path: number;
23
+ description: number;
24
+ reasoning: number;
25
+ }
26
+ /**
27
+ * `description` outweighs everything else — it's the field written specifically to be found
28
+ * by natural language, so a hit there is the strongest possible signal that this is the right
29
+ * node. `identifier` is still weighted highly since an exact-ish name match is usually right.
30
+ * `reasoning` is last: it explains WHY something changed, not WHAT it is, so it's a weaker
31
+ * signal for "find the code that does X" than the other three.
32
+ */
33
+ export declare const DEFAULT_FIELD_WEIGHTS: FieldWeights;
34
+ export interface FieldMatch {
35
+ field: TokenField;
36
+ /** This node's term frequency for the matched token, in this field. */
37
+ tf: number;
38
+ /** How many OTHER nodes also have this token in this field — rarer tokens carry more signal. */
39
+ docFreq: number;
40
+ /** Total nodes considered, for the IDF calculation. */
41
+ totalNodes: number;
42
+ }
43
+ /**
44
+ * A saturating-TF, IDF-weighted score for one candidate node across every (field, token) it
45
+ * matched. This is BM25-shaped (the same `tf*(k1+1)/(tf+k1)` saturation and log-IDF term BM25
46
+ * uses) but deliberately simplified: it scores a small, already-narrowed candidate set (nodes
47
+ * that matched at least one query token via a SQL lookup), not a full-corpus scan, so there's
48
+ * no need for BM25's document-length normalization term — every "document" here is one node's
49
+ * handful of fields, not a variable-length passage.
50
+ */
51
+ export declare function scoreCandidate(matches: FieldMatch[], weights?: FieldWeights): number;
52
+ export interface RankedId {
53
+ id: string;
54
+ score: number;
55
+ }
56
+ /**
57
+ * Reciprocal Rank Fusion — merges the BM25 ranking and the vector-cosine ranking into one list,
58
+ * using each id's POSITION within each ranking rather than the raw scores. Necessary because
59
+ * BM25 scores are unbounded and cosine similarity is bounded [-1,1]; there's no principled scale
60
+ * to sum them on directly, but "1st place" means the same thing in both rankings. An id missing
61
+ * from a ranking simply contributes 0 from it — appearing in both rankings compounds naturally.
62
+ * k=60 is the standard RRF constant (Cormack et al.): large enough that rank 1 vs rank 2 isn't a
63
+ * cliff, small enough that being ranked at all still matters more than being buried at rank 500.
64
+ */
65
+ export declare function reciprocalRankFusion(rankings: string[][], k?: number): RankedId[];
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_FIELD_WEIGHTS = void 0;
4
+ exports.tokenizeNodeField = tokenizeNodeField;
5
+ exports.scoreCandidate = scoreCandidate;
6
+ exports.reciprocalRankFusion = reciprocalRankFusion;
7
+ const tokenize_1 = require("../utils/tokenize");
8
+ /**
9
+ * Tokenizes one field's text into deduplicated (token, tf) pairs, ready to write into
10
+ * `node_tokens`. `identifier`/`path` fields use the identifier splitter (camelCase/snake/kebab
11
+ * boundaries) since that's what they actually are; `description`/`reasoning` use the plain
12
+ * natural-language tokenizer since they're already prose.
13
+ */
14
+ function tokenizeNodeField(text, field) {
15
+ if (!text)
16
+ return [];
17
+ const tokens = field === 'identifier' || field === 'path' ? (0, tokenize_1.tokenizeIdentifier)(text) : (0, tokenize_1.tokenizeText)(text);
18
+ const counts = new Map();
19
+ for (const t of tokens)
20
+ counts.set(t, (counts.get(t) || 0) + 1);
21
+ return Array.from(counts.entries()).map(([token, tf]) => ({ token, field, tf }));
22
+ }
23
+ /**
24
+ * `description` outweighs everything else — it's the field written specifically to be found
25
+ * by natural language, so a hit there is the strongest possible signal that this is the right
26
+ * node. `identifier` is still weighted highly since an exact-ish name match is usually right.
27
+ * `reasoning` is last: it explains WHY something changed, not WHAT it is, so it's a weaker
28
+ * signal for "find the code that does X" than the other three.
29
+ */
30
+ exports.DEFAULT_FIELD_WEIGHTS = {
31
+ description: 4,
32
+ identifier: 3,
33
+ path: 2,
34
+ reasoning: 1.5
35
+ };
36
+ /**
37
+ * A saturating-TF, IDF-weighted score for one candidate node across every (field, token) it
38
+ * matched. This is BM25-shaped (the same `tf*(k1+1)/(tf+k1)` saturation and log-IDF term BM25
39
+ * uses) but deliberately simplified: it scores a small, already-narrowed candidate set (nodes
40
+ * that matched at least one query token via a SQL lookup), not a full-corpus scan, so there's
41
+ * no need for BM25's document-length normalization term — every "document" here is one node's
42
+ * handful of fields, not a variable-length passage.
43
+ */
44
+ function scoreCandidate(matches, weights = exports.DEFAULT_FIELD_WEIGHTS) {
45
+ const K1 = 1.2;
46
+ let score = 0;
47
+ for (const m of matches) {
48
+ const idf = Math.log(1 + (m.totalNodes - m.docFreq + 0.5) / (m.docFreq + 0.5));
49
+ const tfNorm = (m.tf * (K1 + 1)) / (m.tf + K1);
50
+ score += weights[m.field] * Math.max(idf, 0.01) * tfNorm;
51
+ }
52
+ return score;
53
+ }
54
+ /**
55
+ * Reciprocal Rank Fusion — merges the BM25 ranking and the vector-cosine ranking into one list,
56
+ * using each id's POSITION within each ranking rather than the raw scores. Necessary because
57
+ * BM25 scores are unbounded and cosine similarity is bounded [-1,1]; there's no principled scale
58
+ * to sum them on directly, but "1st place" means the same thing in both rankings. An id missing
59
+ * from a ranking simply contributes 0 from it — appearing in both rankings compounds naturally.
60
+ * k=60 is the standard RRF constant (Cormack et al.): large enough that rank 1 vs rank 2 isn't a
61
+ * cliff, small enough that being ranked at all still matters more than being buried at rank 500.
62
+ */
63
+ function reciprocalRankFusion(rankings, k = 60) {
64
+ const scores = new Map();
65
+ for (const ranking of rankings) {
66
+ ranking.forEach((id, index) => {
67
+ scores.set(id, (scores.get(id) || 0) + 1 / (k + index + 1));
68
+ });
69
+ }
70
+ return Array.from(scores.entries())
71
+ .map(([id, score]) => ({ id, score }))
72
+ .sort((a, b) => b.score - a.score);
73
+ }
74
+ //# sourceMappingURL=search-index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-index.js","sourceRoot":"","sources":["../../src/db/search-index.ts"],"names":[],"mappings":"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"}
@@ -4,21 +4,99 @@ export interface StagedEntry {
4
4
  node_id: string;
5
5
  file_path: string;
6
6
  code_snapshot: string;
7
- reasoning: string | ReasoningObject;
7
+ /**
8
+ * The entity's text before this edit. Only `edit_node` can supply it (it holds the pre-edit
9
+ * file); `stage_change` takes a snapshot with nothing to diff against and leaves it undefined.
10
+ * Absent means the entry gets no diff and no revert.
11
+ */
12
+ code_before?: string | null;
8
13
  name?: string;
9
14
  type?: string;
10
15
  signature?: string;
16
+ /**
17
+ * A 1-3 sentence natural-language description of what this entity does. `commit_changes`
18
+ * refuses to create a node that has never had one before (see `add_description`) — but an
19
+ * ordinary edit to an already-described node can leave this unset; `upsertNode`'s COALESCE
20
+ * means the existing description survives untouched.
21
+ */
22
+ description?: string;
11
23
  session_id?: string;
12
24
  /** Optional explicit edges to add on top of AST resolution (source defaults to this entry). */
13
25
  connections?: {
14
26
  source_node_id?: string;
15
27
  target_node_id: string;
16
28
  }[];
29
+ /**
30
+ * When this was staged — set automatically by `stageEntry`, not by the caller. A file can pick
31
+ * up both a traced node-level entry and an untraced whole-file entry (StagedFileEdit) across
32
+ * separate edit_node calls before one commit; the activity message's edits must interleave the
33
+ * two in true chronological order for that file's whole-file reconstruction to undo cleanly, so
34
+ * this is the sort key `commit_changes` uses.
35
+ */
36
+ staged_at?: string;
37
+ }
38
+ /**
39
+ * A whole-file edit that didn't trace to any graph node — a non-code file (CSS, XML, JSON, ...)
40
+ * or a code edit landing outside any function/class (an import line, a top-level constant). The
41
+ * graph has nothing to hold for these, but the local activity log still can: `commit_changes`
42
+ * folds these into the same message as any traced node edits, so every file `edit_node` touches
43
+ * shows up and is individually revertable there, not just the ones that became graph nodes.
44
+ */
45
+ export interface StagedFileEdit {
46
+ file_path: string;
47
+ before: string;
48
+ after: string;
49
+ session_id?: string;
50
+ /** When this was staged — set automatically by `stageFileEdit`. See StagedEntry.staged_at. */
51
+ staged_at?: string;
17
52
  }
18
53
  export declare function readStaged(devmindPath: string): StagedEntry[];
19
- /** Appends one entry to the buffer and returns the new pending count. */
54
+ export declare function readStagedFileEdits(devmindPath: string): StagedFileEdit[];
55
+ export interface SessionStagingPartition {
56
+ entries: StagedEntry[];
57
+ fileEdits: StagedFileEdit[];
58
+ /** Count of staged entries/file edits left behind because another session owns them. */
59
+ otherSessionsPending: number;
60
+ }
61
+ /**
62
+ * Splits the shared buffer into "what this session may commit" vs. "left behind" — the fix for
63
+ * the multi-session bug where `commit_changes` used to flush the ENTIRE buffer regardless of who
64
+ * staged what, silently pulling unrelated in-flight work (sometimes from other repos entirely)
65
+ * into a commit and stamping it with the committing session's reasoning. A plain commit now only
66
+ * ever touches its own session's staged work; other sessions' entries stay staged untouched.
67
+ */
68
+ export declare function partitionStagedForSession(devmindPath: string, sessionId: string): SessionStagingPartition;
69
+ /**
70
+ * Removes only this session's staged entries/file edits, leaving any other session's pending
71
+ * work in place — the scoped counterpart to `clearStaged`, which used to wipe the whole buffer
72
+ * (including work another session had not committed yet) after every commit.
73
+ */
74
+ export declare function clearStagedForSession(devmindPath: string, sessionId: string): void;
75
+ /** Appends one entry to the buffer (stamping `staged_at`) and returns the new pending count. */
20
76
  export declare function stageEntry(devmindPath: string, entry: StagedEntry): number;
77
+ /** Appends one whole-file edit to the buffer (stamping `staged_at`) and returns the new pending count. */
78
+ export declare function stageFileEdit(devmindPath: string, edit: StagedFileEdit): number;
79
+ /**
80
+ * Replaces the buffer's entries wholesale, keeping `file_edits` untouched — used by
81
+ * `add_description` to persist a description written onto an already-staged entry (e.g. after
82
+ * a `commit_changes` rejection), since `readStaged` returns a fresh array each call and
83
+ * mutating it in memory has no effect until it's written back.
84
+ */
85
+ export declare function overwriteStaged(devmindPath: string, entries: StagedEntry[]): void;
21
86
  export declare function clearStaged(devmindPath: string): void;
87
+ /**
88
+ * Drops the newest staged entry for one node and returns it, or null if the node has none.
89
+ *
90
+ * Newest rather than all: the buffer can hold several edits to the same entity, and reverting is
91
+ * an undo of the last one. An entry removed here was never committed, so nothing else has to be
92
+ * unwound — there is no history row yet for it to have written.
93
+ */
94
+ export declare function removeLastStagedEntry(devmindPath: string, nodeId: string): StagedEntry | null;
95
+ /** Resolves an entry's raw node_id to the canonical `{repo}/relpath#symbol` form. Exported so
96
+ * the commit_changes gate can check "does this node already exist" using the exact same
97
+ * canonicalization commitStagedChanges itself uses — a mismatch there would let a node the
98
+ * gate thinks is new slip through uncommitted, or block one that's actually already described. */
99
+ export declare function resolveEntryId(db: DevMindDatabase, entry: StagedEntry): string;
22
100
  export interface CommitSummary {
23
101
  nodes: number;
24
102
  history_entries: number;
@@ -35,9 +113,16 @@ export interface CommitSummary {
35
113
  * auto-creating any missing target nodes. Only the staged nodes' own outbound edges
36
114
  * are recomputed; every other node's edges are left intact.
37
115
  *
116
+ * `reasoning` is ONE object covering the whole batch — every entry in one commit serves one
117
+ * request, so it is asked for once here rather than repeated on every staged entry. Kept as an
118
+ * object (not pre-formatted) all the way to `updateHistory`, since the configured-developer
119
+ * override only fires on the object form.
120
+ *
38
121
  * Idempotent: re-running the same batch yields the same graph (upsert + INSERT OR IGNORE +
39
122
  * clear-then-resolve). Callers should clear the buffer only after this returns successfully.
40
123
  */
41
- export declare function commitStagedChanges(db: DevMindDatabase, devmindPath: string, entries: StagedEntry[]): CommitSummary;
42
- /** Builds a short workflow-step summary from a batch of staged entries' `what_changed` reasoning. */
43
- export declare function summarizeEntriesForWorkflow(entries: StagedEntry[]): string;
124
+ export declare function commitStagedChanges(db: DevMindDatabase, devmindPath: string, entries: StagedEntry[], reasoning: string | ReasoningObject): Promise<CommitSummary>;
125
+ /** Builds a short workflow-step/activity summary for a commit. The commit's own `reasoning`
126
+ * (one object covering the whole batch) is authoritative when it has a `what_changed`; falls
127
+ * back to naming the staged entities for a bare-string reasoning. */
128
+ export declare function summarizeEntriesForWorkflow(entries: StagedEntry[], reasoning: string | ReasoningObject): string;