@wrongstack/plugins 0.317.2 → 0.319.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/index.js
CHANGED
|
@@ -19000,7 +19000,10 @@ var state45 = {
|
|
|
19000
19000
|
buildPromise: null,
|
|
19001
19001
|
buildGeneration: 0,
|
|
19002
19002
|
publishedGeneration: 0,
|
|
19003
|
-
hookUnregister: null
|
|
19003
|
+
hookUnregister: null,
|
|
19004
|
+
deleteHookUnregister: null,
|
|
19005
|
+
lastPruneAt: 0,
|
|
19006
|
+
pruneInFlight: null
|
|
19004
19007
|
};
|
|
19005
19008
|
var DEFAULTS43 = {
|
|
19006
19009
|
enabled: true,
|
|
@@ -19115,11 +19118,11 @@ var YIELD_EVERY_FILES = 64;
|
|
|
19115
19118
|
function yieldEventLoop() {
|
|
19116
19119
|
return new Promise((resolve28) => setImmediate(resolve28));
|
|
19117
19120
|
}
|
|
19118
|
-
function addFileToIndex(relPath, content, size, cfg) {
|
|
19121
|
+
function addFileToIndex(relPath, absPath, content, size, cfg) {
|
|
19119
19122
|
if (!state45.index || content.includes("\0")) return;
|
|
19120
19123
|
state45.bytesIndexed += content.length;
|
|
19121
19124
|
const lines = content.split(/\r?\n/);
|
|
19122
|
-
state45.index.files.set(relPath, { lines, size });
|
|
19125
|
+
state45.index.files.set(relPath, { lines, size, absPath });
|
|
19123
19126
|
for (let i = 0; i < lines.length; i += 1) {
|
|
19124
19127
|
const terms = tokenize(lines[i], cfg.minTokenLength);
|
|
19125
19128
|
for (const term of terms) {
|
|
@@ -19145,7 +19148,7 @@ async function indexFileFromStats(absPath, relPath, stats, cfg) {
|
|
|
19145
19148
|
} catch {
|
|
19146
19149
|
return;
|
|
19147
19150
|
}
|
|
19148
|
-
addFileToIndex(relPath, content, stats.size, cfg);
|
|
19151
|
+
addFileToIndex(relPath, absPath, content, stats.size, cfg);
|
|
19149
19152
|
}
|
|
19150
19153
|
async function flushFileBatch(batch, cfg) {
|
|
19151
19154
|
if (batch.length === 0) return;
|
|
@@ -19238,6 +19241,51 @@ async function ensureIndex(rootPath, cfg) {
|
|
|
19238
19241
|
state45.cachedPath = null;
|
|
19239
19242
|
}
|
|
19240
19243
|
}
|
|
19244
|
+
var PRUNE_MIN_INTERVAL_MS = 3e4;
|
|
19245
|
+
function removeFileFromIndex(relPath, cfg) {
|
|
19246
|
+
const index = state45.index;
|
|
19247
|
+
if (!index) return;
|
|
19248
|
+
const entry = index.files.get(relPath);
|
|
19249
|
+
if (!entry) return;
|
|
19250
|
+
const terms = /* @__PURE__ */ new Set();
|
|
19251
|
+
for (const line of entry.lines) {
|
|
19252
|
+
for (const term of tokenize(line, cfg.minTokenLength)) terms.add(term);
|
|
19253
|
+
}
|
|
19254
|
+
for (const term of terms) {
|
|
19255
|
+
const postings = index.terms.get(term);
|
|
19256
|
+
if (!postings) continue;
|
|
19257
|
+
postings.delete(relPath);
|
|
19258
|
+
if (postings.size === 0) index.terms.delete(term);
|
|
19259
|
+
}
|
|
19260
|
+
index.files.delete(relPath);
|
|
19261
|
+
state45.fileCount = Math.max(0, state45.fileCount - 1);
|
|
19262
|
+
state45.bytesIndexed = Math.max(0, state45.bytesIndexed - entry.size);
|
|
19263
|
+
state45.termCount = index.terms.size;
|
|
19264
|
+
}
|
|
19265
|
+
async function pruneDeletedFiles(rootPath, cfg, opts) {
|
|
19266
|
+
const index = state45.index;
|
|
19267
|
+
if (!index || state45.cachedPath !== rootPath) return 0;
|
|
19268
|
+
if (state45.pruneInFlight) return state45.pruneInFlight;
|
|
19269
|
+
const now = Date.now();
|
|
19270
|
+
if (!opts?.force && now - state45.lastPruneAt < PRUNE_MIN_INTERVAL_MS) return 0;
|
|
19271
|
+
state45.lastPruneAt = now;
|
|
19272
|
+
const pass = (async () => {
|
|
19273
|
+
const missing = [];
|
|
19274
|
+
for (const [relPath, entry] of index.files) {
|
|
19275
|
+
try {
|
|
19276
|
+
await fs2.stat(entry.absPath);
|
|
19277
|
+
} catch {
|
|
19278
|
+
missing.push(relPath);
|
|
19279
|
+
}
|
|
19280
|
+
}
|
|
19281
|
+
for (const relPath of missing) removeFileFromIndex(relPath, cfg);
|
|
19282
|
+
return missing.length;
|
|
19283
|
+
})();
|
|
19284
|
+
state45.pruneInFlight = pass.finally(() => {
|
|
19285
|
+
state45.pruneInFlight = null;
|
|
19286
|
+
});
|
|
19287
|
+
return pass;
|
|
19288
|
+
}
|
|
19241
19289
|
function compareRankedCandidates(a, b) {
|
|
19242
19290
|
return b.score - a.score || a.path.localeCompare(b.path);
|
|
19243
19291
|
}
|
|
@@ -19384,6 +19432,15 @@ var plugin49 = {
|
|
|
19384
19432
|
}
|
|
19385
19433
|
state45.hookUnregister = null;
|
|
19386
19434
|
}
|
|
19435
|
+
if (state45.deleteHookUnregister) {
|
|
19436
|
+
try {
|
|
19437
|
+
state45.deleteHookUnregister();
|
|
19438
|
+
} catch {
|
|
19439
|
+
}
|
|
19440
|
+
state45.deleteHookUnregister = null;
|
|
19441
|
+
}
|
|
19442
|
+
state45.lastPruneAt = 0;
|
|
19443
|
+
state45.pruneInFlight = null;
|
|
19387
19444
|
const cfg = readConfig45(api.config.extensions?.["semantic-search-indexer"]);
|
|
19388
19445
|
api.tools.register({
|
|
19389
19446
|
name: "semantic_search",
|
|
@@ -19493,6 +19550,17 @@ var plugin49 = {
|
|
|
19493
19550
|
{ background: true }
|
|
19494
19551
|
);
|
|
19495
19552
|
}
|
|
19553
|
+
if (typeof api.registerHook === "function") {
|
|
19554
|
+
state45.deleteHookUnregister = api.registerHook(
|
|
19555
|
+
"PostToolUse",
|
|
19556
|
+
"bash|exec|pwsh|delete_file|remove_file",
|
|
19557
|
+
(() => {
|
|
19558
|
+
const root = state45.cachedPath;
|
|
19559
|
+
if (root) void pruneDeletedFiles(root, cfg);
|
|
19560
|
+
}),
|
|
19561
|
+
{ background: true }
|
|
19562
|
+
);
|
|
19563
|
+
}
|
|
19496
19564
|
api.log.info("semantic-search-indexer plugin loaded", {
|
|
19497
19565
|
version: "0.1.0",
|
|
19498
19566
|
defaultLimit: cfg.defaultLimit,
|
|
@@ -19507,6 +19575,13 @@ var plugin49 = {
|
|
|
19507
19575
|
}
|
|
19508
19576
|
state45.hookUnregister = null;
|
|
19509
19577
|
}
|
|
19578
|
+
if (state45.deleteHookUnregister) {
|
|
19579
|
+
try {
|
|
19580
|
+
state45.deleteHookUnregister();
|
|
19581
|
+
} catch {
|
|
19582
|
+
}
|
|
19583
|
+
state45.deleteHookUnregister = null;
|
|
19584
|
+
}
|
|
19510
19585
|
const final = {
|
|
19511
19586
|
fileCount: state45.fileCount,
|
|
19512
19587
|
termCount: state45.termCount,
|
|
@@ -19524,6 +19599,8 @@ var plugin49 = {
|
|
|
19524
19599
|
state45.buildPromise = null;
|
|
19525
19600
|
state45.buildGeneration = 0;
|
|
19526
19601
|
state45.publishedGeneration = 0;
|
|
19602
|
+
state45.lastPruneAt = 0;
|
|
19603
|
+
state45.pruneInFlight = null;
|
|
19527
19604
|
api.log.info("semantic-search-indexer: teardown complete", { final });
|
|
19528
19605
|
},
|
|
19529
19606
|
async health() {
|
|
@@ -42,6 +42,32 @@ interface SemanticSearchConfig {
|
|
|
42
42
|
maxFiles: number;
|
|
43
43
|
}
|
|
44
44
|
export declare function readConfig(raw: unknown): SemanticSearchConfig;
|
|
45
|
+
/**
|
|
46
|
+
* Remove one indexed file's entries (postings + file record) from the index.
|
|
47
|
+
* The stored lines re-derive exactly the terms that were indexed for the
|
|
48
|
+
* file, so each term's posting for it can be dropped and emptied posting
|
|
49
|
+
* maps pruned — no rebuild needed.
|
|
50
|
+
*/
|
|
51
|
+
declare function removeFileFromIndex(relPath: string, cfg: SemanticSearchConfig): void;
|
|
52
|
+
/**
|
|
53
|
+
* Stat-scan the indexed files and drop entries whose file no longer exists —
|
|
54
|
+
* the delete-side counterpart of the write-path generation bump. Shell tools
|
|
55
|
+
* cannot be matched to exact deleted paths reliably, so instead of a full
|
|
56
|
+
* rebuild we prune exactly the dead entries. Throttled and single-flight;
|
|
57
|
+
* resolves with the number of entries removed.
|
|
58
|
+
*/
|
|
59
|
+
declare function pruneDeletedFiles(rootPath: string, cfg: SemanticSearchConfig, opts?: {
|
|
60
|
+
force?: boolean;
|
|
61
|
+
}): Promise<number>;
|
|
45
62
|
declare const plugin: Plugin;
|
|
63
|
+
/**
|
|
64
|
+
* Test seam: direct access to the incremental delete-prune. `force` bypasses
|
|
65
|
+
* the throttle window (production callers go through the PostToolUse hook).
|
|
66
|
+
*/
|
|
67
|
+
export declare const semanticIndexerCoverage: {
|
|
68
|
+
removeFileFromIndex: typeof removeFileFromIndex;
|
|
69
|
+
pruneDeletedFiles: typeof pruneDeletedFiles;
|
|
70
|
+
readConfig: typeof readConfig;
|
|
71
|
+
};
|
|
46
72
|
export default plugin;
|
|
47
73
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -16,7 +16,10 @@ var state = {
|
|
|
16
16
|
buildPromise: null,
|
|
17
17
|
buildGeneration: 0,
|
|
18
18
|
publishedGeneration: 0,
|
|
19
|
-
hookUnregister: null
|
|
19
|
+
hookUnregister: null,
|
|
20
|
+
deleteHookUnregister: null,
|
|
21
|
+
lastPruneAt: 0,
|
|
22
|
+
pruneInFlight: null
|
|
20
23
|
};
|
|
21
24
|
var DEFAULTS = {
|
|
22
25
|
enabled: true,
|
|
@@ -131,11 +134,11 @@ var YIELD_EVERY_FILES = 64;
|
|
|
131
134
|
function yieldEventLoop() {
|
|
132
135
|
return new Promise((resolve2) => setImmediate(resolve2));
|
|
133
136
|
}
|
|
134
|
-
function addFileToIndex(relPath, content, size, cfg) {
|
|
137
|
+
function addFileToIndex(relPath, absPath, content, size, cfg) {
|
|
135
138
|
if (!state.index || content.includes("\0")) return;
|
|
136
139
|
state.bytesIndexed += content.length;
|
|
137
140
|
const lines = content.split(/\r?\n/);
|
|
138
|
-
state.index.files.set(relPath, { lines, size });
|
|
141
|
+
state.index.files.set(relPath, { lines, size, absPath });
|
|
139
142
|
for (let i = 0; i < lines.length; i += 1) {
|
|
140
143
|
const terms = tokenize(lines[i], cfg.minTokenLength);
|
|
141
144
|
for (const term of terms) {
|
|
@@ -161,7 +164,7 @@ async function indexFileFromStats(absPath, relPath, stats, cfg) {
|
|
|
161
164
|
} catch {
|
|
162
165
|
return;
|
|
163
166
|
}
|
|
164
|
-
addFileToIndex(relPath, content, stats.size, cfg);
|
|
167
|
+
addFileToIndex(relPath, absPath, content, stats.size, cfg);
|
|
165
168
|
}
|
|
166
169
|
async function flushFileBatch(batch, cfg) {
|
|
167
170
|
if (batch.length === 0) return;
|
|
@@ -254,6 +257,51 @@ async function ensureIndex(rootPath, cfg) {
|
|
|
254
257
|
state.cachedPath = null;
|
|
255
258
|
}
|
|
256
259
|
}
|
|
260
|
+
var PRUNE_MIN_INTERVAL_MS = 3e4;
|
|
261
|
+
function removeFileFromIndex(relPath, cfg) {
|
|
262
|
+
const index = state.index;
|
|
263
|
+
if (!index) return;
|
|
264
|
+
const entry = index.files.get(relPath);
|
|
265
|
+
if (!entry) return;
|
|
266
|
+
const terms = /* @__PURE__ */ new Set();
|
|
267
|
+
for (const line of entry.lines) {
|
|
268
|
+
for (const term of tokenize(line, cfg.minTokenLength)) terms.add(term);
|
|
269
|
+
}
|
|
270
|
+
for (const term of terms) {
|
|
271
|
+
const postings = index.terms.get(term);
|
|
272
|
+
if (!postings) continue;
|
|
273
|
+
postings.delete(relPath);
|
|
274
|
+
if (postings.size === 0) index.terms.delete(term);
|
|
275
|
+
}
|
|
276
|
+
index.files.delete(relPath);
|
|
277
|
+
state.fileCount = Math.max(0, state.fileCount - 1);
|
|
278
|
+
state.bytesIndexed = Math.max(0, state.bytesIndexed - entry.size);
|
|
279
|
+
state.termCount = index.terms.size;
|
|
280
|
+
}
|
|
281
|
+
async function pruneDeletedFiles(rootPath, cfg, opts) {
|
|
282
|
+
const index = state.index;
|
|
283
|
+
if (!index || state.cachedPath !== rootPath) return 0;
|
|
284
|
+
if (state.pruneInFlight) return state.pruneInFlight;
|
|
285
|
+
const now = Date.now();
|
|
286
|
+
if (!opts?.force && now - state.lastPruneAt < PRUNE_MIN_INTERVAL_MS) return 0;
|
|
287
|
+
state.lastPruneAt = now;
|
|
288
|
+
const pass = (async () => {
|
|
289
|
+
const missing = [];
|
|
290
|
+
for (const [relPath, entry] of index.files) {
|
|
291
|
+
try {
|
|
292
|
+
await fs.stat(entry.absPath);
|
|
293
|
+
} catch {
|
|
294
|
+
missing.push(relPath);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
for (const relPath of missing) removeFileFromIndex(relPath, cfg);
|
|
298
|
+
return missing.length;
|
|
299
|
+
})();
|
|
300
|
+
state.pruneInFlight = pass.finally(() => {
|
|
301
|
+
state.pruneInFlight = null;
|
|
302
|
+
});
|
|
303
|
+
return pass;
|
|
304
|
+
}
|
|
257
305
|
function compareRankedCandidates(a, b) {
|
|
258
306
|
return b.score - a.score || a.path.localeCompare(b.path);
|
|
259
307
|
}
|
|
@@ -400,6 +448,15 @@ var plugin = {
|
|
|
400
448
|
}
|
|
401
449
|
state.hookUnregister = null;
|
|
402
450
|
}
|
|
451
|
+
if (state.deleteHookUnregister) {
|
|
452
|
+
try {
|
|
453
|
+
state.deleteHookUnregister();
|
|
454
|
+
} catch {
|
|
455
|
+
}
|
|
456
|
+
state.deleteHookUnregister = null;
|
|
457
|
+
}
|
|
458
|
+
state.lastPruneAt = 0;
|
|
459
|
+
state.pruneInFlight = null;
|
|
403
460
|
const cfg = readConfig(api.config.extensions?.["semantic-search-indexer"]);
|
|
404
461
|
api.tools.register({
|
|
405
462
|
name: "semantic_search",
|
|
@@ -509,6 +566,17 @@ var plugin = {
|
|
|
509
566
|
{ background: true }
|
|
510
567
|
);
|
|
511
568
|
}
|
|
569
|
+
if (typeof api.registerHook === "function") {
|
|
570
|
+
state.deleteHookUnregister = api.registerHook(
|
|
571
|
+
"PostToolUse",
|
|
572
|
+
"bash|exec|pwsh|delete_file|remove_file",
|
|
573
|
+
(() => {
|
|
574
|
+
const root = state.cachedPath;
|
|
575
|
+
if (root) void pruneDeletedFiles(root, cfg);
|
|
576
|
+
}),
|
|
577
|
+
{ background: true }
|
|
578
|
+
);
|
|
579
|
+
}
|
|
512
580
|
api.log.info("semantic-search-indexer plugin loaded", {
|
|
513
581
|
version: "0.1.0",
|
|
514
582
|
defaultLimit: cfg.defaultLimit,
|
|
@@ -523,6 +591,13 @@ var plugin = {
|
|
|
523
591
|
}
|
|
524
592
|
state.hookUnregister = null;
|
|
525
593
|
}
|
|
594
|
+
if (state.deleteHookUnregister) {
|
|
595
|
+
try {
|
|
596
|
+
state.deleteHookUnregister();
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
state.deleteHookUnregister = null;
|
|
600
|
+
}
|
|
526
601
|
const final = {
|
|
527
602
|
fileCount: state.fileCount,
|
|
528
603
|
termCount: state.termCount,
|
|
@@ -540,6 +615,8 @@ var plugin = {
|
|
|
540
615
|
state.buildPromise = null;
|
|
541
616
|
state.buildGeneration = 0;
|
|
542
617
|
state.publishedGeneration = 0;
|
|
618
|
+
state.lastPruneAt = 0;
|
|
619
|
+
state.pruneInFlight = null;
|
|
543
620
|
api.log.info("semantic-search-indexer: teardown complete", { final });
|
|
544
621
|
},
|
|
545
622
|
async health() {
|
|
@@ -556,8 +633,14 @@ var plugin = {
|
|
|
556
633
|
};
|
|
557
634
|
}
|
|
558
635
|
};
|
|
636
|
+
var semanticIndexerCoverage = {
|
|
637
|
+
removeFileFromIndex,
|
|
638
|
+
pruneDeletedFiles,
|
|
639
|
+
readConfig
|
|
640
|
+
};
|
|
559
641
|
var semantic_search_indexer_default = plugin;
|
|
560
642
|
export {
|
|
561
643
|
semantic_search_indexer_default as default,
|
|
562
|
-
readConfig
|
|
644
|
+
readConfig,
|
|
645
|
+
semanticIndexerCoverage
|
|
563
646
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.319.0",
|
|
4
4
|
"description": "Official WrongStack collection of focused plugins for code quality, security, observability, planning, and agent coordination",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ECOSTACK TECHNOLOGY OÜ",
|
|
@@ -303,10 +303,10 @@
|
|
|
303
303
|
"vitest": "^4.1.11"
|
|
304
304
|
},
|
|
305
305
|
"dependencies": {
|
|
306
|
-
"@wrongstack/core": "0.
|
|
307
|
-
"@wrongstack/primitives": "0.
|
|
308
|
-
"@wrongstack/plugin-sdk": "0.
|
|
309
|
-
"@wrongstack/tools": "0.
|
|
306
|
+
"@wrongstack/core": "0.319.0",
|
|
307
|
+
"@wrongstack/primitives": "0.319.0",
|
|
308
|
+
"@wrongstack/plugin-sdk": "0.319.0",
|
|
309
|
+
"@wrongstack/tools": "0.319.0"
|
|
310
310
|
},
|
|
311
311
|
"scripts": {
|
|
312
312
|
"build": "node ../../scripts/build-package.mjs",
|