@o-lang/semantic-doc-search 1.1.0 → 1.1.2
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/package.json +1 -1
- package/src/resolver.js +27 -68
package/package.json
CHANGED
package/src/resolver.js
CHANGED
|
@@ -36,125 +36,84 @@ function hashText(str) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// ─────────────────────────────────────────────
|
|
39
|
-
// 🔥
|
|
40
|
-
// Format: Action
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
if (!action || typeof action !== "string") return null;
|
|
44
|
-
|
|
45
|
-
// Remove "Action doc-search " prefix if present
|
|
46
|
-
const cleaned = action.replace(/^Action\s+doc-search\s+/, "").trim();
|
|
47
|
-
|
|
48
|
-
// Extract quoted strings: "vector.search" "query" "./docs"
|
|
49
|
-
const matches = [...cleaned.matchAll(/"([^"]*)"/g)].map(m => m[1]);
|
|
50
|
-
|
|
51
|
-
if (matches.length < 1) return null;
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
actionType: matches[0], // "vector.search" or "vector.insert"
|
|
55
|
-
args: matches.slice(1), // remaining args
|
|
56
|
-
raw: action
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ─────────────────────────────────────────────
|
|
61
|
-
// 🔥 MAIN RESOLVER
|
|
39
|
+
// 🔥 MAIN RESOLVER - Simplified: action type = resolver name
|
|
40
|
+
// Format: Action vector.search "arg1" "arg2" ...
|
|
41
|
+
// - 1 arg = ingest: "{doc_root}"
|
|
42
|
+
// - 2+ args = search: "{query}" "{doc_root}"
|
|
62
43
|
// ─────────────────────────────────────────────
|
|
63
44
|
async function resolver(action, context = {}) {
|
|
64
45
|
if (typeof action !== "string") return;
|
|
65
46
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
if (!parsed) {
|
|
69
|
-
console.error("❌ Invalid doc-search action format:", action);
|
|
70
|
-
throw new Error("Invalid doc-search action format");
|
|
71
|
-
}
|
|
47
|
+
// Extract ALL quoted args from action string
|
|
48
|
+
const args = [...action.matchAll(/"([^"]*)"/g)].map(m => m[1]);
|
|
72
49
|
|
|
73
|
-
const { actionType, args } = parsed;
|
|
74
|
-
|
|
75
50
|
const vectorStore = VectorRouter.create(context);
|
|
76
51
|
const getEmbedFn = await embedder({ dimension: 384 });
|
|
77
52
|
|
|
78
|
-
|
|
79
|
-
const useCache =
|
|
80
|
-
const cache =
|
|
53
|
+
// ✅ Always use cache for local persistence — no Postgres/Redis required
|
|
54
|
+
const useCache = true;
|
|
55
|
+
const cache = loadCache();
|
|
81
56
|
|
|
82
57
|
// =====================================================
|
|
83
|
-
// ✅ 1
|
|
58
|
+
// ✅ INGEST: 1 arg = doc_root
|
|
84
59
|
// =====================================================
|
|
85
|
-
if (
|
|
60
|
+
if (args.length === 1) {
|
|
86
61
|
let inserted = 0;
|
|
87
|
-
const ingestRoot = args[0] || doc_root;
|
|
62
|
+
const ingestRoot = args[0] || context.doc_root || "./docs";
|
|
88
63
|
|
|
89
64
|
if (fs.existsSync(ingestRoot)) {
|
|
90
65
|
const files = fs.readdirSync(ingestRoot);
|
|
91
|
-
|
|
92
66
|
for (const file of files) {
|
|
93
67
|
const fullPath = path.join(ingestRoot, file);
|
|
94
68
|
if (!fs.statSync(fullPath).isFile()) continue;
|
|
95
|
-
|
|
96
69
|
const content = fs.readFileSync(fullPath, "utf8");
|
|
97
70
|
if (!content) continue;
|
|
98
|
-
|
|
99
|
-
const chunkText = require("./utils/chunker").chunkText;
|
|
100
|
-
const chunks = chunkText(content, 500, 50);
|
|
101
|
-
|
|
71
|
+
const chunks = require("./utils/chunker").chunkText(content, 500, 50);
|
|
102
72
|
for (let i = 0; i < chunks.length; i++) {
|
|
103
73
|
const text = sanitizeTextForEmbedding(chunks[i]);
|
|
104
74
|
if (!text) continue;
|
|
105
|
-
|
|
106
75
|
const hash = hashText(text);
|
|
107
|
-
if (
|
|
108
|
-
|
|
76
|
+
if (cache[hash]) continue; // skip already-ingested chunks
|
|
109
77
|
const rawVector = await getEmbedFn(text);
|
|
110
|
-
const vector = Array.from(rawVector);
|
|
111
|
-
|
|
112
78
|
await vectorStore.upsert({
|
|
113
79
|
id: `${file}:${i}`,
|
|
114
|
-
vector,
|
|
80
|
+
vector: Array.from(rawVector),
|
|
115
81
|
content: text,
|
|
116
82
|
source: `file:${file}`,
|
|
117
83
|
});
|
|
118
|
-
|
|
119
|
-
if (useCache) cache[hash] = true;
|
|
84
|
+
cache[hash] = true;
|
|
120
85
|
inserted++;
|
|
121
86
|
}
|
|
122
87
|
}
|
|
123
88
|
}
|
|
124
89
|
|
|
125
|
-
|
|
90
|
+
saveCache(cache);
|
|
126
91
|
if (vectorStore.close) await vectorStore.close();
|
|
127
|
-
|
|
92
|
+
console.log(`[vector.search] ✅ Ingested ${inserted} chunks from ${ingestRoot}`);
|
|
128
93
|
return { inserted, doc_root: ingestRoot };
|
|
129
94
|
}
|
|
130
95
|
|
|
131
96
|
// =====================================================
|
|
132
|
-
// ✅ 2
|
|
97
|
+
// ✅ SEARCH: 2+ args = query + doc_root
|
|
133
98
|
// =====================================================
|
|
134
|
-
if (
|
|
135
|
-
const query = sanitizeTextForEmbedding(args[0]
|
|
99
|
+
if (args.length >= 2) {
|
|
100
|
+
const query = sanitizeTextForEmbedding(args[0]);
|
|
136
101
|
if (!query) return { text: "", matches: [] };
|
|
137
|
-
|
|
138
102
|
const rawQueryVector = await getEmbedFn(query);
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
const results = await vectorStore.query(queryVector, {
|
|
142
|
-
topK: context.topK || 5,
|
|
143
|
-
});
|
|
144
|
-
|
|
103
|
+
const results = await vectorStore.query(Array.from(rawQueryVector), { topK: context.topK || 5 });
|
|
145
104
|
if (vectorStore.close) await vectorStore.close();
|
|
146
|
-
|
|
147
105
|
return formatResults(results, query);
|
|
148
106
|
}
|
|
149
107
|
|
|
150
108
|
// =====================================================
|
|
151
|
-
// ❌ Unknown action
|
|
109
|
+
// ❌ Unknown action format
|
|
152
110
|
// =====================================================
|
|
153
|
-
console.warn(`⚠️ Unknown
|
|
111
|
+
console.warn(`⚠️ Unknown vector.search action format: "${action}"`);
|
|
154
112
|
return;
|
|
155
113
|
}
|
|
156
114
|
|
|
157
|
-
|
|
158
|
-
resolver.
|
|
115
|
+
// ✅ Must match workflow's "Allow resolvers: - vector.search"
|
|
116
|
+
resolver.resolverName = "vector.search";
|
|
117
|
+
resolver.version = "1.0.43";
|
|
159
118
|
|
|
160
119
|
module.exports = resolver;
|