@wrongstack/plugins 0.283.1 → 0.284.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/branch-guard.js +32 -21
- package/dist/checkpoint.js +23 -3
- package/dist/commit-validator.js +6 -1
- package/dist/dep-guard.js +6 -1
- package/dist/index.js +385 -204
- package/dist/injection-shield.d.ts +1 -1
- package/dist/injection-shield.js +2 -1
- package/dist/lint-gate.js +105 -69
- package/dist/loop-breaker.js +36 -13
- package/dist/path-guard.js +6 -1
- package/dist/secret-scanner.js +34 -19
- package/dist/semantic-search-indexer.js +94 -37
- package/dist/spec-linker.js +17 -11
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
2
|
import { isAbsolute, resolve, relative } from 'path';
|
|
3
3
|
|
|
4
4
|
// src/semantic-search-indexer/index.ts
|
|
@@ -11,7 +11,8 @@ var state = {
|
|
|
11
11
|
bytesIndexed: 0,
|
|
12
12
|
truncated: false,
|
|
13
13
|
queryCount: 0,
|
|
14
|
-
reindexCount: 0
|
|
14
|
+
reindexCount: 0,
|
|
15
|
+
buildPromise: null};
|
|
15
16
|
var DEFAULTS = {
|
|
16
17
|
enabled: true,
|
|
17
18
|
includeExtensions: [
|
|
@@ -111,26 +112,16 @@ function shouldIndexFile(filePath, cfg) {
|
|
|
111
112
|
}
|
|
112
113
|
return false;
|
|
113
114
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (!stats.isFile() || stats.size > cfg.maxFileBytes) return;
|
|
124
|
-
let content;
|
|
125
|
-
try {
|
|
126
|
-
content = readFileSync(absPath, "utf-8");
|
|
127
|
-
} catch {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
if (content.includes("\0")) return;
|
|
115
|
+
var INDEX_BATCH_SIZE = 32;
|
|
116
|
+
var YIELD_EVERY_FILES = 64;
|
|
117
|
+
function yieldEventLoop() {
|
|
118
|
+
return new Promise((resolve2) => setImmediate(resolve2));
|
|
119
|
+
}
|
|
120
|
+
function addFileToIndex(relPath, content, size, cfg) {
|
|
121
|
+
if (!state.index || content.includes("\0")) return;
|
|
131
122
|
state.bytesIndexed += content.length;
|
|
132
123
|
const lines = content.split(/\r?\n/);
|
|
133
|
-
state.index.files.set(relPath, { lines, size
|
|
124
|
+
state.index.files.set(relPath, { lines, size });
|
|
134
125
|
for (let i = 0; i < lines.length; i += 1) {
|
|
135
126
|
const terms = tokenize(lines[i], cfg.minTokenLength);
|
|
136
127
|
for (const term of terms) {
|
|
@@ -148,10 +139,27 @@ function indexFile(absPath, relPath, cfg) {
|
|
|
148
139
|
}
|
|
149
140
|
}
|
|
150
141
|
}
|
|
151
|
-
function
|
|
142
|
+
async function indexFileFromStats(absPath, relPath, stats, cfg) {
|
|
143
|
+
if (!shouldIndexFile(relPath, cfg) || !stats.isFile() || stats.size > cfg.maxFileBytes) return;
|
|
144
|
+
let content;
|
|
145
|
+
try {
|
|
146
|
+
content = await fs.readFile(absPath, "utf-8");
|
|
147
|
+
} catch {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
addFileToIndex(relPath, content, stats.size, cfg);
|
|
151
|
+
}
|
|
152
|
+
async function flushFileBatch(batch, cfg) {
|
|
153
|
+
if (batch.length === 0) return;
|
|
154
|
+
const current = batch.splice(0, batch.length);
|
|
155
|
+
await Promise.allSettled(
|
|
156
|
+
current.map(({ absPath, relPath, stats }) => indexFileFromStats(absPath, relPath, stats, cfg))
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
async function walkDirectory(absPath, cfg, excludes, fileBatch) {
|
|
152
160
|
let entries;
|
|
153
161
|
try {
|
|
154
|
-
entries =
|
|
162
|
+
entries = await fs.readdir(absPath, { withFileTypes: true });
|
|
155
163
|
} catch {
|
|
156
164
|
return;
|
|
157
165
|
}
|
|
@@ -166,15 +174,28 @@ function walkDirectory(absPath, cfg, excludes) {
|
|
|
166
174
|
if (relChild === "" || relChild === ".") continue;
|
|
167
175
|
if (excludes.some((re) => re.test(relChild))) continue;
|
|
168
176
|
if (ent.isDirectory()) {
|
|
169
|
-
walkDirectory(absChild, cfg, excludes);
|
|
177
|
+
await walkDirectory(absChild, cfg, excludes, fileBatch);
|
|
170
178
|
if (state.truncated) return;
|
|
171
179
|
} else if (ent.isFile()) {
|
|
172
|
-
indexFile(absChild, relChild, cfg);
|
|
173
180
|
state.fileCount += 1;
|
|
181
|
+
if (!shouldIndexFile(relChild, cfg)) continue;
|
|
182
|
+
let stats;
|
|
183
|
+
try {
|
|
184
|
+
stats = await fs.stat(absChild);
|
|
185
|
+
} catch {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
fileBatch.push({ absPath: absChild, relPath: relChild, stats });
|
|
189
|
+
if (fileBatch.length >= INDEX_BATCH_SIZE) {
|
|
190
|
+
await flushFileBatch(fileBatch, cfg);
|
|
191
|
+
}
|
|
192
|
+
if (state.fileCount % YIELD_EVERY_FILES === 0) {
|
|
193
|
+
await yieldEventLoop();
|
|
194
|
+
}
|
|
174
195
|
}
|
|
175
196
|
}
|
|
176
197
|
}
|
|
177
|
-
function buildIndex(rootPath, cfg) {
|
|
198
|
+
async function buildIndex(rootPath, cfg) {
|
|
178
199
|
state.index = { terms: /* @__PURE__ */ new Map(), files: /* @__PURE__ */ new Map() };
|
|
179
200
|
state.cachedPath = rootPath;
|
|
180
201
|
state.fileCount = 0;
|
|
@@ -185,20 +206,50 @@ function buildIndex(rootPath, cfg) {
|
|
|
185
206
|
const excludes = compileExcludes(cfg.excludePatterns);
|
|
186
207
|
let rootStats;
|
|
187
208
|
try {
|
|
188
|
-
rootStats =
|
|
209
|
+
rootStats = await fs.stat(rootPath);
|
|
189
210
|
} catch {
|
|
190
211
|
state.termCount = 0;
|
|
191
212
|
return;
|
|
192
213
|
}
|
|
193
214
|
if (rootStats.isFile()) {
|
|
194
215
|
const relPath = normalizeSlashes(relative(normalizeSlashes(process.cwd()), rootPath));
|
|
195
|
-
|
|
216
|
+
await indexFileFromStats(rootPath, relPath === "" ? "." : relPath, rootStats, cfg);
|
|
196
217
|
state.fileCount = state.index.files.size;
|
|
197
218
|
} else if (rootStats.isDirectory()) {
|
|
198
|
-
|
|
219
|
+
const fileBatch = [];
|
|
220
|
+
await walkDirectory(rootPath, cfg, excludes, fileBatch);
|
|
221
|
+
await flushFileBatch(fileBatch, cfg);
|
|
199
222
|
}
|
|
200
223
|
state.termCount = state.index.terms.size;
|
|
201
224
|
}
|
|
225
|
+
async function ensureIndex(rootPath, cfg) {
|
|
226
|
+
if (state.index && state.cachedPath === rootPath) return;
|
|
227
|
+
state.buildPromise ??= buildIndex(rootPath, cfg).finally(() => {
|
|
228
|
+
state.buildPromise = null;
|
|
229
|
+
});
|
|
230
|
+
await state.buildPromise;
|
|
231
|
+
if (!state.index || state.cachedPath !== rootPath) {
|
|
232
|
+
state.buildPromise = buildIndex(rootPath, cfg).finally(() => {
|
|
233
|
+
state.buildPromise = null;
|
|
234
|
+
});
|
|
235
|
+
await state.buildPromise;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function compareRankedCandidates(a, b) {
|
|
239
|
+
return b.score - a.score || a.path.localeCompare(b.path);
|
|
240
|
+
}
|
|
241
|
+
function insertTopCandidate(top, candidate, limit) {
|
|
242
|
+
if (limit <= 0) return;
|
|
243
|
+
if (top.length === 0) {
|
|
244
|
+
top.push(candidate);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
let insertAt = top.findIndex((existing) => compareRankedCandidates(candidate, existing) < 0);
|
|
248
|
+
if (insertAt === -1) insertAt = top.length;
|
|
249
|
+
if (insertAt >= limit) return;
|
|
250
|
+
top.splice(insertAt, 0, candidate);
|
|
251
|
+
if (top.length > limit) top.pop();
|
|
252
|
+
}
|
|
202
253
|
function runQuery(query, limit, cfg) {
|
|
203
254
|
if (!state.index) return [];
|
|
204
255
|
const rawTokens = tokenize(query, cfg.minTokenLength);
|
|
@@ -219,12 +270,18 @@ function runQuery(query, limit, cfg) {
|
|
|
219
270
|
terms.add(token);
|
|
220
271
|
}
|
|
221
272
|
}
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
273
|
+
const top = [];
|
|
274
|
+
for (const [path, score] of scores) {
|
|
275
|
+
insertTopCandidate(
|
|
276
|
+
top,
|
|
277
|
+
{
|
|
278
|
+
path,
|
|
279
|
+
score,
|
|
280
|
+
terms: Array.from(matchedTerms.get(path) ?? [])
|
|
281
|
+
},
|
|
282
|
+
limit
|
|
283
|
+
);
|
|
284
|
+
}
|
|
228
285
|
return top.map(({ path, score, terms }) => {
|
|
229
286
|
const entry = state.index.files.get(path);
|
|
230
287
|
const matchedLines = [];
|
|
@@ -314,6 +371,7 @@ var plugin = {
|
|
|
314
371
|
state.truncated = false;
|
|
315
372
|
state.queryCount = 0;
|
|
316
373
|
state.reindexCount = 0;
|
|
374
|
+
state.buildPromise = null;
|
|
317
375
|
const cfg = readConfig(api.config.extensions?.["semantic-search-indexer"]);
|
|
318
376
|
api.tools.register({
|
|
319
377
|
name: "semantic_search",
|
|
@@ -349,9 +407,7 @@ var plugin = {
|
|
|
349
407
|
if (!resolved) {
|
|
350
408
|
return { ok: false, error: "path outside project root" };
|
|
351
409
|
}
|
|
352
|
-
|
|
353
|
-
buildIndex(resolved, cfg);
|
|
354
|
-
}
|
|
410
|
+
await ensureIndex(resolved, cfg);
|
|
355
411
|
const query = String(input.query ?? "");
|
|
356
412
|
const limit = typeof input.limit === "number" && input.limit >= 1 ? Math.floor(input.limit) : cfg.defaultLimit;
|
|
357
413
|
const results = runQuery(query, limit, cfg);
|
|
@@ -415,6 +471,7 @@ var plugin = {
|
|
|
415
471
|
state.truncated = false;
|
|
416
472
|
state.queryCount = 0;
|
|
417
473
|
state.reindexCount = 0;
|
|
474
|
+
state.buildPromise = null;
|
|
418
475
|
api.log.info("semantic-search-indexer: teardown complete", { final });
|
|
419
476
|
},
|
|
420
477
|
async health() {
|
package/dist/spec-linker.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync, statSync } from 'fs';
|
|
2
1
|
import * as fsp from 'fs/promises';
|
|
3
2
|
import 'path';
|
|
4
3
|
import 'child_process';
|
|
4
|
+
import 'fs';
|
|
5
5
|
import '@wrongstack/core';
|
|
6
6
|
import 'os';
|
|
7
7
|
import 'crypto';
|
|
@@ -102,7 +102,10 @@ var prompt_firewall_default = plugin28;
|
|
|
102
102
|
// src/secret-scanner/index.ts
|
|
103
103
|
var BASE_PATTERNS = [
|
|
104
104
|
// LLM provider keys
|
|
105
|
-
{
|
|
105
|
+
{
|
|
106
|
+
type: "anthropic_key",
|
|
107
|
+
regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g
|
|
108
|
+
},
|
|
106
109
|
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
107
110
|
// GitHub
|
|
108
111
|
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
|
|
@@ -114,7 +117,10 @@ var BASE_PATTERNS = [
|
|
|
114
117
|
// Slack
|
|
115
118
|
{ type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
|
|
116
119
|
// Stripe
|
|
117
|
-
{
|
|
120
|
+
{
|
|
121
|
+
type: "stripe_key",
|
|
122
|
+
regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g
|
|
123
|
+
},
|
|
118
124
|
// Twilio
|
|
119
125
|
{ type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
|
|
120
126
|
// Telegram
|
|
@@ -151,10 +157,7 @@ var BASE_PATTERNS = [
|
|
|
151
157
|
var PATTERNS3 = [...BASE_PATTERNS];
|
|
152
158
|
buildCombinedRegex(PATTERNS3);
|
|
153
159
|
function buildCombinedRegex(patterns) {
|
|
154
|
-
return new RegExp(
|
|
155
|
-
patterns.map((p) => `(${p.regex.source})`).join("|"),
|
|
156
|
-
"g"
|
|
157
|
-
);
|
|
160
|
+
return new RegExp(patterns.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
158
161
|
}
|
|
159
162
|
var plugin29 = {
|
|
160
163
|
name: "secret-scanner"};
|
|
@@ -565,11 +568,10 @@ var plugin62 = {
|
|
|
565
568
|
return;
|
|
566
569
|
}
|
|
567
570
|
state59.postInvocations += 1;
|
|
568
|
-
if (!existsSync(filePath)) return;
|
|
569
571
|
let content;
|
|
570
572
|
try {
|
|
571
|
-
const
|
|
572
|
-
if (!
|
|
573
|
+
const stat4 = await fsp.stat(filePath);
|
|
574
|
+
if (!stat4.isFile()) return;
|
|
573
575
|
content = await fsp.readFile(filePath, "utf-8");
|
|
574
576
|
} catch {
|
|
575
577
|
state59.readErrorCount += 1;
|
|
@@ -615,7 +617,11 @@ ${lines}${overflowNote}`
|
|
|
615
617
|
\u{1F517} spec-linker (autoFix): wrapped unlinked plugin reference(s) in '${filePath}'.`
|
|
616
618
|
};
|
|
617
619
|
};
|
|
618
|
-
state59.preHookUnregister = api.registerHook("PreToolUse", "write", preHook
|
|
620
|
+
state59.preHookUnregister = api.registerHook("PreToolUse", "write", preHook, {
|
|
621
|
+
name: "spec-linker-autofix",
|
|
622
|
+
stage: "mutate",
|
|
623
|
+
failurePolicy: "open"
|
|
624
|
+
});
|
|
619
625
|
}
|
|
620
626
|
api.tools.register({
|
|
621
627
|
name: "spec_linker_status",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.284.0",
|
|
4
4
|
"description": "Official WrongStack plugin collection — 62 focused, single-purpose plugins for code quality, security, observability, planning, and agent coordination",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ECOSTACK TECHNOLOGY OÜ",
|
|
@@ -275,8 +275,8 @@
|
|
|
275
275
|
"vitest": "^4.1.9"
|
|
276
276
|
},
|
|
277
277
|
"dependencies": {
|
|
278
|
-
"@wrongstack/
|
|
279
|
-
"@wrongstack/
|
|
278
|
+
"@wrongstack/tools": "0.284.0",
|
|
279
|
+
"@wrongstack/core": "0.284.0"
|
|
280
280
|
},
|
|
281
281
|
"scripts": {
|
|
282
282
|
"build": "tsup",
|