@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.
Files changed (84) hide show
  1. package/README.md +30 -4
  2. package/dist/accessibility-auditor.d.ts +40 -0
  3. package/dist/accessibility-auditor.js +411 -0
  4. package/dist/agent-handoff.d.ts +37 -0
  5. package/dist/agent-handoff.js +298 -0
  6. package/dist/api-compatibility-gate.d.ts +38 -0
  7. package/dist/api-compatibility-gate.js +357 -0
  8. package/dist/auto-escalate.d.ts +1 -1
  9. package/dist/auto-i18n-extractor.d.ts +36 -0
  10. package/dist/auto-i18n-extractor.js +335 -0
  11. package/dist/branch-guard.d.ts +6 -5
  12. package/dist/branch-guard.js +54 -4
  13. package/dist/checkpoint.js +18 -0
  14. package/dist/code-metrics.d.ts +31 -0
  15. package/dist/code-metrics.js +338 -0
  16. package/dist/commit-validator.js +67 -12
  17. package/dist/context-pins.js +4 -4
  18. package/dist/cost-tracker.d.ts +1 -1
  19. package/dist/cost-tracker.js +58 -19
  20. package/dist/cron.js +18 -8
  21. package/dist/dead-code-detector.d.ts +34 -0
  22. package/dist/dead-code-detector.js +354 -0
  23. package/dist/dep-guard.js +47 -6
  24. package/dist/dependency-vulnerability-gate.d.ts +35 -0
  25. package/dist/dependency-vulnerability-gate.js +308 -0
  26. package/dist/diff-summary.js +97 -10
  27. package/dist/doc-sync-guard.d.ts +33 -0
  28. package/dist/doc-sync-guard.js +223 -0
  29. package/dist/duplicate-code-detector.d.ts +33 -0
  30. package/dist/duplicate-code-detector.js +384 -0
  31. package/dist/feature-flag-tracker.d.ts +38 -0
  32. package/dist/feature-flag-tracker.js +316 -0
  33. package/dist/file-watcher.js +85 -36
  34. package/dist/format-on-save.js +99 -18
  35. package/dist/import-organizer.js +73 -14
  36. package/dist/index.d.ts +27 -0
  37. package/dist/index.js +11205 -2106
  38. package/dist/interface-contract-guard.d.ts +37 -0
  39. package/dist/interface-contract-guard.js +302 -0
  40. package/dist/knowledge-graph.d.ts +45 -0
  41. package/dist/knowledge-graph.js +325 -0
  42. package/dist/license-audit-gate.d.ts +34 -0
  43. package/dist/license-audit-gate.js +260 -0
  44. package/dist/llm-cache.js +5 -0
  45. package/dist/loop-breaker.d.ts +0 -38
  46. package/dist/loop-breaker.js +209 -8
  47. package/dist/migration-planner.d.ts +30 -0
  48. package/dist/migration-planner.js +349 -0
  49. package/dist/model-router.js +5 -0
  50. package/dist/performance-regression-gate.d.ts +33 -0
  51. package/dist/performance-regression-gate.js +315 -0
  52. package/dist/plugin-stack-observer.d.ts +35 -0
  53. package/dist/plugin-stack-observer.js +138 -0
  54. package/dist/pr-drafter.d.ts +35 -0
  55. package/dist/pr-drafter.js +334 -0
  56. package/dist/prompt-firewall.js +5 -0
  57. package/dist/refactor-suggester.d.ts +38 -0
  58. package/dist/refactor-suggester.js +382 -0
  59. package/dist/release-notes-generator.d.ts +27 -0
  60. package/dist/release-notes-generator.js +209 -0
  61. package/dist/schema-evolution-guard.d.ts +42 -0
  62. package/dist/schema-evolution-guard.js +319 -0
  63. package/dist/security-hotspot-scanner.d.ts +30 -0
  64. package/dist/security-hotspot-scanner.js +402 -0
  65. package/dist/semantic-search-indexer.d.ts +38 -0
  66. package/dist/semantic-search-indexer.js +436 -0
  67. package/dist/shell-check.js +38 -3
  68. package/dist/smart-rename.d.ts +26 -0
  69. package/dist/smart-rename.js +170 -0
  70. package/dist/spec-linker.js +273 -133
  71. package/dist/test-coverage-gate.d.ts +37 -0
  72. package/dist/test-coverage-gate.js +263 -0
  73. package/dist/test-flake-detector.d.ts +27 -0
  74. package/dist/test-flake-detector.js +274 -0
  75. package/dist/test-generator.d.ts +32 -0
  76. package/dist/test-generator.js +243 -0
  77. package/dist/test-runner-gate.js +154 -22
  78. package/dist/todo-listener.d.ts +2 -2
  79. package/dist/todo-listener.js +5 -5
  80. package/dist/token-throttle.js +5 -0
  81. package/dist/type-gate.d.ts +37 -0
  82. package/dist/type-gate.js +311 -0
  83. package/package.json +112 -4
  84. package/LICENSE +0 -21
