@wrongstack/plugins 0.281.3 → 0.282.1
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 +30 -4
- package/dist/accessibility-auditor.d.ts +40 -0
- package/dist/accessibility-auditor.js +411 -0
- package/dist/agent-handoff.d.ts +37 -0
- package/dist/agent-handoff.js +298 -0
- package/dist/api-compatibility-gate.d.ts +38 -0
- package/dist/api-compatibility-gate.js +357 -0
- package/dist/auto-escalate.d.ts +1 -1
- package/dist/auto-i18n-extractor.d.ts +36 -0
- package/dist/auto-i18n-extractor.js +335 -0
- package/dist/branch-guard.d.ts +6 -5
- package/dist/branch-guard.js +54 -4
- package/dist/checkpoint.js +18 -0
- package/dist/code-metrics.d.ts +31 -0
- package/dist/code-metrics.js +338 -0
- package/dist/commit-validator.js +67 -12
- package/dist/context-pins.js +4 -4
- package/dist/cost-tracker.d.ts +1 -1
- package/dist/cost-tracker.js +58 -19
- package/dist/cron.js +18 -8
- package/dist/dead-code-detector.d.ts +34 -0
- package/dist/dead-code-detector.js +354 -0
- package/dist/dep-guard.js +47 -6
- package/dist/dependency-vulnerability-gate.d.ts +35 -0
- package/dist/dependency-vulnerability-gate.js +308 -0
- package/dist/diff-summary.js +97 -10
- package/dist/doc-sync-guard.d.ts +33 -0
- package/dist/doc-sync-guard.js +223 -0
- package/dist/duplicate-code-detector.d.ts +33 -0
- package/dist/duplicate-code-detector.js +384 -0
- package/dist/feature-flag-tracker.d.ts +38 -0
- package/dist/feature-flag-tracker.js +316 -0
- package/dist/file-watcher.js +85 -36
- package/dist/format-on-save.js +99 -18
- package/dist/import-organizer.js +73 -14
- package/dist/index.d.ts +27 -0
- package/dist/index.js +11205 -2106
- package/dist/interface-contract-guard.d.ts +37 -0
- package/dist/interface-contract-guard.js +302 -0
- package/dist/knowledge-graph.d.ts +45 -0
- package/dist/knowledge-graph.js +325 -0
- package/dist/license-audit-gate.d.ts +34 -0
- package/dist/license-audit-gate.js +260 -0
- package/dist/llm-cache.js +5 -0
- package/dist/loop-breaker.d.ts +0 -38
- package/dist/loop-breaker.js +209 -8
- package/dist/migration-planner.d.ts +30 -0
- package/dist/migration-planner.js +349 -0
- package/dist/model-router.js +5 -0
- package/dist/performance-regression-gate.d.ts +33 -0
- package/dist/performance-regression-gate.js +315 -0
- package/dist/plugin-stack-observer.d.ts +35 -0
- package/dist/plugin-stack-observer.js +138 -0
- package/dist/pr-drafter.d.ts +35 -0
- package/dist/pr-drafter.js +334 -0
- package/dist/prompt-firewall.js +5 -0
- package/dist/refactor-suggester.d.ts +38 -0
- package/dist/refactor-suggester.js +382 -0
- package/dist/release-notes-generator.d.ts +27 -0
- package/dist/release-notes-generator.js +209 -0
- package/dist/schema-evolution-guard.d.ts +42 -0
- package/dist/schema-evolution-guard.js +319 -0
- package/dist/security-hotspot-scanner.d.ts +30 -0
- package/dist/security-hotspot-scanner.js +402 -0
- package/dist/semantic-search-indexer.d.ts +38 -0
- package/dist/semantic-search-indexer.js +436 -0
- package/dist/shell-check.js +38 -3
- package/dist/smart-rename.d.ts +26 -0
- package/dist/smart-rename.js +170 -0
- package/dist/spec-linker.js +273 -133
- package/dist/test-coverage-gate.d.ts +37 -0
- package/dist/test-coverage-gate.js +263 -0
- package/dist/test-flake-detector.d.ts +27 -0
- package/dist/test-flake-detector.js +274 -0
- package/dist/test-generator.d.ts +32 -0
- package/dist/test-generator.js +243 -0
- package/dist/test-runner-gate.js +154 -22
- package/dist/todo-listener.d.ts +2 -2
- package/dist/todo-listener.js +5 -5
- package/dist/token-throttle.js +5 -0
- package/dist/type-gate.d.ts +37 -0
- package/dist/type-gate.js +311 -0
- package/package.json +112 -4
- package/LICENSE +0 -21
package/dist/format-on-save.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
1
|
+
import { execSync, execFileSync } from 'child_process';
|
|
2
2
|
import { existsSync, statSync } from 'fs';
|
|
3
|
+
import { isAbsolute, resolve, relative } from 'path';
|
|
3
4
|
|
|
4
5
|
// src/format-on-save/index.ts
|
|
5
6
|
var API_VERSION = "^0.1.10";
|
|
7
|
+
function withinProject(p) {
|
|
8
|
+
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
9
|
+
const root = process.cwd();
|
|
10
|
+
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
11
|
+
const rel = relative(root, resolved);
|
|
12
|
+
if (rel === "" || rel === ".") return true;
|
|
13
|
+
if (rel.startsWith("..")) return false;
|
|
14
|
+
if (isAbsolute(rel)) return false;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
6
17
|
var state = {
|
|
7
18
|
invocationCount: 0,
|
|
8
19
|
/** Times formatting was applied (file changed). */
|
|
@@ -11,24 +22,57 @@ var state = {
|
|
|
11
22
|
cleanCount: 0,
|
|
12
23
|
/** Times biome failed (not installed, timeout, parse error). */
|
|
13
24
|
errorCount: 0,
|
|
25
|
+
/** Times the format pass was skipped because import-organizer
|
|
26
|
+
* had already covered the path within the TTL window. */
|
|
27
|
+
coveredSkipCount: 0,
|
|
14
28
|
/** Hook handle for teardown. */
|
|
15
29
|
hookUnregister: null,
|
|
30
|
+
/** Cross-plugin event listener handle for teardown. */
|
|
31
|
+
patternUnregister: null,
|
|
16
32
|
/** Last format result — surfaced by health() + status tool. */
|
|
17
33
|
lastResult: null
|
|
18
34
|
};
|
|
19
35
|
var DEFAULTS = {
|
|
20
36
|
enabled: true,
|
|
21
|
-
timeoutMs: 5e3
|
|
37
|
+
timeoutMs: 5e3,
|
|
38
|
+
skipWhenCoveredBy: true,
|
|
39
|
+
skipTtlMs: 3e4
|
|
22
40
|
};
|
|
23
41
|
function readConfig(raw) {
|
|
24
42
|
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
25
43
|
const r = raw;
|
|
26
44
|
return {
|
|
27
45
|
enabled: r["enabled"] !== false,
|
|
28
|
-
timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs
|
|
46
|
+
timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs,
|
|
47
|
+
skipWhenCoveredBy: r["skipWhenCoveredBy"] !== false,
|
|
48
|
+
skipTtlMs: typeof r["skipTtlMs"] === "number" && r["skipTtlMs"] >= 0 ? r["skipTtlMs"] : DEFAULTS.skipTtlMs
|
|
29
49
|
};
|
|
30
50
|
}
|
|
51
|
+
var recentlyCovered = /* @__PURE__ */ new Map();
|
|
52
|
+
function clearRegistrations() {
|
|
53
|
+
if (state.hookUnregister) {
|
|
54
|
+
try {
|
|
55
|
+
state.hookUnregister();
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
state.hookUnregister = null;
|
|
59
|
+
}
|
|
60
|
+
if (state.patternUnregister) {
|
|
61
|
+
try {
|
|
62
|
+
state.patternUnregister();
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
state.patternUnregister = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function evictExpired(ttlMs) {
|
|
69
|
+
const cutoff = Date.now() - ttlMs;
|
|
70
|
+
for (const [path, ts] of recentlyCovered) {
|
|
71
|
+
if (ts < cutoff) recentlyCovered.delete(path);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
31
74
|
function formatFile(filePath, timeoutMs) {
|
|
75
|
+
if (!withinProject(filePath)) return null;
|
|
32
76
|
if (!existsSync(filePath)) return null;
|
|
33
77
|
let bytesBefore;
|
|
34
78
|
try {
|
|
@@ -37,7 +81,7 @@ function formatFile(filePath, timeoutMs) {
|
|
|
37
81
|
return null;
|
|
38
82
|
}
|
|
39
83
|
try {
|
|
40
|
-
|
|
84
|
+
execFileSync("npx", ["biome", "format", "--write", filePath], {
|
|
41
85
|
encoding: "utf-8",
|
|
42
86
|
timeout: timeoutMs,
|
|
43
87
|
cwd: process.cwd(),
|
|
@@ -57,7 +101,7 @@ function formatFile(filePath, timeoutMs) {
|
|
|
57
101
|
return { changed: true, bytesBefore, bytesAfter };
|
|
58
102
|
}
|
|
59
103
|
try {
|
|
60
|
-
|
|
104
|
+
execFileSync("npx", ["biome", "format", filePath], {
|
|
61
105
|
encoding: "utf-8",
|
|
62
106
|
timeout: timeoutMs,
|
|
63
107
|
cwd: process.cwd(),
|
|
@@ -88,16 +132,29 @@ var plugin = {
|
|
|
88
132
|
minimum: 1e3,
|
|
89
133
|
default: 5e3,
|
|
90
134
|
description: "Biome format process timeout in milliseconds."
|
|
135
|
+
},
|
|
136
|
+
skipWhenCoveredBy: {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
default: true,
|
|
139
|
+
description: "Skip the format pass when another plugin (e.g. import-organizer) just touched the same path. Saves one biome invocation per write/edit when both plugins are enabled."
|
|
140
|
+
},
|
|
141
|
+
skipTtlMs: {
|
|
142
|
+
type: "number",
|
|
143
|
+
minimum: 0,
|
|
144
|
+
default: 3e4,
|
|
145
|
+
description: "How long (ms) to remember a path covered by another plugin. 0 disables the memory."
|
|
91
146
|
}
|
|
92
147
|
}
|
|
93
148
|
},
|
|
94
149
|
setup(api) {
|
|
150
|
+
clearRegistrations();
|
|
95
151
|
state.invocationCount = 0;
|
|
96
152
|
state.formattedCount = 0;
|
|
97
153
|
state.cleanCount = 0;
|
|
98
154
|
state.errorCount = 0;
|
|
99
|
-
state.
|
|
155
|
+
state.coveredSkipCount = 0;
|
|
100
156
|
state.lastResult = null;
|
|
157
|
+
recentlyCovered.clear();
|
|
101
158
|
const cfg = readConfig(api.config.extensions?.["format-on-save"]);
|
|
102
159
|
let biomeAvailable = false;
|
|
103
160
|
try {
|
|
@@ -120,6 +177,20 @@ var plugin = {
|
|
|
120
177
|
const inp = input.toolInput ?? {};
|
|
121
178
|
const filePath = inp["path"];
|
|
122
179
|
if (!filePath || typeof filePath !== "string") return;
|
|
180
|
+
if (cfg.skipWhenCoveredBy && cfg.skipTtlMs > 0) {
|
|
181
|
+
evictExpired(cfg.skipTtlMs);
|
|
182
|
+
if (recentlyCovered.has(filePath)) {
|
|
183
|
+
state.coveredSkipCount = (state.coveredSkipCount ?? 0) + 1;
|
|
184
|
+
recentlyCovered.delete(filePath);
|
|
185
|
+
api.log.info(
|
|
186
|
+
`format-on-save: skipped ${filePath} \u2014 already formatted by import-organizer`,
|
|
187
|
+
{
|
|
188
|
+
tool: toolName
|
|
189
|
+
}
|
|
190
|
+
);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
123
194
|
state.invocationCount += 1;
|
|
124
195
|
const result = formatFile(filePath, cfg.timeoutMs);
|
|
125
196
|
if (!result) {
|
|
@@ -150,9 +221,18 @@ var plugin = {
|
|
|
150
221
|
return;
|
|
151
222
|
};
|
|
152
223
|
state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
|
|
224
|
+
state.patternUnregister = api.onPattern(
|
|
225
|
+
"import-organizer:done",
|
|
226
|
+
(_eventName, payload) => {
|
|
227
|
+
const p = payload ?? {};
|
|
228
|
+
if (typeof p.path !== "string" || p.path.length === 0) return;
|
|
229
|
+
recentlyCovered.set(p.path, Date.now());
|
|
230
|
+
api.metrics.counter("covered_notice");
|
|
231
|
+
}
|
|
232
|
+
);
|
|
153
233
|
api.tools.register({
|
|
154
234
|
name: "format_on_save_status",
|
|
155
|
-
description: "Reports format-on-save state: biome availability, and per-session formatted/clean/error counters.",
|
|
235
|
+
description: "Reports format-on-save state: biome availability, and per-session formatted/clean/error/skipped counters.",
|
|
156
236
|
inputSchema: { type: "object", properties: {} },
|
|
157
237
|
permission: "auto",
|
|
158
238
|
category: "Code Quality",
|
|
@@ -163,11 +243,14 @@ var plugin = {
|
|
|
163
243
|
enabled: cfg.enabled,
|
|
164
244
|
biomeAvailable,
|
|
165
245
|
timeoutMs: cfg.timeoutMs,
|
|
246
|
+
skipWhenCoveredBy: cfg.skipWhenCoveredBy,
|
|
247
|
+
skipTtlMs: cfg.skipTtlMs,
|
|
166
248
|
counters: {
|
|
167
249
|
invocations: state.invocationCount,
|
|
168
250
|
formatted: state.formattedCount,
|
|
169
251
|
clean: state.cleanCount,
|
|
170
|
-
errors: state.errorCount
|
|
252
|
+
errors: state.errorCount,
|
|
253
|
+
coveredSkips: state.coveredSkipCount
|
|
171
254
|
},
|
|
172
255
|
lastResult: state.lastResult
|
|
173
256
|
};
|
|
@@ -180,35 +263,33 @@ var plugin = {
|
|
|
180
263
|
});
|
|
181
264
|
},
|
|
182
265
|
teardown(api) {
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
state.hookUnregister();
|
|
186
|
-
} catch {
|
|
187
|
-
}
|
|
188
|
-
state.hookUnregister = null;
|
|
189
|
-
}
|
|
266
|
+
clearRegistrations();
|
|
190
267
|
const final = {
|
|
191
268
|
invocations: state.invocationCount,
|
|
192
269
|
formatted: state.formattedCount,
|
|
193
270
|
clean: state.cleanCount,
|
|
194
|
-
errors: state.errorCount
|
|
271
|
+
errors: state.errorCount,
|
|
272
|
+
coveredSkips: state.coveredSkipCount
|
|
195
273
|
};
|
|
196
274
|
state.invocationCount = 0;
|
|
197
275
|
state.formattedCount = 0;
|
|
198
276
|
state.cleanCount = 0;
|
|
199
277
|
state.errorCount = 0;
|
|
278
|
+
state.coveredSkipCount = 0;
|
|
200
279
|
state.lastResult = null;
|
|
280
|
+
recentlyCovered.clear();
|
|
201
281
|
api.log.info("format-on-save: teardown complete", { final });
|
|
202
282
|
},
|
|
203
283
|
async health() {
|
|
204
284
|
return {
|
|
205
285
|
ok: true,
|
|
206
|
-
message: state.lastResult === null ? `format-on-save: ${state.invocationCount} invocation(s), ${state.formattedCount} formatted` : state.lastResult.changed ? `format-on-save: last formatted ${state.lastResult.path} (${state.lastResult.tool}) at ${state.lastResult.when}` : `format-on-save: last check on ${state.lastResult.path} was already clean`,
|
|
286
|
+
message: state.lastResult === null ? `format-on-save: ${state.invocationCount} invocation(s), ${state.formattedCount} formatted, ${state.coveredSkipCount} covered-skipped` : state.lastResult.changed ? `format-on-save: last formatted ${state.lastResult.path} (${state.lastResult.tool}) at ${state.lastResult.when}` : `format-on-save: last check on ${state.lastResult.path} was already clean`,
|
|
207
287
|
counters: {
|
|
208
288
|
invocations: state.invocationCount,
|
|
209
289
|
formatted: state.formattedCount,
|
|
210
290
|
clean: state.cleanCount,
|
|
211
|
-
errors: state.errorCount
|
|
291
|
+
errors: state.errorCount,
|
|
292
|
+
coveredSkips: state.coveredSkipCount
|
|
212
293
|
},
|
|
213
294
|
lastResult: state.lastResult
|
|
214
295
|
};
|
package/dist/import-organizer.js
CHANGED
|
@@ -1,7 +1,48 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { existsSync, statSync } from 'fs';
|
|
3
|
+
import { isAbsolute, resolve, relative, basename } from 'path';
|
|
3
4
|
|
|
4
5
|
// src/import-organizer/index.ts
|
|
6
|
+
function withinProject(p) {
|
|
7
|
+
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
8
|
+
const root = process.cwd();
|
|
9
|
+
const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
|
|
10
|
+
const rel = relative(root, resolved);
|
|
11
|
+
if (rel === "" || rel === ".") return true;
|
|
12
|
+
if (rel.startsWith("..")) return false;
|
|
13
|
+
if (isAbsolute(rel)) return false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
var ALLOWED_FIRST_TOKENS = /* @__PURE__ */ new Set([
|
|
17
|
+
"npx",
|
|
18
|
+
"pnpm",
|
|
19
|
+
"npm",
|
|
20
|
+
"yarn",
|
|
21
|
+
"biome",
|
|
22
|
+
"@biomejs/biome",
|
|
23
|
+
"eslint",
|
|
24
|
+
"node"
|
|
25
|
+
// Recognise a fully-qualified project-local path like
|
|
26
|
+
// "./node_modules/.bin/biome"; basename check kicks in below.
|
|
27
|
+
]);
|
|
28
|
+
function resolveAllowedCommand(command) {
|
|
29
|
+
const tokens = command.split(/\s+/).filter(Boolean);
|
|
30
|
+
if (tokens.length === 0) return null;
|
|
31
|
+
const head = tokens[0];
|
|
32
|
+
if (ALLOWED_FIRST_TOKENS.has(head)) {
|
|
33
|
+
return { cmd: head, args: tokens.slice(1) };
|
|
34
|
+
}
|
|
35
|
+
if (isAbsolute(head)) {
|
|
36
|
+
if (!withinProject(head)) return null;
|
|
37
|
+
const base = basename(head);
|
|
38
|
+
if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
|
|
39
|
+
}
|
|
40
|
+
if (!isAbsolute(head) && withinProject(head)) {
|
|
41
|
+
const base = basename(head);
|
|
42
|
+
if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
5
46
|
var API_VERSION = "^0.1.10";
|
|
6
47
|
var state = {
|
|
7
48
|
invocationCount: 0,
|
|
@@ -17,7 +58,8 @@ var DEFAULTS = {
|
|
|
17
58
|
enabled: true,
|
|
18
59
|
command: "npx @biomejs/biome check --write --unsafe",
|
|
19
60
|
fallbackCommand: "npx eslint --fix",
|
|
20
|
-
timeoutMs: 1e4
|
|
61
|
+
timeoutMs: 1e4,
|
|
62
|
+
notifyFormatOnSave: true
|
|
21
63
|
};
|
|
22
64
|
function readConfig(raw) {
|
|
23
65
|
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
@@ -26,11 +68,12 @@ function readConfig(raw) {
|
|
|
26
68
|
enabled: r["enabled"] !== false,
|
|
27
69
|
command: typeof r["command"] === "string" && r["command"].length > 0 ? r["command"] : DEFAULTS.command,
|
|
28
70
|
fallbackCommand: typeof r["fallbackCommand"] === "string" && r["fallbackCommand"].length > 0 ? r["fallbackCommand"] : DEFAULTS.fallbackCommand,
|
|
29
|
-
timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs
|
|
71
|
+
timeoutMs: typeof r["timeoutMs"] === "number" && r["timeoutMs"] > 0 ? r["timeoutMs"] : DEFAULTS.timeoutMs,
|
|
72
|
+
notifyFormatOnSave: r["notifyFormatOnSave"] !== false
|
|
30
73
|
};
|
|
31
74
|
}
|
|
32
75
|
function runCommand(command, args, timeoutMs, cwd) {
|
|
33
|
-
return new Promise((
|
|
76
|
+
return new Promise((resolve2) => {
|
|
34
77
|
let timedOut = false;
|
|
35
78
|
const stdoutChunks = [];
|
|
36
79
|
const stderrChunks = [];
|
|
@@ -42,17 +85,17 @@ function runCommand(command, args, timeoutMs, cwd) {
|
|
|
42
85
|
signal: AbortSignal.timeout(timeoutMs)
|
|
43
86
|
});
|
|
44
87
|
} catch {
|
|
45
|
-
|
|
88
|
+
resolve2({ code: 127, stdout: "", stderr: "", timedOut: false });
|
|
46
89
|
return;
|
|
47
90
|
}
|
|
48
91
|
child.stdout?.on("data", (c) => stdoutChunks.push(c));
|
|
49
92
|
child.stderr?.on("data", (c) => stderrChunks.push(c));
|
|
50
93
|
child.on("error", () => {
|
|
51
|
-
|
|
94
|
+
resolve2({ code: 127, stdout: "", stderr: "", timedOut: false });
|
|
52
95
|
});
|
|
53
96
|
child.on("close", (code) => {
|
|
54
97
|
if (timedOut) return;
|
|
55
|
-
|
|
98
|
+
resolve2({
|
|
56
99
|
code,
|
|
57
100
|
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
58
101
|
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
@@ -61,11 +104,12 @@ function runCommand(command, args, timeoutMs, cwd) {
|
|
|
61
104
|
});
|
|
62
105
|
child.on("abort", () => {
|
|
63
106
|
timedOut = true;
|
|
64
|
-
|
|
107
|
+
resolve2({ code: null, stdout: "", stderr: "", timedOut: true });
|
|
65
108
|
});
|
|
66
109
|
});
|
|
67
110
|
}
|
|
68
111
|
async function organizeImports(filePath, cfg, cwd) {
|
|
112
|
+
if (!withinProject(filePath)) return null;
|
|
69
113
|
if (!existsSync(filePath)) return null;
|
|
70
114
|
let bytesBefore;
|
|
71
115
|
try {
|
|
@@ -73,15 +117,15 @@ async function organizeImports(filePath, cfg, cwd) {
|
|
|
73
117
|
} catch {
|
|
74
118
|
return null;
|
|
75
119
|
}
|
|
76
|
-
const primary = cfg.command
|
|
77
|
-
if (primary
|
|
78
|
-
const [primaryCmd, ...primaryArgs] = primary;
|
|
120
|
+
const primary = resolveAllowedCommand(cfg.command);
|
|
121
|
+
if (!primary) return null;
|
|
122
|
+
const [primaryCmd, ...primaryArgs] = [primary.cmd, ...primary.args];
|
|
79
123
|
let result = await runCommand(primaryCmd, [...primaryArgs, filePath], cfg.timeoutMs, cwd);
|
|
80
124
|
let usedCommand = cfg.command;
|
|
81
125
|
if (result.code === 127 && cfg.fallbackCommand) {
|
|
82
|
-
const fallback = cfg.fallbackCommand
|
|
83
|
-
if (fallback
|
|
84
|
-
const [fbCmd, ...fbArgs] = fallback;
|
|
126
|
+
const fallback = resolveAllowedCommand(cfg.fallbackCommand);
|
|
127
|
+
if (fallback) {
|
|
128
|
+
const [fbCmd, ...fbArgs] = [fallback.cmd, ...fallback.args];
|
|
85
129
|
result = await runCommand(fbCmd, [...fbArgs, filePath], cfg.timeoutMs, cwd);
|
|
86
130
|
usedCommand = cfg.fallbackCommand;
|
|
87
131
|
}
|
|
@@ -132,6 +176,11 @@ var plugin = {
|
|
|
132
176
|
minimum: 1e3,
|
|
133
177
|
default: 1e4,
|
|
134
178
|
description: "Per-invocation linter timeout in milliseconds."
|
|
179
|
+
},
|
|
180
|
+
notifyFormatOnSave: {
|
|
181
|
+
type: "boolean",
|
|
182
|
+
default: true,
|
|
183
|
+
description: "Emit `import-organizer:done` after each successful run so `format-on-save` can skip its redundant `biome format --write` pass on the same file. Set false to keep both running unconditionally."
|
|
135
184
|
}
|
|
136
185
|
}
|
|
137
186
|
},
|
|
@@ -160,13 +209,23 @@ var plugin = {
|
|
|
160
209
|
if (!state.linterAvailable) {
|
|
161
210
|
state.linterAvailable = false;
|
|
162
211
|
state.probeComplete = true;
|
|
163
|
-
api.log.warn(
|
|
212
|
+
api.log.warn(
|
|
213
|
+
"import-organizer: no linter available \u2014 hook will be a no-op for the rest of the session"
|
|
214
|
+
);
|
|
164
215
|
}
|
|
165
216
|
state.errorCount += 1;
|
|
166
217
|
return;
|
|
167
218
|
}
|
|
168
219
|
state.linterAvailable = true;
|
|
169
220
|
state.probeComplete = true;
|
|
221
|
+
if (cfg.notifyFormatOnSave) {
|
|
222
|
+
api.emitCustom("import-organizer:done", {
|
|
223
|
+
path: filePath,
|
|
224
|
+
changed: result.changed,
|
|
225
|
+
command: result.command,
|
|
226
|
+
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
227
|
+
});
|
|
228
|
+
}
|
|
170
229
|
state.lastResult = {
|
|
171
230
|
path: filePath,
|
|
172
231
|
tool: toolName,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { default as agentHandoffPlugin } from './agent-handoff.js';
|
|
1
2
|
export { default as autoDocPlugin } from './auto-doc.js';
|
|
2
3
|
export { default as autoEscalatePlugin } from './auto-escalate.js';
|
|
3
4
|
export { default as branchGuardPlugin } from './branch-guard.js';
|
|
@@ -16,12 +17,15 @@ export { default as formatOnSavePlugin } from './format-on-save.js';
|
|
|
16
17
|
export { default as gitAutocommitPlugin } from './git-autocommit.js';
|
|
17
18
|
export { default as importOrganizerPlugin } from './import-organizer.js';
|
|
18
19
|
export { default as injectionShieldPlugin } from './injection-shield.js';
|
|
20
|
+
export { default as knowledgeGraphPlugin } from './knowledge-graph.js';
|
|
19
21
|
export { default as lintGatePlugin } from './lint-gate.js';
|
|
20
22
|
export { default as llmCachePlugin } from './llm-cache.js';
|
|
21
23
|
export { default as loopBreakerPlugin } from './loop-breaker.js';
|
|
22
24
|
export { default as modelRouterPlugin } from './model-router.js';
|
|
23
25
|
export { default as notifyHubPlugin } from './notify-hub.js';
|
|
24
26
|
export { default as pathGuardPlugin } from './path-guard.js';
|
|
27
|
+
export { default as pluginStackObserverPlugin } from './plugin-stack-observer.js';
|
|
28
|
+
export { default as prDrafterPlugin } from './pr-drafter.js';
|
|
25
29
|
export { default as promptFirewallPlugin } from './prompt-firewall.js';
|
|
26
30
|
export { default as secretScannerPlugin } from './secret-scanner.js';
|
|
27
31
|
export { default as semverBumpPlugin } from './semver-bump.js';
|
|
@@ -29,7 +33,30 @@ export { default as sessionRecapPlugin } from './session-recap.js';
|
|
|
29
33
|
export { default as shellCheckPlugin } from './shell-check.js';
|
|
30
34
|
export { default as specLinkerPlugin } from './spec-linker.js';
|
|
31
35
|
export { default as templateEnginePlugin } from './template-engine.js';
|
|
36
|
+
export { default as testCoverageGatePlugin } from './test-coverage-gate.js';
|
|
32
37
|
export { default as testRunnerGatePlugin } from './test-runner-gate.js';
|
|
38
|
+
export { default as typeGatePlugin } from './type-gate.js';
|
|
39
|
+
export { default as accessibilityAuditorPlugin } from './accessibility-auditor.js';
|
|
40
|
+
export { default as apiCompatibilityGatePlugin } from './api-compatibility-gate.js';
|
|
41
|
+
export { default as autoI18nExtractorPlugin } from './auto-i18n-extractor.js';
|
|
42
|
+
export { default as codeMetricsPlugin } from './code-metrics.js';
|
|
43
|
+
export { default as deadCodeDetectorPlugin } from './dead-code-detector.js';
|
|
44
|
+
export { default as dependencyVulnerabilityGatePlugin } from './dependency-vulnerability-gate.js';
|
|
45
|
+
export { default as docSyncGuardPlugin } from './doc-sync-guard.js';
|
|
46
|
+
export { default as duplicateCodeDetectorPlugin } from './duplicate-code-detector.js';
|
|
47
|
+
export { default as featureFlagTrackerPlugin } from './feature-flag-tracker.js';
|
|
48
|
+
export { default as interfaceContractGuardPlugin } from './interface-contract-guard.js';
|
|
49
|
+
export { default as licenseAuditGatePlugin } from './license-audit-gate.js';
|
|
50
|
+
export { default as migrationPlannerPlugin } from './migration-planner.js';
|
|
51
|
+
export { default as performanceRegressionGatePlugin } from './performance-regression-gate.js';
|
|
52
|
+
export { default as refactorSuggesterPlugin } from './refactor-suggester.js';
|
|
53
|
+
export { default as releaseNotesGeneratorPlugin } from './release-notes-generator.js';
|
|
54
|
+
export { default as schemaEvolutionGuardPlugin } from './schema-evolution-guard.js';
|
|
55
|
+
export { default as securityHotspotScannerPlugin } from './security-hotspot-scanner.js';
|
|
56
|
+
export { default as semanticSearchIndexerPlugin } from './semantic-search-indexer.js';
|
|
57
|
+
export { default as smartRenamePlugin } from './smart-rename.js';
|
|
58
|
+
export { default as testFlakeDetectorPlugin } from './test-flake-detector.js';
|
|
59
|
+
export { default as testGeneratorPlugin } from './test-generator.js';
|
|
33
60
|
export { default as todoListenerPlugin } from './todo-listener.js';
|
|
34
61
|
export { default as todoTrackerPlugin } from './todo-tracker.js';
|
|
35
62
|
export { default as tokenBudgetPlugin } from './token-budget.js';
|