@wrongstack/plugins 0.281.1 → 0.282.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.
Files changed (78) hide show
  1. package/README.md +29 -3
  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-i18n-extractor.d.ts +36 -0
  9. package/dist/auto-i18n-extractor.js +335 -0
  10. package/dist/checkpoint.js +18 -0
  11. package/dist/code-metrics.d.ts +31 -0
  12. package/dist/code-metrics.js +338 -0
  13. package/dist/commit-validator.js +67 -12
  14. package/dist/cost-tracker.js +58 -16
  15. package/dist/dead-code-detector.d.ts +34 -0
  16. package/dist/dead-code-detector.js +354 -0
  17. package/dist/dep-guard.js +47 -6
  18. package/dist/dependency-vulnerability-gate.d.ts +35 -0
  19. package/dist/dependency-vulnerability-gate.js +308 -0
  20. package/dist/diff-summary.js +97 -10
  21. package/dist/doc-sync-guard.d.ts +33 -0
  22. package/dist/doc-sync-guard.js +223 -0
  23. package/dist/duplicate-code-detector.d.ts +33 -0
  24. package/dist/duplicate-code-detector.js +384 -0
  25. package/dist/feature-flag-tracker.d.ts +38 -0
  26. package/dist/feature-flag-tracker.js +316 -0
  27. package/dist/file-watcher.js +85 -36
  28. package/dist/format-on-save.js +76 -10
  29. package/dist/import-organizer.js +73 -14
  30. package/dist/index.d.ts +27 -0
  31. package/dist/index.js +14067 -5054
  32. package/dist/interface-contract-guard.d.ts +37 -0
  33. package/dist/interface-contract-guard.js +302 -0
  34. package/dist/knowledge-graph.d.ts +45 -0
  35. package/dist/knowledge-graph.js +325 -0
  36. package/dist/license-audit-gate.d.ts +34 -0
  37. package/dist/license-audit-gate.js +260 -0
  38. package/dist/llm-cache.js +5 -0
  39. package/dist/loop-breaker.d.ts +0 -38
  40. package/dist/loop-breaker.js +209 -8
  41. package/dist/migration-planner.d.ts +30 -0
  42. package/dist/migration-planner.js +349 -0
  43. package/dist/model-router.js +5 -0
  44. package/dist/performance-regression-gate.d.ts +33 -0
  45. package/dist/performance-regression-gate.js +315 -0
  46. package/dist/plugin-stack-observer.d.ts +35 -0
  47. package/dist/plugin-stack-observer.js +125 -0
  48. package/dist/pr-drafter.d.ts +35 -0
  49. package/dist/pr-drafter.js +334 -0
  50. package/dist/prompt-firewall.js +5 -0
  51. package/dist/refactor-suggester.d.ts +38 -0
  52. package/dist/refactor-suggester.js +382 -0
  53. package/dist/release-notes-generator.d.ts +27 -0
  54. package/dist/release-notes-generator.js +209 -0
  55. package/dist/schema-evolution-guard.d.ts +42 -0
  56. package/dist/schema-evolution-guard.js +319 -0
  57. package/dist/security-hotspot-scanner.d.ts +30 -0
  58. package/dist/security-hotspot-scanner.js +402 -0
  59. package/dist/semantic-search-indexer.d.ts +38 -0
  60. package/dist/semantic-search-indexer.js +436 -0
  61. package/dist/shell-check.js +38 -3
  62. package/dist/smart-rename.d.ts +26 -0
  63. package/dist/smart-rename.js +170 -0
  64. package/dist/spec-linker.js +273 -133
  65. package/dist/test-coverage-gate.d.ts +37 -0
  66. package/dist/test-coverage-gate.js +263 -0
  67. package/dist/test-flake-detector.d.ts +27 -0
  68. package/dist/test-flake-detector.js +274 -0
  69. package/dist/test-generator.d.ts +32 -0
  70. package/dist/test-generator.js +243 -0
  71. package/dist/test-runner-gate.js +154 -22
  72. package/dist/todo-listener.d.ts +2 -2
  73. package/dist/todo-listener.js +5 -5
  74. package/dist/token-throttle.js +5 -0
  75. package/dist/type-gate.d.ts +37 -0
  76. package/dist/type-gate.js +311 -0
  77. package/package.json +112 -4
  78. package/LICENSE +0 -21
