@monoes/monograph 1.4.0 → 1.5.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.
- package/dist/src/analysis/benchmark.js.map +1 -1
- package/dist/src/analysis/changed-files.d.ts.map +1 -1
- package/dist/src/analysis/changed-files.js +11 -2
- package/dist/src/analysis/changed-files.js.map +1 -1
- package/dist/src/analysis/changed-workspaces.d.ts.map +1 -1
- package/dist/src/analysis/changed-workspaces.js +11 -3
- package/dist/src/analysis/changed-workspaces.js.map +1 -1
- package/dist/src/analysis/churn.d.ts +1 -1
- package/dist/src/analysis/churn.d.ts.map +1 -1
- package/dist/src/analysis/churn.js +56 -10
- package/dist/src/analysis/churn.js.map +1 -1
- package/dist/src/analysis/feature-flags.d.ts.map +1 -1
- package/dist/src/analysis/feature-flags.js +11 -5
- package/dist/src/analysis/feature-flags.js.map +1 -1
- package/dist/src/analysis/progress.d.ts.map +1 -1
- package/dist/src/analysis/project-state.d.ts.map +1 -1
- package/dist/src/analysis/shape-extractor.js.map +1 -1
- package/dist/src/analysis/suppression-context.d.ts.map +1 -1
- package/dist/src/analysis/surprise-scorer.js.map +1 -1
- package/dist/src/analysis/targets.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.js.map +1 -1
- package/dist/src/analyze/unused-deps.js.map +1 -1
- package/dist/src/analyze/unused-exports.js.map +1 -1
- package/dist/src/analyze/unused-files.js.map +1 -1
- package/dist/src/cache/augment-cache.js.map +1 -1
- package/dist/src/cache/extraction-cache.d.ts +21 -0
- package/dist/src/cache/extraction-cache.d.ts.map +1 -1
- package/dist/src/cache/extraction-cache.js +62 -6
- package/dist/src/cache/extraction-cache.js.map +1 -1
- package/dist/src/check/index.js.map +1 -1
- package/dist/src/cli/check-update.js.map +1 -1
- package/dist/src/cli/doctor.d.ts.map +1 -1
- package/dist/src/cli/doctor.js +6 -10
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/config/boundary-config.js.map +1 -1
- package/dist/src/config/config-parsing.d.ts +1 -1
- package/dist/src/config/config-parsing.d.ts.map +1 -1
- package/dist/src/config/migration-types.d.ts +2 -2
- package/dist/src/config/migration-types.d.ts.map +1 -1
- package/dist/src/config/migration-types.js.map +1 -1
- package/dist/src/config/used-class-members.js.map +1 -1
- package/dist/src/config/workspace.js.map +1 -1
- package/dist/src/coverage/upload-source-maps.js.map +1 -1
- package/dist/src/duplicates/detect/filtering.d.ts.map +1 -1
- package/dist/src/duplicates/shingle-filter.js.map +1 -1
- package/dist/src/error/fallow-error.d.ts.map +1 -1
- package/dist/src/error/fallow-error.js.map +1 -1
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js.map +1 -1
- package/dist/src/export/analysis-json.js.map +1 -1
- package/dist/src/export/badge.js.map +1 -1
- package/dist/src/export/dot.js.map +1 -1
- package/dist/src/export/sarif.js.map +1 -1
- package/dist/src/fix/orchestrator.js.map +1 -1
- package/dist/src/graph/cycle-detection.js.map +1 -1
- package/dist/src/graph/dead-code.d.ts +7 -2
- package/dist/src/graph/dead-code.d.ts.map +1 -1
- package/dist/src/graph/dead-code.js +19 -16
- package/dist/src/graph/dead-code.js.map +1 -1
- package/dist/src/graph/dfs.js.map +1 -1
- package/dist/src/graph/edge-filter.js.map +1 -1
- package/dist/src/graph/hotspots.js.map +1 -1
- package/dist/src/graph/import-chain.js.map +1 -1
- package/dist/src/graph/merge.js.map +1 -1
- package/dist/src/graph/node-search.js.map +1 -1
- package/dist/src/graph/node-types.d.ts.map +1 -1
- package/dist/src/graph/pagerank.d.ts.map +1 -1
- package/dist/src/graph/pagerank.js +10 -1
- package/dist/src/graph/pagerank.js.map +1 -1
- package/dist/src/graph/regex-search.js.map +1 -1
- package/dist/src/graph/regression.d.ts.map +1 -1
- package/dist/src/graph/resolve/discover-types.js.map +1 -1
- package/dist/src/graph/resolve/types.d.ts +3 -3
- package/dist/src/graph/resolve/types.d.ts.map +1 -1
- package/dist/src/groups/group-search.d.ts.map +1 -1
- package/dist/src/groups/group-search.js +9 -18
- package/dist/src/groups/group-search.js.map +1 -1
- package/dist/src/health/complexity-findings.js.map +1 -1
- package/dist/src/health/grouping.js.map +1 -1
- package/dist/src/health/health-report-types.js.map +1 -1
- package/dist/src/health/health-report.js.map +1 -1
- package/dist/src/health/targets.js.map +1 -1
- package/dist/src/ingest/transcribe.js.map +1 -1
- package/dist/src/ingest/url-ingest.js.map +1 -1
- package/dist/src/license/manager.d.ts.map +1 -1
- package/dist/src/license/manager.js.map +1 -1
- package/dist/src/lsp/diagnostics-push.js.map +1 -1
- package/dist/src/mcp-tools/impact.d.ts.map +1 -1
- package/dist/src/mcp-tools/impact.js +7 -1
- package/dist/src/mcp-tools/impact.js.map +1 -1
- package/dist/src/mcp-tools/list-repos.d.ts +1 -1
- package/dist/src/mcp-tools/list-repos.d.ts.map +1 -1
- package/dist/src/mcp-tools/query.d.ts +1 -1
- package/dist/src/mcp-tools/query.d.ts.map +1 -1
- package/dist/src/parsers/c.js.map +1 -1
- package/dist/src/parsers/cpp.js.map +1 -1
- package/dist/src/parsers/csharp.js.map +1 -1
- package/dist/src/parsers/dart.js.map +1 -1
- package/dist/src/parsers/go.js.map +1 -1
- package/dist/src/parsers/java.js.map +1 -1
- package/dist/src/parsers/kotlin.js.map +1 -1
- package/dist/src/parsers/loader.js.map +1 -1
- package/dist/src/parsers/php.js.map +1 -1
- package/dist/src/parsers/python.js.map +1 -1
- package/dist/src/parsers/ruby.js.map +1 -1
- package/dist/src/parsers/rust.js.map +1 -1
- package/dist/src/parsers/swift.js.map +1 -1
- package/dist/src/parsers/typescript.js.map +1 -1
- package/dist/src/parsers/vue.js.map +1 -1
- package/dist/src/pipeline/effort.js.map +1 -1
- package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/src/pipeline/orchestrator.js +48 -24
- package/dist/src/pipeline/orchestrator.js.map +1 -1
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.d.ts +11 -0
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.js +96 -0
- package/dist/src/pipeline/phases/bridge-adapters/electron-ipc.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.d.ts +3 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.js +13 -0
- package/dist/src/pipeline/phases/bridge-adapters/registry.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.d.ts +10 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.js +104 -0
- package/dist/src/pipeline/phases/bridge-adapters/tauri.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.d.ts +30 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.js +2 -0
- package/dist/src/pipeline/phases/bridge-adapters/types.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.d.ts +10 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.js +27 -0
- package/dist/src/pipeline/phases/bridge-adapters/wails.js.map +1 -0
- package/dist/src/pipeline/phases/bridge-resolver.d.ts +23 -0
- package/dist/src/pipeline/phases/bridge-resolver.d.ts.map +1 -0
- package/dist/src/pipeline/phases/bridge-resolver.js +70 -0
- package/dist/src/pipeline/phases/bridge-resolver.js.map +1 -0
- package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
- package/dist/src/pipeline/phases/cross-file.js +70 -27
- package/dist/src/pipeline/phases/cross-file.js.map +1 -1
- package/dist/src/pipeline/phases/leiden.js.map +1 -1
- package/dist/src/pipeline/phases/parse.d.ts.map +1 -1
- package/dist/src/pipeline/phases/parse.js +58 -17
- package/dist/src/pipeline/phases/parse.js.map +1 -1
- package/dist/src/pipeline/phases/scan.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scan.js +12 -5
- package/dist/src/pipeline/phases/scan.js.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.d.ts +13 -0
- package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.js +18 -4
- package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
- package/dist/src/pipeline/progress.d.ts.map +1 -1
- package/dist/src/pipeline/runner.d.ts.map +1 -1
- package/dist/src/pipeline/runner.js.map +1 -1
- package/dist/src/pipeline/types.d.ts +2 -0
- package/dist/src/pipeline/types.d.ts.map +1 -1
- package/dist/src/pipeline/types.js +1 -0
- package/dist/src/pipeline/types.js.map +1 -1
- package/dist/src/regression/baseline.js.map +1 -1
- package/dist/src/regression/outcome.js.map +1 -1
- package/dist/src/report/badge-svg.js.map +1 -1
- package/dist/src/report/duplication-grouping.js.map +1 -1
- package/dist/src/report/human-check.js.map +1 -1
- package/dist/src/report/human-dupes.js.map +1 -1
- package/dist/src/report/human-health.js.map +1 -1
- package/dist/src/report/json-builders.js.map +1 -1
- package/dist/src/report/number-format.js.map +1 -1
- package/dist/src/reporting/graph-report.js.map +1 -1
- package/dist/src/scope-resolution/evidence-weights.d.ts.map +1 -1
- package/dist/src/search/hybrid-query.js.map +1 -1
- package/dist/src/security/safe-fetch.js.map +1 -1
- package/dist/src/storage/db.d.ts +13 -1
- package/dist/src/storage/db.d.ts.map +1 -1
- package/dist/src/storage/db.js +17 -1
- package/dist/src/storage/db.js.map +1 -1
- package/dist/src/storage/edge-store.d.ts +6 -0
- package/dist/src/storage/edge-store.d.ts.map +1 -1
- package/dist/src/storage/edge-store.js +8 -1
- package/dist/src/storage/edge-store.js.map +1 -1
- package/dist/src/storage/fts-store.d.ts +7 -0
- package/dist/src/storage/fts-store.d.ts.map +1 -1
- package/dist/src/storage/fts-store.js +153 -10
- package/dist/src/storage/fts-store.js.map +1 -1
- package/dist/src/storage/node-store.js.map +1 -1
- package/dist/src/storage/unlinked-refs.js.map +1 -1
- package/dist/src/types.d.ts +2 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/watch/watcher.d.ts.map +1 -1
- package/dist/src/watch/watcher.js +12 -2
- package/dist/src/watch/watcher.js.map +1 -1
- package/dist/src/web/analyze-api.js.map +1 -1
- package/dist/src/web/api.js.map +1 -1
- package/dist/src/wiki/providers.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -2
- package/src/__tests__/graph/dead-code.test.ts +27 -7
- package/src/__tests__/pipeline/phases/bridge-adapters/electron-ipc.test.ts +87 -0
- package/src/__tests__/pipeline/phases/bridge-adapters/tauri.test.ts +116 -0
- package/src/__tests__/pipeline/phases/bridge-adapters/wails.test.ts +81 -0
- package/src/__tests__/pipeline/phases/bridge-resolver.test.ts +110 -0
- package/src/analysis/changed-files.ts +14 -2
- package/src/analysis/changed-workspaces.ts +13 -4
- package/src/analysis/churn.ts +57 -10
- package/src/analysis/feature-flags.ts +10 -5
- package/src/cache/extraction-cache.ts +59 -6
- package/src/cli/doctor.ts +6 -10
- package/src/graph/dead-code.ts +19 -16
- package/src/graph/pagerank.ts +10 -1
- package/src/groups/group-search.ts +9 -27
- package/src/mcp-tools/impact.ts +7 -1
- package/src/pipeline/orchestrator.ts +50 -26
- package/src/pipeline/phases/bridge-adapters/electron-ipc.ts +91 -0
- package/src/pipeline/phases/bridge-adapters/registry.ts +14 -0
- package/src/pipeline/phases/bridge-adapters/tauri.ts +103 -0
- package/src/pipeline/phases/bridge-adapters/types.ts +31 -0
- package/src/pipeline/phases/bridge-adapters/wails.ts +33 -0
- package/src/pipeline/phases/bridge-resolver.ts +77 -0
- package/src/pipeline/phases/cross-file.ts +75 -27
- package/src/pipeline/phases/parse.ts +60 -17
- package/src/pipeline/phases/scan.ts +11 -5
- package/src/pipeline/phases/scope-resolution.ts +19 -4
- package/src/pipeline/types.ts +3 -0
- package/src/storage/db.ts +27 -1
- package/src/storage/edge-store.ts +8 -1
- package/src/storage/fts-store.ts +158 -10
- package/src/watch/watcher.ts +11 -2
- package/tsconfig.json +2 -1
|
@@ -73,10 +73,17 @@ export function getEdgesForTarget(db: Database.Database, targetId: string): Mono
|
|
|
73
73
|
return rows.map(rowToEdge);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Delete every edge that touches a node belonging to `filePath`, on either end.
|
|
78
|
+
* Must be called BEFORE the corresponding nodes are deleted (the subselects need
|
|
79
|
+
* the file's nodes to still exist to identify which edges to remove) — e.g.
|
|
80
|
+
* `deleteEdgesForFile` then `deleteNodesForFile`, never the reverse.
|
|
81
|
+
*/
|
|
76
82
|
export function deleteEdgesForFile(db: Database.Database, filePath: string): void {
|
|
77
83
|
db.prepare(`
|
|
78
84
|
DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file_path = ?)
|
|
79
|
-
|
|
85
|
+
OR target_id IN (SELECT id FROM nodes WHERE file_path = ?)
|
|
86
|
+
`).run(filePath, filePath);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
export function countEdges(db: Database.Database): number {
|
package/src/storage/fts-store.ts
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
import type Database from 'better-sqlite3';
|
|
2
2
|
|
|
3
|
+
// ── Intent-to-identifier extraction ──────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
const STOPWORDS = new Set([
|
|
6
|
+
'a','an','the','is','are','was','were','be','been','being','have','has','had',
|
|
7
|
+
'do','does','did','will','would','shall','should','may','might','must','can',
|
|
8
|
+
'could','am','it','its','i','me','my','we','our','you','your','he','she',
|
|
9
|
+
'they','them','their','this','that','these','those','of','in','to','for',
|
|
10
|
+
'with','on','at','from','by','about','as','into','through','during','before',
|
|
11
|
+
'after','above','below','between','out','off','over','under','again','then',
|
|
12
|
+
'once','here','there','when','where','why','how','all','both','each','every',
|
|
13
|
+
'few','more','most','other','some','such','no','nor','not','only','own',
|
|
14
|
+
'same','so','than','too','very','just','because','but','and','or','if',
|
|
15
|
+
'while','what','which','who','whom','up','down','get','set','add','use',
|
|
16
|
+
'find','show','make','let','want','need','like','look','also','new','old',
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract code-relevant search terms from a natural-language query.
|
|
21
|
+
* Strips stopwords, splits camelCase/PascalCase/snake_case, and strips
|
|
22
|
+
* file extensions (so "CLAUDE.md" also tries "CLAUDE").
|
|
23
|
+
* Returns an empty array when the query is already a bare identifier.
|
|
24
|
+
*/
|
|
25
|
+
export function extractSearchTerms(query: string): string[] {
|
|
26
|
+
const words = query.split(/[\s,;:!?()\[\]{}'"]+/).filter(Boolean);
|
|
27
|
+
// If query is a single token or all tokens are code-like, it's already an identifier query
|
|
28
|
+
if (words.length <= 1) return [];
|
|
29
|
+
const hasStopword = words.some(w => STOPWORDS.has(w.toLowerCase()));
|
|
30
|
+
if (!hasStopword && words.length <= 3) return [];
|
|
31
|
+
|
|
32
|
+
const terms = new Set<string>();
|
|
33
|
+
for (const word of words) {
|
|
34
|
+
const lower = word.toLowerCase();
|
|
35
|
+
if (STOPWORDS.has(lower)) continue;
|
|
36
|
+
if (word.length < 3) continue;
|
|
37
|
+
|
|
38
|
+
// Strip file extensions: "CLAUDE.md" → also add "CLAUDE"
|
|
39
|
+
const dotIdx = word.lastIndexOf('.');
|
|
40
|
+
if (dotIdx > 0 && dotIdx < word.length - 1) {
|
|
41
|
+
terms.add(word.slice(0, dotIdx));
|
|
42
|
+
terms.add(word);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Split camelCase/PascalCase: "ExtensionBridge" → "Extension", "Bridge"
|
|
46
|
+
const camelParts = word.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
47
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
48
|
+
.split(/[\s_-]+/)
|
|
49
|
+
.filter(p => p.length >= 3 && !STOPWORDS.has(p.toLowerCase()));
|
|
50
|
+
if (camelParts.length > 1) {
|
|
51
|
+
for (const part of camelParts) terms.add(part);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Keep the original word
|
|
55
|
+
terms.add(word);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Array.from(terms);
|
|
59
|
+
}
|
|
60
|
+
|
|
3
61
|
export interface FtsResult {
|
|
4
62
|
id: string;
|
|
5
63
|
name: string;
|
|
@@ -13,6 +71,20 @@ export interface FtsResult {
|
|
|
13
71
|
endLine: number | null;
|
|
14
72
|
}
|
|
15
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Quote a single FTS5 search term as a string literal when it contains characters
|
|
76
|
+
* that would otherwise be parsed as FTS5 syntax (quotes, parens, boolean keywords
|
|
77
|
+
* like AND/OR/NOT, colons, hyphens, etc). Wrapping in double-quotes forces FTS5 to
|
|
78
|
+
* treat the term as a literal string; any literal `"` inside the term is escaped by
|
|
79
|
+
* doubling it, per FTS5's own string-literal quoting rules.
|
|
80
|
+
*/
|
|
81
|
+
function quoteFtsTerm(term: string): string {
|
|
82
|
+
// Bare alphanumeric/underscore terms need no quoting and are the common case.
|
|
83
|
+
if (/^[A-Za-z0-9_]+$/.test(term)) return term;
|
|
84
|
+
const escaped = term.replace(/"/g, '""');
|
|
85
|
+
return `"${escaped}"`;
|
|
86
|
+
}
|
|
87
|
+
|
|
16
88
|
export function ftsSearch(
|
|
17
89
|
db: Database.Database,
|
|
18
90
|
query: string,
|
|
@@ -24,8 +96,13 @@ export function ftsSearch(
|
|
|
24
96
|
const safeQuery = query.replace(/[*]/g, ' ').trim();
|
|
25
97
|
if (!safeQuery) return [];
|
|
26
98
|
|
|
27
|
-
// Trigram handles substring matching natively — no need to append * to each term
|
|
28
|
-
|
|
99
|
+
// Trigram handles substring matching natively — no need to append * to each term.
|
|
100
|
+
// Quote each term individually so characters like `"`, `(`, `)`, or bare boolean
|
|
101
|
+
// keywords (AND/OR/NOT) don't get parsed as FTS5 query syntax.
|
|
102
|
+
const ftsQuery = safeQuery
|
|
103
|
+
.split(/\s+/)
|
|
104
|
+
.map(quoteFtsTerm)
|
|
105
|
+
.join(' ');
|
|
29
106
|
|
|
30
107
|
let matchSql = `
|
|
31
108
|
SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
|
|
@@ -52,18 +129,89 @@ export function ftsSearch(
|
|
|
52
129
|
// FTS MATCH can throw on malformed queries; fall through to LIKE path
|
|
53
130
|
}
|
|
54
131
|
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
if (
|
|
58
|
-
const
|
|
132
|
+
// ── Intent-extraction fallback: when the raw query looks like natural language
|
|
133
|
+
// and FTS returned nothing, extract code-relevant tokens and retry FTS. ──────
|
|
134
|
+
if (matchRows.length === 0) {
|
|
135
|
+
const extracted = extractSearchTerms(safeQuery);
|
|
136
|
+
if (extracted.length > 0) {
|
|
137
|
+
// Try each extracted term individually via FTS, then merge by best rank
|
|
138
|
+
const termFtsQuery = extracted.map(quoteFtsTerm).join(' OR ');
|
|
139
|
+
let termSql = `
|
|
140
|
+
SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
|
|
141
|
+
n.start_line, n.end_line, nodes_fts.rank
|
|
142
|
+
FROM nodes_fts
|
|
143
|
+
JOIN nodes n ON n.rowid = nodes_fts.rowid
|
|
144
|
+
WHERE nodes_fts MATCH ?
|
|
145
|
+
`;
|
|
146
|
+
const termParams: unknown[] = [termFtsQuery];
|
|
147
|
+
if (label) {
|
|
148
|
+
termSql += ' AND n.label = ?';
|
|
149
|
+
termParams.push(label);
|
|
150
|
+
}
|
|
151
|
+
termSql += ' ORDER BY nodes_fts.rank LIMIT ?';
|
|
152
|
+
termParams.push(limit * 2);
|
|
153
|
+
try {
|
|
154
|
+
matchRows = db.prepare(termSql).all(...(termParams as [string, ...unknown[]])) as Record<string, unknown>[];
|
|
155
|
+
} catch { /* FTS MATCH can throw on malformed queries */ }
|
|
156
|
+
|
|
157
|
+
// Also try individual LIKE for each extracted term to catch camelCase substrings
|
|
158
|
+
if (matchRows.length < limit) {
|
|
159
|
+
const seenIds = new Set(matchRows.map((r) => r.id as string));
|
|
160
|
+
for (const term of extracted) {
|
|
161
|
+
if (matchRows.length >= limit) break;
|
|
162
|
+
const escapedTerm = term.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
163
|
+
const pat = `%${escapedTerm}%`;
|
|
164
|
+
let termLikeSql = `
|
|
165
|
+
SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
|
|
166
|
+
n.start_line, n.end_line, 0 AS rank
|
|
167
|
+
FROM nodes n
|
|
168
|
+
WHERE (n.name LIKE ? ESCAPE '\\' OR n.norm_label LIKE ? ESCAPE '\\')
|
|
169
|
+
`;
|
|
170
|
+
const termLikeParams: unknown[] = [pat, pat];
|
|
171
|
+
if (label) {
|
|
172
|
+
termLikeSql += ' AND n.label = ?';
|
|
173
|
+
termLikeParams.push(label);
|
|
174
|
+
}
|
|
175
|
+
termLikeSql += ' LIMIT ?';
|
|
176
|
+
termLikeParams.push(limit);
|
|
177
|
+
const likeRows = db.prepare(termLikeSql).all(...(termLikeParams as [string, ...unknown[]])) as Record<string, unknown>[];
|
|
178
|
+
for (const r of likeRows) {
|
|
179
|
+
if (!seenIds.has(r.id as string)) {
|
|
180
|
+
matchRows.push(r);
|
|
181
|
+
seenIds.add(r.id as string);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Run the LIKE fallback whenever MATCH threw (malformed/boolean-keyword query) or
|
|
190
|
+
// returned zero rows — not just for short (≤2 char) queries. Short queries need it
|
|
191
|
+
// because trigram requires ≥3 characters to fire; longer queries need it whenever
|
|
192
|
+
// MATCH couldn't handle the syntax, so a query-syntax failure doesn't silently read
|
|
193
|
+
// as "no results".
|
|
194
|
+
if (safeQuery.length <= 2 || matchRows.length === 0) {
|
|
195
|
+
const escapedQuery = safeQuery.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
59
196
|
const likePattern = `%${escapedQuery}%`;
|
|
197
|
+
|
|
198
|
+
// Also try without file extension: "CLAUDE.md" → "CLAUDE"
|
|
199
|
+
const dotIdx = safeQuery.lastIndexOf('.');
|
|
200
|
+
const strippedLikePattern = (dotIdx > 0 && dotIdx < safeQuery.length - 1)
|
|
201
|
+
? `%${safeQuery.slice(0, dotIdx).replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_')}%`
|
|
202
|
+
: null;
|
|
203
|
+
|
|
60
204
|
let likeSql = `
|
|
61
205
|
SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
|
|
62
206
|
n.start_line, n.end_line, 0 AS rank
|
|
63
207
|
FROM nodes n
|
|
64
|
-
WHERE (n.name LIKE ? OR n.norm_label LIKE ? OR n.file_path LIKE ?
|
|
65
|
-
`;
|
|
208
|
+
WHERE (n.name LIKE ? ESCAPE '\\' OR n.norm_label LIKE ? ESCAPE '\\' OR n.file_path LIKE ? ESCAPE '\\'`;
|
|
66
209
|
const likeParams: unknown[] = [likePattern, likePattern, likePattern];
|
|
210
|
+
if (strippedLikePattern) {
|
|
211
|
+
likeSql += ` OR n.name LIKE ? ESCAPE '\\'`;
|
|
212
|
+
likeParams.push(strippedLikePattern);
|
|
213
|
+
}
|
|
214
|
+
likeSql += ')';
|
|
67
215
|
if (label) {
|
|
68
216
|
likeSql += ' AND n.label = ?';
|
|
69
217
|
likeParams.push(label);
|
|
@@ -186,13 +334,13 @@ export function hybridSearch(
|
|
|
186
334
|
// ── Strategy 2: LIKE fallback ──────────────────────────────────────────────
|
|
187
335
|
// Always run for short queries (≤3 chars) or when FTS returned nothing.
|
|
188
336
|
if (safeQuery.length <= 3 || ftsRows.length === 0) {
|
|
189
|
-
const escapedSafeQuery = safeQuery.replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
337
|
+
const escapedSafeQuery = safeQuery.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
|
190
338
|
const likePattern = `%${escapedSafeQuery}%`;
|
|
191
339
|
let likeSql = `
|
|
192
340
|
SELECT n.id, n.name, n.norm_label, n.file_path, n.label,
|
|
193
341
|
n.start_line, n.end_line
|
|
194
342
|
FROM nodes n
|
|
195
|
-
WHERE (n.name LIKE ? OR n.norm_label LIKE ?)
|
|
343
|
+
WHERE (n.name LIKE ? ESCAPE '\\' OR n.norm_label LIKE ? ESCAPE '\\')
|
|
196
344
|
`;
|
|
197
345
|
const likeParams: unknown[] = [likePattern, likePattern];
|
|
198
346
|
if (label) {
|
package/src/watch/watcher.ts
CHANGED
|
@@ -34,8 +34,17 @@ export async function watchAsync(
|
|
|
34
34
|
do {
|
|
35
35
|
rerun = false;
|
|
36
36
|
opts.onProgress?.({ phase: 'watch', message: `Changed: ${files.slice(0, 3).join(', ')}` });
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
try {
|
|
38
|
+
await buildAsync(repoPath, { onProgress: opts.onProgress, force: opts.force, codeOnly: opts.codeOnly, llmMaxSections: opts.llmMaxSections ?? 0 });
|
|
39
|
+
opts.onProgress?.({ phase: 'watch', message: 'Graph rebuilt.' });
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// A rebuild failure (locked DB past busy_timeout, disk full, transient
|
|
42
|
+
// I/O error) must not crash the watch process — log it, notify listeners,
|
|
43
|
+
// and keep watching for the next change instead of letting an unhandled
|
|
44
|
+
// rejection escape this listener.
|
|
45
|
+
watcher.emit('monograph:error', err);
|
|
46
|
+
opts.onProgress?.({ phase: 'watch', message: `Rebuild failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
47
|
+
}
|
|
39
48
|
} while (rerun);
|
|
40
49
|
} finally {
|
|
41
50
|
building = false;
|
package/tsconfig.json
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"outDir": "dist",
|
|
5
5
|
"rootDir": ".",
|
|
6
|
-
"composite": true
|
|
6
|
+
"composite": true,
|
|
7
|
+
"types": ["node", "better-sqlite3", "express", "micromatch"]
|
|
7
8
|
},
|
|
8
9
|
"include": ["src/**/*.ts"],
|
|
9
10
|
"exclude": ["node_modules", "dist", "__tests__", "src/**/__tests__/**", "src/**/*.test.ts", "src/**/*.spec.ts"]
|