@@ -0,0 +1,37 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * interface-contract-guard plugin — checks TypeScript interface declarations
5
+ * for apparent implementers, warning when an interface looks unimplemented.
6
+ *
7
+ * Tools registered:
8
+ * - check_interface_contracts : Scan TypeScript files for unimplemented interfaces.
9
+ * - interface_contract_status : Report config + counters.
10
+ *
11
+ * Hooks registered:
12
+ * - PostToolUse with matcher `write|edit` to .ts/.tsx files, warning when the
13
+ * changed file contains interface declarations because implementers may need
14
+ * updating.
15
+ *
16
+ * Config (`config.extensions['interface-contract-guard']`):
17
+ *
18
+ * ```jsonc
19
+ * {
20
+ * "enabled": true,
21
+ * "extensions": [".ts", ".tsx"],
22
+ * "maxFindings": 50
23
+ * }
24
+ * ```
25
+ *
26
+ * @public
27
+ */
28
+
29
+ interface InterfaceContractFinding {
30
+ interfaceName: string;
31
+ file: string;
32
+ line: number;
33
+ message: string;
34
+ }
35
+ declare const plugin: Plugin;
36
+
37
+ export { type InterfaceContractFinding, plugin as default };
@@ -0,0 +1,302 @@
1
+ import { readFileSync, existsSync, statSync, readdirSync } from 'fs';
2
+ import { resolve, isAbsolute, relative, extname } from 'path';
3
+
4
+ // src/interface-contract-guard/index.ts
5
+ var API_VERSION = "^0.1.10";
6
+ var state = {
7
+ scanCount: 0,
8
+ findingCount: 0,
9
+ hookInvocationCount: 0,
10
+ warningCount: 0,
11
+ errorCount: 0,
12
+ hookUnregister: null
13
+ };
14
+ var DEFAULTS = {
15
+ enabled: false,
16
+ extensions: [".ts", ".tsx"],
17
+ maxFindings: 50
18
+ };
19
+ function readConfig(raw) {
20
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
21
+ const r = raw;
22
+ return {
23
+ enabled: r["enabled"] !== false,
24
+ extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
25
+ maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings
26
+ };
27
+ }
28
+ function withinProject(p) {
29
+ if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
30
+ const root = process.cwd();
31
+ const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
32
+ const rel = relative(root, resolved);
33
+ if (rel === "" || rel === ".") return true;
34
+ if (rel.startsWith("..")) return false;
35
+ if (isAbsolute(rel)) return false;
36
+ return true;
37
+ }
38
+ function normalizeExtensions(exts) {
39
+ return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
40
+ }
41
+ function matchesExtension(p, exts) {
42
+ return exts.includes(extname(p).toLowerCase());
43
+ }
44
+ function collectSourceFiles(root, exts) {
45
+ const files = [];
46
+ if (!existsSync(root)) return files;
47
+ const s = statSync(root);
48
+ if (s.isFile()) {
49
+ if (matchesExtension(root, exts)) files.push(root);
50
+ return files;
51
+ }
52
+ if (!s.isDirectory()) return files;
53
+ function walk(dir) {
54
+ let entries;
55
+ try {
56
+ entries = readdirSync(dir);
57
+ } catch {
58
+ return;
59
+ }
60
+ for (const entry of entries) {
61
+ if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
62
+ const full = resolve(dir, entry);
63
+ let st;
64
+ try {
65
+ st = statSync(full);
66
+ } catch {
67
+ continue;
68
+ }
69
+ if (st.isDirectory()) {
70
+ walk(full);
71
+ } else if (st.isFile() && matchesExtension(full, exts)) {
72
+ files.push(full);
73
+ }
74
+ }
75
+ }
76
+ walk(root);
77
+ return files;
78
+ }
79
+ function toPosix(p) {
80
+ return p.replace(/\\/g, "/");
81
+ }
82
+ function relativePath(p) {
83
+ return toPosix(relative(process.cwd(), p));
84
+ }
85
+ var INTERFACE_RE = /(?:export\s+)?interface\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
86
+ function extractInterfaceNames(content) {
87
+ const names = [];
88
+ let m;
89
+ INTERFACE_RE.lastIndex = 0;
90
+ while ((m = INTERFACE_RE.exec(content)) !== null) {
91
+ names.push({ name: m[1], line: content.slice(0, m.index).split(/\r?\n/).length });
92
+ }
93
+ return names;
94
+ }
95
+ function hasImplementer(name, contents) {
96
+ const implRe = new RegExp(`(?:implements|satisfies|\\bas)\\s+${name}\\b`, "g");
97
+ for (const content of contents) {
98
+ if (implRe.test(content)) return true;
99
+ }
100
+ return false;
101
+ }
102
+ function scanPath(rawPath, cfg) {
103
+ const root = process.cwd();
104
+ const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
105
+ const exts = normalizeExtensions(cfg.extensions);
106
+ const files = collectSourceFiles(resolved, exts);
107
+ const contents = [];
108
+ for (const p of files) {
109
+ try {
110
+ contents.push({ path: p, content: readFileSync(p, "utf-8") });
111
+ } catch {
112
+ }
113
+ }
114
+ const allContentStrings = contents.map((c) => c.content);
115
+ const findings = [];
116
+ for (const { path, content } of contents) {
117
+ for (const { name, line } of extractInterfaceNames(content)) {
118
+ if (!hasImplementer(name, allContentStrings)) {
119
+ findings.push({
120
+ interfaceName: name,
121
+ file: relativePath(path),
122
+ line,
123
+ message: `interface "${name}" is declared but has no visible implementer`
124
+ });
125
+ if (findings.length >= cfg.maxFindings) break;
126
+ }
127
+ }
128
+ if (findings.length >= cfg.maxFindings) break;
129
+ }
130
+ return { findings, scannedFiles: contents.length };
131
+ }
132
+ var plugin = {
133
+ name: "interface-contract-guard",
134
+ version: "0.1.0",
135
+ description: "Checks TypeScript interfaces for visible implementers and warns about contract drift",
136
+ apiVersion: API_VERSION,
137
+ capabilities: { tools: true, hooks: true },
138
+ defaultConfig: { ...DEFAULTS },
139
+ configSchema: {
140
+ type: "object",
141
+ properties: {
142
+ enabled: { type: "boolean", default: true, description: "Master switch." },
143
+ extensions: {
144
+ type: "array",
145
+ items: { type: "string" },
146
+ default: [".ts", ".tsx"],
147
+ description: "File extensions to scan."
148
+ },
149
+ maxFindings: {
150
+ type: "number",
151
+ minimum: 1,
152
+ maximum: 500,
153
+ default: 50,
154
+ description: "Maximum findings reported per scan."
155
+ }
156
+ }
157
+ },
158
+ setup(api) {
159
+ state.scanCount = 0;
160
+ state.findingCount = 0;
161
+ state.hookInvocationCount = 0;
162
+ state.warningCount = 0;
163
+ state.errorCount = 0;
164
+ if (state.hookUnregister) {
165
+ try {
166
+ state.hookUnregister();
167
+ } catch {
168
+ }
169
+ state.hookUnregister = null;
170
+ }
171
+ const cfg = readConfig(api.config.extensions?.["interface-contract-guard"]);
172
+ const hook = (input) => {
173
+ if (!cfg.enabled) return;
174
+ if (input.toolResult?.isError) return;
175
+ const inp = input.toolInput ?? {};
176
+ const sourcePath = inp["path"];
177
+ if (!sourcePath || typeof sourcePath !== "string") return;
178
+ if (!withinProject(sourcePath)) return;
179
+ const exts = normalizeExtensions(cfg.extensions);
180
+ if (!matchesExtension(sourcePath, exts)) return;
181
+ state.hookInvocationCount += 1;
182
+ const resolved = resolve(process.cwd(), sourcePath);
183
+ let content;
184
+ try {
185
+ content = readFileSync(resolved, "utf-8");
186
+ } catch {
187
+ state.errorCount += 1;
188
+ return;
189
+ }
190
+ const interfaces = extractInterfaceNames(content);
191
+ if (interfaces.length === 0) return;
192
+ state.warningCount += 1;
193
+ const names = interfaces.map((i) => i.name).join(", ");
194
+ return {
195
+ additionalContext: `
196
+ \u{1F6E1}\uFE0F interface-contract-guard: ${sourcePath} declares interface(s): ${names}.
197
+ If you changed member shapes, search the project for implementers/\`satisfies\`/\`as\` usages and update them accordingly.`
198
+ };
199
+ };
200
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
201
+ api.tools.register({
202
+ name: "check_interface_contracts",
203
+ description: "Scan TypeScript files for interface declarations that have no visible implementer (implements / as / satisfies).",
204
+ inputSchema: {
205
+ type: "object",
206
+ properties: {
207
+ path: { type: "string", default: ".", description: "File or directory path to scan." }
208
+ }
209
+ },
210
+ permission: "auto",
211
+ category: "Diagnostics",
212
+ mutating: false,
213
+ async execute(input) {
214
+ if (!cfg.enabled) return { ok: false, error: "interface-contract-guard is disabled" };
215
+ const rawPath = typeof input.path === "string" ? input.path : ".";
216
+ if (!withinProject(rawPath)) {
217
+ return { ok: false, error: "path is outside the project root" };
218
+ }
219
+ state.scanCount += 1;
220
+ let result;
221
+ try {
222
+ result = scanPath(rawPath, cfg);
223
+ } catch (err) {
224
+ state.errorCount += 1;
225
+ return { ok: false, error: String(err) };
226
+ }
227
+ state.findingCount += result.findings.length;
228
+ return {
229
+ ok: true,
230
+ path: relativePath(resolve(process.cwd(), rawPath)),
231
+ scannedFiles: result.scannedFiles,
232
+ findings: result.findings
233
+ };
234
+ }
235
+ });
236
+ api.tools.register({
237
+ name: "interface_contract_status",
238
+ description: "Reports interface-contract-guard state: config + counters.",
239
+ inputSchema: { type: "object", properties: {} },
240
+ permission: "auto",
241
+ category: "Diagnostics",
242
+ mutating: false,
243
+ async execute() {
244
+ return {
245
+ ok: true,
246
+ enabled: cfg.enabled,
247
+ extensions: cfg.extensions,
248
+ maxFindings: cfg.maxFindings,
249
+ counters: {
250
+ scans: state.scanCount,
251
+ findings: state.findingCount,
252
+ hookInvocations: state.hookInvocationCount,
253
+ warnings: state.warningCount,
254
+ errors: state.errorCount
255
+ }
256
+ };
257
+ }
258
+ });
259
+ api.log.info("interface-contract-guard plugin loaded", {
260
+ version: "0.1.0",
261
+ extensions: cfg.extensions
262
+ });
263
+ },
264
+ teardown(api) {
265
+ if (state.hookUnregister) {
266
+ try {
267
+ state.hookUnregister();
268
+ } catch {
269
+ }
270
+ state.hookUnregister = null;
271
+ }
272
+ const final = {
273
+ scans: state.scanCount,
274
+ findings: state.findingCount,
275
+ hookInvocations: state.hookInvocationCount,
276
+ warnings: state.warningCount,
277
+ errors: state.errorCount
278
+ };
279
+ state.scanCount = 0;
280
+ state.findingCount = 0;
281
+ state.hookInvocationCount = 0;
282
+ state.warningCount = 0;
283
+ state.errorCount = 0;
284
+ api.log.info("interface-contract-guard: teardown complete", { final });
285
+ },
286
+ async health() {
287
+ return {
288
+ ok: state.errorCount === 0,
289
+ message: state.errorCount ? `interface-contract-guard: ${state.errorCount} error(s)` : `interface-contract-guard: ${state.scanCount} scan(s), ${state.findingCount} finding(s)`,
290
+ counters: {
291
+ scans: state.scanCount,
292
+ findings: state.findingCount,
293
+ hookInvocations: state.hookInvocationCount,
294
+ warnings: state.warningCount,
295
+ errors: state.errorCount
296
+ }
297
+ };
298
+ }
299
+ };
300
+ var interface_contract_guard_default = plugin;
301
+
302
+ export { interface_contract_guard_default as default };
@@ -0,0 +1,45 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * knowledge-graph plugin — accumulates structured facts about the project
5
+ * across sessions.
6
+ *
7
+ * The plugin gives the agent three tools:
8
+ * - `kg_add_fact` — add a (subject, relation, object) triple
9
+ * - `kg_query` — query facts by subject, relation, or object
10
+ * - `kg_remove_fact` — remove a fact by id
11
+ * - `kg_status` — counters + persisted path
12
+ *
13
+ * Facts persist in a project-local JSON file (default:
14
+ * `<projectDir>/.wrongstack/knowledge-graph.json`) so they survive across
15
+ * sessions. Optionally the plugin can contribute a compact summary of
16
+ * relevant facts to the system prompt for the current task.
17
+ *
18
+ * Config (`config.extensions['knowledge-graph']`):
19
+ *
20
+ * ```jsonc
21
+ * {
22
+ * "enabled": true,
23
+ * "filePath": ".wrongstack/knowledge-graph.json",
24
+ * "maxFacts": 200,
25
+ * "maxFactChars": 300,
26
+ * "contributeToSystemPrompt": true,
27
+ * "contributeMaxChars": 1500
28
+ * }
29
+ * ```
30
+ *
31
+ * @public
32
+ */
33
+
34
+ interface Fact {
35
+ id: string;
36
+ subject: string;
37
+ relation: string;
38
+ object: string;
39
+ source: string | null;
40
+ confidence: 'low' | 'medium' | 'high';
41
+ createdAt: string;
42
+ }
43
+ declare const plugin: Plugin;
44
+
45
+ export { type Fact, plugin as default };