@@ -0,0 +1,334 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { mkdirSync, writeFileSync } from 'fs';
3
+ import { dirname, resolve, isAbsolute, relative } from 'path';
4
+
5
+ // src/pr-drafter/index.ts
6
+ var API_VERSION = "^0.1.10";
7
+ var state = {
8
+ commits: [],
9
+ files: /* @__PURE__ */ new Set(),
10
+ models: /* @__PURE__ */ new Set(),
11
+ totalInputTokens: 0,
12
+ totalOutputTokens: 0,
13
+ toolCalls: 0,
14
+ draftsWritten: 0,
15
+ draftErrors: 0,
16
+ stopInvocations: 0,
17
+ stopHookUnregister: null,
18
+ eventUnsubscribers: []
19
+ };
20
+ var DEFAULTS = {
21
+ enabled: true,
22
+ outputPath: ".wrongstack/PR_DRAFT.md",
23
+ writeOnStop: true,
24
+ includeDiff: true,
25
+ includeCommits: true,
26
+ includeFiles: true,
27
+ aiSummary: false
28
+ };
29
+ function readConfig(raw) {
30
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
31
+ const r = raw;
32
+ return {
33
+ enabled: r["enabled"] !== false,
34
+ outputPath: typeof r["outputPath"] === "string" ? r["outputPath"] : DEFAULTS.outputPath,
35
+ writeOnStop: r["writeOnStop"] !== false,
36
+ includeDiff: r["includeDiff"] !== false,
37
+ includeCommits: r["includeCommits"] !== false,
38
+ includeFiles: r["includeFiles"] !== false,
39
+ aiSummary: r["aiSummary"] === true
40
+ };
41
+ }
42
+ function resolveProjectPath(rawPath, cwd = process.cwd()) {
43
+ if (typeof rawPath !== "string" || rawPath.length === 0) return null;
44
+ const root = resolve(cwd);
45
+ const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
46
+ const rel = relative(root, resolved);
47
+ if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return resolved;
48
+ return null;
49
+ }
50
+ function gitBranch() {
51
+ try {
52
+ const out = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
53
+ encoding: "utf-8",
54
+ cwd: process.cwd(),
55
+ stdio: ["pipe", "pipe", "ignore"],
56
+ timeout: 5e3
57
+ });
58
+ return out.trim();
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ function gitDiff() {
64
+ try {
65
+ return execFileSync("git", ["diff", "--stat"], {
66
+ encoding: "utf-8",
67
+ cwd: process.cwd(),
68
+ stdio: ["pipe", "pipe", "ignore"],
69
+ timeout: 1e4
70
+ }).trim();
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ async function buildDraft(cfg, llm) {
76
+ const branch = gitBranch();
77
+ const diffStat = cfg.includeDiff ? gitDiff() : null;
78
+ const lines = [];
79
+ lines.push("# PR Draft");
80
+ if (branch) lines.push(`**Branch:** \`${branch}\``);
81
+ lines.push(`**Generated:** ${(/* @__PURE__ */ new Date()).toISOString()}`);
82
+ lines.push("");
83
+ if (cfg.aiSummary && llm) {
84
+ try {
85
+ const context = `Files changed: ${[...state.files].join(", ") || "none"}
86
+ Commits: ${state.commits.join("; ") || "none"}
87
+ Tool calls: ${state.toolCalls}
88
+ Tokens: ${state.totalInputTokens} in / ${state.totalOutputTokens} out`;
89
+ const result = await llm.complete(
90
+ "Write a concise PR title and one-paragraph summary for the changes below. Format exactly as:\nTITLE: <title>\nSUMMARY: <summary>\n\n" + context,
91
+ { system: "You write terse engineering PR descriptions.", maxTokens: 250 }
92
+ );
93
+ const text = result.text.trim();
94
+ const titleMatch = /TITLE:\s*(.+)/i.exec(text);
95
+ const summaryMatch = /SUMMARY:\s*(.+)/is.exec(text);
96
+ if (titleMatch?.[1]) lines.push(`## ${titleMatch[1].trim()}`);
97
+ if (summaryMatch?.[1]) lines.push(summaryMatch[1].trim(), "");
98
+ } catch {
99
+ }
100
+ }
101
+ if (cfg.includeCommits && state.commits.length > 0) {
102
+ lines.push("## Commits");
103
+ for (const c of state.commits) lines.push(`- ${c}`);
104
+ lines.push("");
105
+ }
106
+ if (cfg.includeFiles && state.files.size > 0) {
107
+ lines.push("## Files changed");
108
+ for (const f of [...state.files].sort()) lines.push(`- \`${f}\``);
109
+ lines.push("");
110
+ }
111
+ if (diffStat) {
112
+ lines.push("## Diff summary");
113
+ lines.push("```");
114
+ lines.push(diffStat);
115
+ lines.push("```");
116
+ lines.push("");
117
+ }
118
+ lines.push("## Session metrics");
119
+ lines.push(`- Tool calls: ${state.toolCalls}`);
120
+ lines.push(`- Models used: ${[...state.models].join(", ") || "unknown"}`);
121
+ lines.push(`- Tokens: ${state.totalInputTokens} in / ${state.totalOutputTokens} out`);
122
+ lines.push("");
123
+ const title = state.commits[0] ?? `Changes on ${branch ?? "current branch"}`;
124
+ return { title, body: lines.join("\n") };
125
+ }
126
+ async function writeDraft(cfg, llm) {
127
+ const resolved = resolveProjectPath(cfg.outputPath);
128
+ if (!resolved) {
129
+ state.draftErrors += 1;
130
+ return;
131
+ }
132
+ const draft = await buildDraft(cfg, llm);
133
+ try {
134
+ mkdirSync(dirname(resolved), { recursive: true });
135
+ writeFileSync(resolved, draft.body);
136
+ state.draftsWritten += 1;
137
+ } catch {
138
+ state.draftErrors += 1;
139
+ }
140
+ }
141
+ var plugin = {
142
+ name: "pr-drafter",
143
+ version: "0.1.0",
144
+ description: "Collects session work (commits, edited files, diff) and drafts a pull-request description",
145
+ apiVersion: API_VERSION,
146
+ capabilities: { tools: true, hooks: true },
147
+ defaultConfig: { ...DEFAULTS },
148
+ configSchema: {
149
+ type: "object",
150
+ properties: {
151
+ enabled: { type: "boolean", default: true, description: "Master switch." },
152
+ outputPath: {
153
+ type: "string",
154
+ default: ".wrongstack/PR_DRAFT.md",
155
+ description: "Path where the markdown PR draft is written."
156
+ },
157
+ writeOnStop: {
158
+ type: "boolean",
159
+ default: true,
160
+ description: "Automatically write the draft when the session stops."
161
+ },
162
+ includeDiff: {
163
+ type: "boolean",
164
+ default: true,
165
+ description: "Include git diff --stat in the draft."
166
+ },
167
+ includeCommits: {
168
+ type: "boolean",
169
+ default: true,
170
+ description: "Include recorded commit messages."
171
+ },
172
+ includeFiles: {
173
+ type: "boolean",
174
+ default: true,
175
+ description: "Include files touched by write/edit tools."
176
+ },
177
+ aiSummary: {
178
+ type: "boolean",
179
+ default: false,
180
+ description: "Use api.llm to generate a PR title and summary."
181
+ },
182
+ llm: {
183
+ type: "object",
184
+ description: "Optional { provider, model } override for the AI summary."
185
+ }
186
+ }
187
+ },
188
+ setup(api) {
189
+ state.commits = [];
190
+ state.files = /* @__PURE__ */ new Set();
191
+ state.models = /* @__PURE__ */ new Set();
192
+ state.totalInputTokens = 0;
193
+ state.totalOutputTokens = 0;
194
+ state.toolCalls = 0;
195
+ state.draftsWritten = 0;
196
+ state.draftErrors = 0;
197
+ state.stopInvocations = 0;
198
+ state.stopHookUnregister = null;
199
+ for (const off of state.eventUnsubscribers) {
200
+ try {
201
+ off();
202
+ } catch {
203
+ }
204
+ }
205
+ state.eventUnsubscribers = [];
206
+ const cfg = readConfig(api.config.extensions?.["pr-drafter"]);
207
+ if (api.onPattern) {
208
+ const offTool = api.onPattern("tool.completed", (_event, payload) => {
209
+ const p = payload;
210
+ const toolName = p?.tool;
211
+ state.toolCalls += 1;
212
+ if (toolName === "git_autocommit" && p?.result?.committed) {
213
+ const msg = p.result.commitMessage ?? p.input?.message ?? "commit";
214
+ state.commits.push(msg);
215
+ }
216
+ if ((toolName === "write" || toolName === "edit") && p?.input?.path) {
217
+ state.files.add(p.input.path);
218
+ }
219
+ });
220
+ state.eventUnsubscribers.push(offTool);
221
+ }
222
+ if (api.onEvent) {
223
+ const offUsage = api.onEvent("provider.response", (payload) => {
224
+ const p = payload;
225
+ if (p?.model) state.models.add(p.model);
226
+ state.totalInputTokens += p?.usage?.input ?? 0;
227
+ state.totalOutputTokens += p?.usage?.output ?? 0;
228
+ });
229
+ state.eventUnsubscribers.push(offUsage);
230
+ }
231
+ const stopHook = async () => {
232
+ if (!cfg.enabled || !cfg.writeOnStop) return;
233
+ state.stopInvocations += 1;
234
+ await writeDraft(cfg, api.llm);
235
+ };
236
+ state.stopHookUnregister = api.registerHook("Stop", void 0, stopHook);
237
+ api.tools.register({
238
+ name: "pr_draft",
239
+ description: "Generate or refresh the pull-request draft for the current session. Writes the markdown file and returns its path + title.",
240
+ inputSchema: {
241
+ type: "object",
242
+ properties: {
243
+ preview: {
244
+ type: "boolean",
245
+ description: "When true, return the draft body without writing to disk."
246
+ }
247
+ }
248
+ },
249
+ permission: "auto",
250
+ category: "Workflow",
251
+ mutating: false,
252
+ async execute(input) {
253
+ if (!cfg.enabled) return { ok: false, error: "pr-drafter is disabled" };
254
+ const draft = await buildDraft(cfg, api.llm);
255
+ if (input.preview) {
256
+ return { ok: true, preview: true, title: draft.title, body: draft.body };
257
+ }
258
+ const resolved = resolveProjectPath(cfg.outputPath);
259
+ if (!resolved) return { ok: false, error: "outputPath resolves outside project" };
260
+ try {
261
+ mkdirSync(dirname(resolved), { recursive: true });
262
+ writeFileSync(resolved, draft.body);
263
+ state.draftsWritten += 1;
264
+ return {
265
+ ok: true,
266
+ path: cfg.outputPath,
267
+ resolvedPath: resolved,
268
+ title: draft.title
269
+ };
270
+ } catch (err) {
271
+ state.draftErrors += 1;
272
+ return {
273
+ ok: false,
274
+ error: err instanceof Error ? err.message : String(err)
275
+ };
276
+ }
277
+ }
278
+ });
279
+ api.log.info("pr-drafter plugin loaded", {
280
+ version: "0.1.0",
281
+ outputPath: cfg.outputPath,
282
+ writeOnStop: cfg.writeOnStop
283
+ });
284
+ },
285
+ teardown(api) {
286
+ if (state.stopHookUnregister) {
287
+ try {
288
+ state.stopHookUnregister();
289
+ } catch {
290
+ }
291
+ state.stopHookUnregister = null;
292
+ }
293
+ for (const off of state.eventUnsubscribers) {
294
+ try {
295
+ off();
296
+ } catch {
297
+ }
298
+ }
299
+ state.eventUnsubscribers = [];
300
+ const final = {
301
+ commits: state.commits.length,
302
+ files: state.files.size,
303
+ toolCalls: state.toolCalls,
304
+ draftsWritten: state.draftsWritten,
305
+ draftErrors: state.draftErrors
306
+ };
307
+ state.commits = [];
308
+ state.files = /* @__PURE__ */ new Set();
309
+ state.models = /* @__PURE__ */ new Set();
310
+ state.totalInputTokens = 0;
311
+ state.totalOutputTokens = 0;
312
+ state.toolCalls = 0;
313
+ state.draftsWritten = 0;
314
+ state.draftErrors = 0;
315
+ state.stopInvocations = 0;
316
+ api.log.info("pr-drafter: teardown complete", { final });
317
+ },
318
+ async health() {
319
+ return {
320
+ ok: state.draftErrors === 0,
321
+ message: `pr-drafter: ${state.commits.length} commit(s), ${state.files.size} file(s), ${state.draftsWritten} draft(s) written, ${state.draftErrors} error(s)`,
322
+ counters: {
323
+ commits: state.commits.length,
324
+ files: state.files.size,
325
+ toolCalls: state.toolCalls,
326
+ draftsWritten: state.draftsWritten,
327
+ draftErrors: state.draftErrors
328
+ }
329
+ };
330
+ }
331
+ };
332
+ var pr_drafter_default = plugin;
333
+
334
+ export { pr_drafter_default as default };
@@ -157,6 +157,11 @@ var plugin = {
157
157
  }
158
158
  const cfg = readConfig(api.config.extensions?.["prompt-firewall"]);
159
159
  if (cfg.enabled) {
160
+ api.emitCustom?.("provider.wrap:loaded", {
161
+ plugin: "prompt-firewall",
162
+ kind: "security",
163
+ wraps: ["request", "response"]
164
+ });
160
165
  state.extensionUnregister = api.extensions.register({
161
166
  name: "prompt-firewall",
162
167
  owner: "prompt-firewall",
@@ -0,0 +1,38 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * refactor-suggester plugin — regex-based smell detector that suggests
5
+ * refactoring opportunities in source files.
6
+ *
7
+ * Tools registered:
8
+ * - suggest_refactors : Scan a file or directory for smells.
9
+ * - refactor_status : Report config + counters.
10
+ *
11
+ * Hooks registered:
12
+ * - PostToolUse with matcher `write|edit` to source files, injecting
13
+ * suggestions for the changed file.
14
+ *
15
+ * Config (`config.extensions['refactor-suggester']`):
16
+ *
17
+ * ```jsonc
18
+ * {
19
+ * "enabled": true,
20
+ * "extensions": [".ts", ".tsx", ".js", ".jsx"],
21
+ * "maxSuggestions": 20,
22
+ * "rules": { "longFunctionLines": 50, "maxParams": 5, "maxNesting": 3 }
23
+ * }
24
+ * ```
25
+ *
26
+ * @public
27
+ */
28
+
29
+ type SmellType = 'long-function' | 'deep-nesting' | 'many-parameters' | 'magic-number' | 'console-log';
30
+ interface RefactorSuggestion {
31
+ file: string;
32
+ line: number;
33
+ type: SmellType;
34
+ message: string;
35
+ }
36
+ declare const plugin: Plugin;
37
+
38
+ export { type RefactorSuggestion, type SmellType, plugin as default };