@wrongstack/plugins 0.286.0 → 0.289.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/README.md +20 -11
- package/dist/accessibility-auditor/index.d.ts.map +1 -1
- package/dist/accessibility-auditor.js +67 -55
- package/dist/api-compatibility-gate.js +1 -1
- package/dist/auto-doc.js +1 -1
- package/dist/auto-i18n-extractor/index.d.ts.map +1 -1
- package/dist/auto-i18n-extractor.js +20 -12
- package/dist/changelog-writer.js +1 -1
- package/dist/code-metrics/index.d.ts.map +1 -1
- package/dist/code-metrics.js +63 -50
- package/dist/commit-validator.js +1 -1
- package/dist/config-validator.js +1 -1
- package/dist/cost-tracker/index.d.ts.map +1 -1
- package/dist/cost-tracker.js +4 -2
- package/dist/cron/index.d.ts.map +1 -1
- package/dist/cron.js +29 -0
- package/dist/dead-code-detector/index.d.ts.map +1 -1
- package/dist/dead-code-detector.js +28 -19
- package/dist/dep-guard.js +1 -1
- package/dist/diff-summary/index.d.ts.map +1 -1
- package/dist/diff-summary.js +19 -11
- package/dist/doc-sync-guard/index.d.ts.map +1 -1
- package/dist/doc-sync-guard.js +25 -16
- package/dist/duplicate-code-detector/index.d.ts.map +1 -1
- package/dist/duplicate-code-detector.js +70 -57
- package/dist/error-lens.js +1 -1
- package/dist/feature-flag-tracker/index.d.ts.map +1 -1
- package/dist/feature-flag-tracker.js +69 -56
- package/dist/file-watcher/index.d.ts.map +1 -1
- package/dist/file-watcher.js +21 -4
- package/dist/format-on-save/index.d.ts.map +1 -1
- package/dist/format-on-save.js +19 -11
- package/dist/git-autocommit.js +1 -1
- package/dist/import-organizer/index.d.ts.map +1 -1
- package/dist/import-organizer.js +23 -14
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1167 -1048
- package/dist/interface-contract-guard/index.d.ts.map +1 -1
- package/dist/interface-contract-guard.js +64 -51
- package/dist/lint-gate/index.d.ts.map +1 -1
- package/dist/lint-gate.js +61 -30
- package/dist/migration-planner/index.d.ts +12 -2
- package/dist/migration-planner/index.d.ts.map +1 -1
- package/dist/migration-planner.js +210 -28
- package/dist/pr-drafter.js +1 -1
- package/dist/prompt-firewall/index.d.ts +14 -6
- package/dist/prompt-firewall/index.d.ts.map +1 -1
- package/dist/prompt-firewall.js +7 -6
- package/dist/refactor-suggester/index.d.ts.map +1 -1
- package/dist/refactor-suggester.js +69 -56
- package/dist/release-notes-generator/index.d.ts +3 -1
- package/dist/release-notes-generator/index.d.ts.map +1 -1
- package/dist/release-notes-generator.js +152 -12
- package/dist/runtime/index.d.ts +36 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/llm.d.ts +33 -0
- package/dist/runtime/llm.d.ts.map +1 -0
- package/dist/runtime.js +119 -25
- package/dist/schema-evolution-guard/index.d.ts.map +1 -1
- package/dist/schema-evolution-guard.js +22 -13
- package/dist/secret-scanner/index.d.ts.map +1 -1
- package/dist/secret-scanner.js +17 -6
- package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
- package/dist/security-hotspot-scanner.js +24 -15
- package/dist/session-recap.js +1 -1
- package/dist/spec-linker.js +1167 -1048
- package/dist/test-generator/index.d.ts +5 -3
- package/dist/test-generator/index.d.ts.map +1 -1
- package/dist/test-generator.js +169 -26
- package/dist/test-runner-gate/index.d.ts.map +1 -1
- package/dist/test-runner-gate.js +65 -43
- package/dist/type-gate/index.d.ts.map +1 -1
- package/dist/type-gate.js +23 -35
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dead-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dead-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAiQ/C,QAAA,MAAM,MAAM,EAAE,MAkOb,CAAC;eAEa,MAAM"}
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
// src/dead-code-detector/index.ts
|
|
2
2
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
-
import { extname
|
|
3
|
+
import { extname as extname2, join, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/runtime/index.ts
|
|
6
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
function hasLeadingDash(arg) {
|
|
9
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
10
|
+
}
|
|
11
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
12
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
13
|
+
if (hasLeadingDash(candidate)) return false;
|
|
14
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
15
|
+
const rel = relative(projectRoot, resolved);
|
|
16
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
17
|
+
}
|
|
18
|
+
function withinProject(p) {
|
|
19
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/dead-code-detector/index.ts
|
|
4
23
|
var API_VERSION = "^0.1.10";
|
|
5
24
|
var state = {
|
|
6
25
|
scanCount: 0,
|
|
@@ -44,14 +63,14 @@ function gatherFiles(root, depth, cfg) {
|
|
|
44
63
|
walk(join(dir, entry.name), remaining - 1);
|
|
45
64
|
}
|
|
46
65
|
} else if (entry.isFile()) {
|
|
47
|
-
const ext =
|
|
66
|
+
const ext = extname2(entry.name).toLowerCase();
|
|
48
67
|
if (cfg.extensions.includes(ext)) {
|
|
49
68
|
files.push(join(dir, entry.name));
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
72
|
}
|
|
54
|
-
walk(
|
|
73
|
+
walk(resolve2(root), depth);
|
|
55
74
|
return files;
|
|
56
75
|
}
|
|
57
76
|
function stripNoise(content) {
|
|
@@ -134,22 +153,12 @@ function scan(root, depth, cfg) {
|
|
|
134
153
|
}
|
|
135
154
|
return { findings: findUnusedSymbols(files), scannedFiles: files.length };
|
|
136
155
|
}
|
|
137
|
-
function withinProject(p) {
|
|
138
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
139
|
-
const root = process.cwd();
|
|
140
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
141
|
-
const rel = relative(root, resolved);
|
|
142
|
-
if (rel === "" || rel === ".") return true;
|
|
143
|
-
if (rel.startsWith("..")) return false;
|
|
144
|
-
if (isAbsolute(rel)) return false;
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
156
|
function resolveScanRoot(rawPath) {
|
|
148
|
-
const resolved =
|
|
157
|
+
const resolved = resolve2(process.cwd(), rawPath);
|
|
149
158
|
try {
|
|
150
159
|
const stats = statSync(resolved);
|
|
151
160
|
if (!stats.isDirectory()) {
|
|
152
|
-
return
|
|
161
|
+
return resolve2(resolved, "..");
|
|
153
162
|
}
|
|
154
163
|
} catch {
|
|
155
164
|
}
|
|
@@ -159,7 +168,7 @@ function toPosix(p) {
|
|
|
159
168
|
return p.replace(/\\/g, "/");
|
|
160
169
|
}
|
|
161
170
|
function relativePath(p) {
|
|
162
|
-
return toPosix(
|
|
171
|
+
return toPosix(relative2(process.cwd(), p));
|
|
163
172
|
}
|
|
164
173
|
var plugin = {
|
|
165
174
|
name: "dead-code-detector",
|
|
@@ -230,8 +239,8 @@ var plugin = {
|
|
|
230
239
|
state.errorCount += 1;
|
|
231
240
|
return;
|
|
232
241
|
}
|
|
233
|
-
const changedFile =
|
|
234
|
-
const relevant = result.findings.filter((f) =>
|
|
242
|
+
const changedFile = resolve2(process.cwd(), sourcePath);
|
|
243
|
+
const relevant = result.findings.filter((f) => resolve2(f.file) === changedFile);
|
|
235
244
|
if (relevant.length === 0) {
|
|
236
245
|
state.missCount += 1;
|
|
237
246
|
return;
|
|
@@ -247,7 +256,7 @@ ${list}
|
|
|
247
256
|
Consider removing the export if it is not part of the public API.`;
|
|
248
257
|
return { additionalContext: message };
|
|
249
258
|
};
|
|
250
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
259
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
251
260
|
api.tools.register({
|
|
252
261
|
name: "dead_code_scan",
|
|
253
262
|
description: "Scan TypeScript/JavaScript source files for exported symbols that appear unused anywhere in the project.",
|
package/dist/dep-guard.js
CHANGED
|
@@ -138,7 +138,7 @@ var plugin = {
|
|
|
138
138
|
version: "0.1.0",
|
|
139
139
|
description: "Supervises dependency installs: blocks deny-listed packages, flags typosquat lookalikes, and optionally warns on unpinned versions",
|
|
140
140
|
apiVersion: "^0.1.10",
|
|
141
|
-
capabilities: { tools: true, hooks: true },
|
|
141
|
+
capabilities: { tools: true, hooks: true, llm: true },
|
|
142
142
|
defaultConfig: { ...DEFAULTS },
|
|
143
143
|
configSchema: {
|
|
144
144
|
type: "object",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAwP/C,QAAA,MAAM,MAAM,EAAE,MA4Pb,CAAC;eAEa,MAAM"}
|
package/dist/diff-summary.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
// src/diff-summary/index.ts
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
// src/runtime/index.ts
|
|
5
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
6
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
function hasLeadingDash(arg) {
|
|
8
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
9
|
+
}
|
|
10
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
11
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
12
|
+
if (hasLeadingDash(candidate)) return false;
|
|
13
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
14
|
+
const rel = relative(projectRoot, resolved);
|
|
15
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
16
|
+
}
|
|
5
17
|
function withinProject(p) {
|
|
6
|
-
|
|
7
|
-
const root = process.cwd();
|
|
8
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
9
|
-
const rel = relative(root, resolved);
|
|
10
|
-
if (rel === "" || rel === ".") return true;
|
|
11
|
-
if (rel.startsWith("..")) return false;
|
|
12
|
-
if (isAbsolute(rel)) return false;
|
|
13
|
-
return true;
|
|
18
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
14
19
|
}
|
|
20
|
+
|
|
21
|
+
// src/diff-summary/index.ts
|
|
22
|
+
var API_VERSION = "^0.1.10";
|
|
15
23
|
var state = {
|
|
16
24
|
invocationCount: 0,
|
|
17
25
|
/** Times a diff was successfully generated and injected. */
|
|
@@ -243,7 +251,7 @@ var plugin = {
|
|
|
243
251
|
additionalContext: header + summary
|
|
244
252
|
};
|
|
245
253
|
};
|
|
246
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
254
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
247
255
|
api.tools.register({
|
|
248
256
|
name: "diff_summary_status",
|
|
249
257
|
description: "Reports diff-summary state: mode, maxLines, and per-session invocation/injected/fallback counters.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA0I/C,QAAA,MAAM,MAAM,EAAE,MAuJb,CAAC;eAEa,MAAM"}
|
package/dist/doc-sync-guard.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
// src/doc-sync-guard/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import { basename as basename2, extname as extname2 } from "node:path";
|
|
3
|
+
|
|
4
|
+
// src/runtime/index.ts
|
|
5
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
6
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
7
|
+
function hasLeadingDash(arg) {
|
|
8
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
9
|
+
}
|
|
10
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
11
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
12
|
+
if (hasLeadingDash(candidate)) return false;
|
|
13
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
14
|
+
const rel = relative(projectRoot, resolved);
|
|
15
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
16
|
+
}
|
|
17
|
+
function withinProject(p) {
|
|
18
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/doc-sync-guard/index.ts
|
|
3
22
|
var API_VERSION = "^0.1.10";
|
|
4
23
|
var state = {
|
|
5
24
|
changedFiles: [],
|
|
@@ -24,35 +43,25 @@ function readConfig(raw) {
|
|
|
24
43
|
maxTrackedFiles: typeof r["maxTrackedFiles"] === "number" && r["maxTrackedFiles"] >= 1 && r["maxTrackedFiles"] <= 200 ? r["maxTrackedFiles"] : DEFAULTS.maxTrackedFiles
|
|
25
44
|
};
|
|
26
45
|
}
|
|
27
|
-
function withinProject(p) {
|
|
28
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
29
|
-
const root = process.cwd();
|
|
30
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
31
|
-
const rel = relative(root, resolved);
|
|
32
|
-
if (rel === "" || rel === ".") return true;
|
|
33
|
-
if (rel.startsWith("..")) return false;
|
|
34
|
-
if (isAbsolute(rel)) return false;
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
46
|
function normalizeSlashes(p) {
|
|
38
47
|
return p.replace(/\\/g, "/");
|
|
39
48
|
}
|
|
40
49
|
function isSourceFile(p, extensions) {
|
|
41
|
-
const ext =
|
|
50
|
+
const ext = extname2(p).toLowerCase();
|
|
42
51
|
return extensions.includes(ext);
|
|
43
52
|
}
|
|
44
53
|
function isPublicSource(p, extensions) {
|
|
45
54
|
if (!isSourceFile(p, extensions)) return false;
|
|
46
55
|
const norm = normalizeSlashes(p);
|
|
47
56
|
if (norm.includes("/node_modules/") || norm.startsWith("node_modules/")) return false;
|
|
48
|
-
const base =
|
|
57
|
+
const base = basename2(norm).toLowerCase();
|
|
49
58
|
if (/\.(test|spec)\./.test(base)) return false;
|
|
50
59
|
if (base.startsWith("_")) return false;
|
|
51
60
|
return true;
|
|
52
61
|
}
|
|
53
62
|
function isDocFile(p, docNames) {
|
|
54
63
|
const norm = normalizeSlashes(p);
|
|
55
|
-
const base =
|
|
64
|
+
const base = basename2(norm);
|
|
56
65
|
const lowerBase = base.toLowerCase();
|
|
57
66
|
if (docNames.some((name) => lowerBase === name.toLowerCase())) return true;
|
|
58
67
|
if (norm.includes("/docs/") || norm.startsWith("docs/")) return true;
|
|
@@ -70,7 +79,7 @@ function extractDocContent(toolInput) {
|
|
|
70
79
|
return void 0;
|
|
71
80
|
}
|
|
72
81
|
function referenceTokens(p) {
|
|
73
|
-
const base =
|
|
82
|
+
const base = basename2(p);
|
|
74
83
|
const withoutExt = base.replace(/\.[^.]+$/, "");
|
|
75
84
|
const tokens = [base];
|
|
76
85
|
if (withoutExt && withoutExt !== base) tokens.push(withoutExt);
|
|
@@ -155,7 +164,7 @@ Consider mentioning these changes in the documentation.`;
|
|
|
155
164
|
return { additionalContext: message };
|
|
156
165
|
}
|
|
157
166
|
};
|
|
158
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
167
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
159
168
|
api.tools.register({
|
|
160
169
|
name: "doc_sync_status",
|
|
161
170
|
description: "Reports doc-sync-guard state: tracked changed files, doc-write count, and warning count.",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/duplicate-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/duplicate-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAoM/C,QAAA,MAAM,MAAM,EAAE,MA8Qb,CAAC;eAEa,MAAM"}
|
|
@@ -1,60 +1,37 @@
|
|
|
1
1
|
// src/duplicate-code-detector/index.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
hookUnregister: null,
|
|
12
|
-
lastHookWarning: /* @__PURE__ */ new Map()
|
|
13
|
-
};
|
|
14
|
-
var DEFAULTS = {
|
|
15
|
-
enabled: false,
|
|
16
|
-
minLines: 8,
|
|
17
|
-
threshold: 0.8,
|
|
18
|
-
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
19
|
-
excludeDirs: ["node_modules", "dist", ".git", "coverage"],
|
|
20
|
-
maxFindings: 5
|
|
21
|
-
};
|
|
22
|
-
function readConfig(raw) {
|
|
23
|
-
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
24
|
-
const r = raw;
|
|
25
|
-
return {
|
|
26
|
-
enabled: r["enabled"] === true,
|
|
27
|
-
minLines: typeof r["minLines"] === "number" && r["minLines"] >= 2 && r["minLines"] <= 100 ? r["minLines"] : DEFAULTS.minLines,
|
|
28
|
-
threshold: typeof r["threshold"] === "number" && r["threshold"] > 0 && r["threshold"] <= 1 ? r["threshold"] : DEFAULTS.threshold,
|
|
29
|
-
extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
|
|
30
|
-
excludeDirs: Array.isArray(r["excludeDirs"]) ? r["excludeDirs"].filter((x) => typeof x === "string") : DEFAULTS.excludeDirs,
|
|
31
|
-
maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
|
|
32
|
-
};
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/runtime/index.ts
|
|
6
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
7
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
8
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
9
|
+
function hasLeadingDash(arg) {
|
|
10
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
33
11
|
}
|
|
34
|
-
function
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
const resolved = isAbsolute(
|
|
38
|
-
const rel = relative(
|
|
39
|
-
|
|
40
|
-
if (rel.startsWith("..")) return false;
|
|
41
|
-
if (isAbsolute(rel)) return false;
|
|
42
|
-
return true;
|
|
12
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
13
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
14
|
+
if (hasLeadingDash(candidate)) return false;
|
|
15
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
16
|
+
const rel = relative(projectRoot, resolved);
|
|
17
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
43
18
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
return exts.includes(ext);
|
|
19
|
+
function withinProject(p) {
|
|
20
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
47
21
|
}
|
|
48
|
-
|
|
22
|
+
var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
|
|
23
|
+
function collectSourceFiles(root, opts) {
|
|
49
24
|
const files = [];
|
|
50
25
|
if (!existsSync(root)) return files;
|
|
51
26
|
const s = statSync(root);
|
|
52
27
|
if (s.isFile()) {
|
|
53
|
-
if (matchesExtension(root,
|
|
28
|
+
if (matchesExtension(root, opts.extensions)) files.push(root);
|
|
54
29
|
return files;
|
|
55
30
|
}
|
|
56
31
|
if (!s.isDirectory()) return files;
|
|
57
|
-
|
|
32
|
+
const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
33
|
+
function walk(dir, depth) {
|
|
34
|
+
if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
|
|
58
35
|
let entries;
|
|
59
36
|
try {
|
|
60
37
|
entries = readdirSync(dir);
|
|
@@ -62,6 +39,7 @@ function collectSourceFiles(root, cfg) {
|
|
|
62
39
|
return;
|
|
63
40
|
}
|
|
64
41
|
for (const entry of entries) {
|
|
42
|
+
if (exclude.includes(entry)) continue;
|
|
65
43
|
const full = resolve(dir, entry);
|
|
66
44
|
let st;
|
|
67
45
|
try {
|
|
@@ -70,20 +48,55 @@ function collectSourceFiles(root, cfg) {
|
|
|
70
48
|
continue;
|
|
71
49
|
}
|
|
72
50
|
if (st.isDirectory()) {
|
|
73
|
-
|
|
74
|
-
} else if (st.isFile() && matchesExtension(full,
|
|
51
|
+
walk(full, depth + 1);
|
|
52
|
+
} else if (st.isFile() && matchesExtension(full, opts.extensions)) {
|
|
75
53
|
files.push(full);
|
|
76
54
|
}
|
|
77
55
|
}
|
|
78
56
|
}
|
|
79
|
-
walk(root);
|
|
57
|
+
walk(root, 0);
|
|
80
58
|
return files;
|
|
81
59
|
}
|
|
60
|
+
function matchesExtension(p, exts) {
|
|
61
|
+
return exts.includes(extname(p).toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/duplicate-code-detector/index.ts
|
|
65
|
+
var API_VERSION = "^0.1.10";
|
|
66
|
+
var state = {
|
|
67
|
+
scanCount: 0,
|
|
68
|
+
findingCount: 0,
|
|
69
|
+
hookInvocationCount: 0,
|
|
70
|
+
warningCount: 0,
|
|
71
|
+
errorCount: 0,
|
|
72
|
+
hookUnregister: null,
|
|
73
|
+
lastHookWarning: /* @__PURE__ */ new Map()
|
|
74
|
+
};
|
|
75
|
+
var DEFAULTS = {
|
|
76
|
+
enabled: false,
|
|
77
|
+
minLines: 8,
|
|
78
|
+
threshold: 0.8,
|
|
79
|
+
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
80
|
+
excludeDirs: ["node_modules", "dist", ".git", "coverage"],
|
|
81
|
+
maxFindings: 5
|
|
82
|
+
};
|
|
83
|
+
function readConfig(raw) {
|
|
84
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
85
|
+
const r = raw;
|
|
86
|
+
return {
|
|
87
|
+
enabled: r["enabled"] === true,
|
|
88
|
+
minLines: typeof r["minLines"] === "number" && r["minLines"] >= 2 && r["minLines"] <= 100 ? r["minLines"] : DEFAULTS.minLines,
|
|
89
|
+
threshold: typeof r["threshold"] === "number" && r["threshold"] > 0 && r["threshold"] <= 1 ? r["threshold"] : DEFAULTS.threshold,
|
|
90
|
+
extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
|
|
91
|
+
excludeDirs: Array.isArray(r["excludeDirs"]) ? r["excludeDirs"].filter((x) => typeof x === "string") : DEFAULTS.excludeDirs,
|
|
92
|
+
maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
|
|
93
|
+
};
|
|
94
|
+
}
|
|
82
95
|
function toPosix(p) {
|
|
83
96
|
return p.replace(/\\/g, "/");
|
|
84
97
|
}
|
|
85
98
|
function relativePath(p) {
|
|
86
|
-
return toPosix(
|
|
99
|
+
return toPosix(relative2(process.cwd(), p));
|
|
87
100
|
}
|
|
88
101
|
function removeInlineComments(line) {
|
|
89
102
|
return line.replace(/\/\/.*$/, "").replace(/\/\*[\s\S]*?\*\//, "");
|
|
@@ -141,8 +154,8 @@ function findDuplicates(files, minLines, maxFindings) {
|
|
|
141
154
|
}
|
|
142
155
|
function scanPath(rawPath, cfg) {
|
|
143
156
|
const root = process.cwd();
|
|
144
|
-
const resolved =
|
|
145
|
-
const filePaths = collectSourceFiles(resolved, cfg);
|
|
157
|
+
const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
|
|
158
|
+
const filePaths = collectSourceFiles(resolved, { extensions: cfg.extensions, excludeDirs: cfg.excludeDirs });
|
|
146
159
|
const files = /* @__PURE__ */ new Map();
|
|
147
160
|
for (const p of filePaths) {
|
|
148
161
|
try {
|
|
@@ -226,7 +239,7 @@ var plugin = {
|
|
|
226
239
|
const now = Date.now();
|
|
227
240
|
const lastWarning = state.lastHookWarning.get(sourcePath);
|
|
228
241
|
if (lastWarning !== void 0 && now - lastWarning < 6e4) return;
|
|
229
|
-
const changedFile =
|
|
242
|
+
const changedFile = resolve2(process.cwd(), sourcePath);
|
|
230
243
|
let content;
|
|
231
244
|
try {
|
|
232
245
|
content = readFileSync(changedFile, "utf-8");
|
|
@@ -236,10 +249,10 @@ var plugin = {
|
|
|
236
249
|
}
|
|
237
250
|
const changedWindows = extractWindows(changedFile, content, cfg.minLines);
|
|
238
251
|
if (changedWindows.length === 0) return;
|
|
239
|
-
const projectRoot =
|
|
252
|
+
const projectRoot = resolve2(process.cwd());
|
|
240
253
|
let otherFiles;
|
|
241
254
|
try {
|
|
242
|
-
const filePaths = collectSourceFiles(projectRoot, cfg).filter((p) => p !== changedFile);
|
|
255
|
+
const filePaths = collectSourceFiles(projectRoot, { extensions: cfg.extensions, excludeDirs: cfg.excludeDirs }).filter((p) => p !== changedFile);
|
|
243
256
|
otherFiles = /* @__PURE__ */ new Map();
|
|
244
257
|
for (const p of filePaths) {
|
|
245
258
|
try {
|
|
@@ -272,7 +285,7 @@ var plugin = {
|
|
|
272
285
|
contextAs: "separate"
|
|
273
286
|
};
|
|
274
287
|
};
|
|
275
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
288
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
276
289
|
api.tools.register({
|
|
277
290
|
name: "detect_duplicate_code",
|
|
278
291
|
description: "Scan source files for duplicated code blocks. Uses normalized-line fingerprinting to find identical multi-line blocks across files.",
|
|
@@ -302,7 +315,7 @@ var plugin = {
|
|
|
302
315
|
state.findingCount += result.findings.length;
|
|
303
316
|
return {
|
|
304
317
|
ok: true,
|
|
305
|
-
path: relativePath(
|
|
318
|
+
path: relativePath(resolve2(process.cwd(), rawPath)),
|
|
306
319
|
scannedFiles: result.scannedFiles,
|
|
307
320
|
minLines: cfg.minLines,
|
|
308
321
|
findings: result.findings
|
package/dist/error-lens.js
CHANGED
|
@@ -85,7 +85,7 @@ var plugin = {
|
|
|
85
85
|
version: "0.1.0",
|
|
86
86
|
description: "Distills failed command output into a compact digest (error line + project stack frames) and flags repeated failures",
|
|
87
87
|
apiVersion: "^0.1.10",
|
|
88
|
-
capabilities: { tools: true, hooks: true },
|
|
88
|
+
capabilities: { tools: true, hooks: true, llm: true },
|
|
89
89
|
defaultConfig: { ...DEFAULTS },
|
|
90
90
|
configSchema: {
|
|
91
91
|
type: "object",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/feature-flag-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/feature-flag-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AA0ID,QAAA,MAAM,MAAM,EAAE,MA+Mb,CAAC;eAEa,MAAM"}
|
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
// src/feature-flag-tracker/index.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/runtime/index.ts
|
|
6
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
7
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
8
|
+
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
9
|
+
function hasLeadingDash(arg) {
|
|
10
|
+
return arg.length > 0 && arg.startsWith("-");
|
|
11
|
+
}
|
|
12
|
+
function withinProjectPath(projectRoot, candidate) {
|
|
13
|
+
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
14
|
+
if (hasLeadingDash(candidate)) return false;
|
|
15
|
+
const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
|
|
16
|
+
const rel = relative(projectRoot, resolved);
|
|
17
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
18
|
+
}
|
|
19
|
+
function withinProject(p) {
|
|
20
|
+
return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
|
|
21
|
+
}
|
|
22
|
+
var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
|
|
23
|
+
function collectSourceFiles(root, opts) {
|
|
24
|
+
const files = [];
|
|
25
|
+
if (!existsSync(root)) return files;
|
|
26
|
+
const s = statSync(root);
|
|
27
|
+
if (s.isFile()) {
|
|
28
|
+
if (matchesExtension(root, opts.extensions)) files.push(root);
|
|
29
|
+
return files;
|
|
30
|
+
}
|
|
31
|
+
if (!s.isDirectory()) return files;
|
|
32
|
+
const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
33
|
+
function walk(dir, depth) {
|
|
34
|
+
if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
|
|
35
|
+
let entries;
|
|
36
|
+
try {
|
|
37
|
+
entries = readdirSync(dir);
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
if (exclude.includes(entry)) continue;
|
|
43
|
+
const full = resolve(dir, entry);
|
|
44
|
+
let st;
|
|
45
|
+
try {
|
|
46
|
+
st = statSync(full);
|
|
47
|
+
} catch {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (st.isDirectory()) {
|
|
51
|
+
walk(full, depth + 1);
|
|
52
|
+
} else if (st.isFile() && matchesExtension(full, opts.extensions)) {
|
|
53
|
+
files.push(full);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
walk(root, 0);
|
|
58
|
+
return files;
|
|
59
|
+
}
|
|
60
|
+
function matchesExtension(p, exts) {
|
|
61
|
+
return exts.includes(extname(p).toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/feature-flag-tracker/index.ts
|
|
4
65
|
var API_VERSION = "^0.1.10";
|
|
5
66
|
var state = {
|
|
6
67
|
scanCount: 0,
|
|
@@ -32,62 +93,14 @@ function readConfig(raw) {
|
|
|
32
93
|
maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
|
|
33
94
|
};
|
|
34
95
|
}
|
|
35
|
-
function withinProject(p) {
|
|
36
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
37
|
-
const root = process.cwd();
|
|
38
|
-
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
39
|
-
const rel = relative(root, resolved);
|
|
40
|
-
if (rel === "" || rel === ".") return true;
|
|
41
|
-
if (rel.startsWith("..")) return false;
|
|
42
|
-
if (isAbsolute(rel)) return false;
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
96
|
function normalizeExtensions(exts) {
|
|
46
97
|
return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
|
|
47
98
|
}
|
|
48
|
-
function matchesExtension(p, exts) {
|
|
49
|
-
return exts.includes(extname(p).toLowerCase());
|
|
50
|
-
}
|
|
51
|
-
function collectSourceFiles(root, exts) {
|
|
52
|
-
const files = [];
|
|
53
|
-
if (!existsSync(root)) return files;
|
|
54
|
-
const s = statSync(root);
|
|
55
|
-
if (s.isFile()) {
|
|
56
|
-
if (matchesExtension(root, exts)) files.push(root);
|
|
57
|
-
return files;
|
|
58
|
-
}
|
|
59
|
-
if (!s.isDirectory()) return files;
|
|
60
|
-
function walk(dir) {
|
|
61
|
-
let entries;
|
|
62
|
-
try {
|
|
63
|
-
entries = readdirSync(dir);
|
|
64
|
-
} catch {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
for (const entry of entries) {
|
|
68
|
-
if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
|
|
69
|
-
const full = resolve(dir, entry);
|
|
70
|
-
let st;
|
|
71
|
-
try {
|
|
72
|
-
st = statSync(full);
|
|
73
|
-
} catch {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (st.isDirectory()) {
|
|
77
|
-
walk(full);
|
|
78
|
-
} else if (st.isFile() && matchesExtension(full, exts)) {
|
|
79
|
-
files.push(full);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
walk(root);
|
|
84
|
-
return files;
|
|
85
|
-
}
|
|
86
99
|
function toPosix(p) {
|
|
87
100
|
return p.replace(/\\/g, "/");
|
|
88
101
|
}
|
|
89
102
|
function relativePath(p) {
|
|
90
|
-
return toPosix(
|
|
103
|
+
return toPosix(relative2(process.cwd(), p));
|
|
91
104
|
}
|
|
92
105
|
function compilePatterns(patterns) {
|
|
93
106
|
const sources = [...DEFAULT_PATTERNS, ...patterns];
|
|
@@ -117,9 +130,9 @@ function scanFile(filePath, content, patterns, maxFindings) {
|
|
|
117
130
|
}
|
|
118
131
|
function scanPath(rawPath, cfg) {
|
|
119
132
|
const root = process.cwd();
|
|
120
|
-
const resolved =
|
|
133
|
+
const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
|
|
121
134
|
const exts = normalizeExtensions(cfg.extensions);
|
|
122
|
-
const files = collectSourceFiles(resolved, exts);
|
|
135
|
+
const files = collectSourceFiles(resolved, { extensions: exts });
|
|
123
136
|
const patterns = compilePatterns(cfg.patterns);
|
|
124
137
|
const usages = [];
|
|
125
138
|
for (const p of files) {
|
|
@@ -188,7 +201,7 @@ var plugin = {
|
|
|
188
201
|
const exts = normalizeExtensions(cfg.extensions);
|
|
189
202
|
if (!matchesExtension(sourcePath, exts)) return;
|
|
190
203
|
state.hookInvocationCount += 1;
|
|
191
|
-
const resolved =
|
|
204
|
+
const resolved = resolve2(process.cwd(), sourcePath);
|
|
192
205
|
let content;
|
|
193
206
|
try {
|
|
194
207
|
content = readFileSync(resolved, "utf-8");
|
|
@@ -207,7 +220,7 @@ var plugin = {
|
|
|
207
220
|
Make sure flag behavior is intentional and consider updating flag inventory/docs.`
|
|
208
221
|
};
|
|
209
222
|
};
|
|
210
|
-
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
223
|
+
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
|
|
211
224
|
api.tools.register({
|
|
212
225
|
name: "scan_feature_flags",
|
|
213
226
|
description: "Scan source files for feature-flag-like expressions (isFeatureEnabled, featureFlags.*, useFeatureFlag, flags.*, plus custom patterns).",
|
|
@@ -237,7 +250,7 @@ Make sure flag behavior is intentional and consider updating flag inventory/docs
|
|
|
237
250
|
state.flagCount += result.usages.length;
|
|
238
251
|
return {
|
|
239
252
|
ok: true,
|
|
240
|
-
path: relativePath(
|
|
253
|
+
path: relativePath(resolve2(process.cwd(), rawPath)),
|
|
241
254
|
scannedFiles: result.scannedFiles,
|
|
242
255
|
usages: result.usages
|
|
243
256
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file-watcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2D/C,QAAA,MAAM,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file-watcher/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2D/C,QAAA,MAAM,MAAM,EAAE,MAsYb,CAAC;eAEa,MAAM"}
|