@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
@@ -107,6 +107,11 @@ var plugin = {
107
107
  }
108
108
  const cfg = readConfig(api.config.extensions?.["model-router"]);
109
109
  if (cfg.enabled && cfg.rules.length > 0) {
110
+ api.emitCustom?.("provider.wrap:loaded", {
111
+ plugin: "model-router",
112
+ kind: "routing",
113
+ wraps: ["request"]
114
+ });
110
115
  state.extensionUnregister = api.extensions.register({
111
116
  name: "model-router",
112
117
  owner: "model-router",
@@ -0,0 +1,33 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * performance-regression-gate plugin — compares benchmark results to detect
5
+ * performance regressions.
6
+ *
7
+ * Tool registered:
8
+ * - perf_regression_status : Reads `bench-results.json` in the project root
9
+ * (or a supplied path) and reports benchmarks whose execution time (`mean`)
10
+ * increased beyond the configured threshold.
11
+ *
12
+ * The tool supports two comparison modes:
13
+ * 1. Internal pairing: when only `resultsPath` is supplied, benchmarks named
14
+ * with `(old)` and `(new)` suffixes are paired. This matches Vitest bench
15
+ * output comparing an old implementation against a new one.
16
+ * 2. Cross-file comparison: when `baselinePath` is also supplied, benchmarks
17
+ * are matched by name across the two files.
18
+ *
19
+ * Config (`config.extensions['performance-regression-gate']`):
20
+ *
21
+ * ```jsonc
22
+ * {
23
+ * "enabled": true,
24
+ * "thresholdPercent": 10
25
+ * }
26
+ * ```
27
+ *
28
+ * @public
29
+ */
30
+
31
+ declare const plugin: Plugin;
32
+
33
+ export { plugin as default };
@@ -0,0 +1,315 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { resolve, isAbsolute, relative } from 'path';
3
+
4
+ // src/performance-regression-gate/index.ts
5
+ var API_VERSION = "^0.1.10";
6
+ var state = {
7
+ invocationCount: 0,
8
+ comparisonCount: 0,
9
+ regressionCount: 0,
10
+ missingResultsCount: 0,
11
+ errorCount: 0,
12
+ lastResult: null
13
+ };
14
+ var DEFAULTS = {
15
+ enabled: true,
16
+ thresholdPercent: 10
17
+ };
18
+ function readConfig(raw) {
19
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
20
+ const r = raw;
21
+ const threshold = typeof r["thresholdPercent"] === "number" && r["thresholdPercent"] >= 0 && r["thresholdPercent"] <= 1e3 ? r["thresholdPercent"] : DEFAULTS.thresholdPercent;
22
+ return {
23
+ enabled: r["enabled"] !== false,
24
+ thresholdPercent: threshold
25
+ };
26
+ }
27
+ function withinProject(p, cwd = process.cwd()) {
28
+ if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
29
+ const root = resolve(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
+ function resolveProjectPath(rawPath, cwd = process.cwd()) {
38
+ if (typeof rawPath !== "string" || rawPath.length === 0) return null;
39
+ const root = resolve(cwd);
40
+ const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
41
+ if (!withinProject(resolved, cwd)) return null;
42
+ return resolved;
43
+ }
44
+ function isValidNumber(n) {
45
+ return typeof n === "number" && Number.isFinite(n) && n > 0;
46
+ }
47
+ function flattenResults(results) {
48
+ const flat = [];
49
+ for (const file of results.files ?? []) {
50
+ for (const group of file.groups ?? []) {
51
+ for (const bench of group.benchmarks ?? []) {
52
+ if (!bench || typeof bench.name !== "string") continue;
53
+ const mean = isValidNumber(bench.mean) ? bench.mean : isValidNumber(bench.period) ? bench.period : null;
54
+ if (mean === null) continue;
55
+ flat.push({
56
+ key: `${group.fullName} > ${bench.name}`,
57
+ file: file.filepath ?? "",
58
+ group: group.fullName ?? "",
59
+ name: bench.name,
60
+ mean,
61
+ hz: isValidNumber(bench.hz) ? bench.hz : null
62
+ });
63
+ }
64
+ }
65
+ }
66
+ return flat;
67
+ }
68
+ function loadResults(path) {
69
+ if (!path || !existsSync(path)) return null;
70
+ try {
71
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
72
+ return raw;
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+ function stripVariantSuffix(name) {
78
+ const trimmed = name.trim();
79
+ const oldMatch = /\(old\)\s*$/i.exec(trimmed);
80
+ if (oldMatch) {
81
+ return { base: trimmed.slice(0, oldMatch.index).trim(), variant: "old" };
82
+ }
83
+ const newMatch = /\(new\)\s*$/i.exec(trimmed);
84
+ if (newMatch) {
85
+ return { base: trimmed.slice(0, newMatch.index).trim(), variant: "new" };
86
+ }
87
+ return { base: trimmed, variant: null };
88
+ }
89
+ function pairInternal(benchmarks) {
90
+ const byBase = /* @__PURE__ */ new Map();
91
+ for (const bench of benchmarks) {
92
+ const { base, variant } = stripVariantSuffix(bench.name);
93
+ if (!variant) continue;
94
+ const groupBase = `${bench.group} > ${base}`;
95
+ const entry = byBase.get(groupBase) ?? {};
96
+ if (variant === "old") entry.old = bench;
97
+ if (variant === "new") entry.new = bench;
98
+ byBase.set(groupBase, entry);
99
+ }
100
+ const pairs = [];
101
+ for (const entry of byBase.values()) {
102
+ if (entry.old && entry.new) {
103
+ pairs.push({ old: entry.old, new: entry.new });
104
+ }
105
+ }
106
+ return pairs;
107
+ }
108
+ function pairCrossFile(baseline, current) {
109
+ const byKey = /* @__PURE__ */ new Map();
110
+ for (const bench of baseline) {
111
+ byKey.set(bench.key, bench);
112
+ }
113
+ const pairs = [];
114
+ for (const bench of current) {
115
+ const base = stripVariantSuffix(bench.name).base;
116
+ const lookupKey = `${bench.group} > ${base}`;
117
+ const old = byKey.get(lookupKey) ?? byKey.get(bench.key);
118
+ if (old) {
119
+ pairs.push({ old, new: bench });
120
+ }
121
+ }
122
+ return pairs;
123
+ }
124
+ function comparePairs(pairs, thresholdPercent) {
125
+ const regressions = [];
126
+ const thresholdFactor = 1 + thresholdPercent / 100;
127
+ for (const { old, new: current } of pairs) {
128
+ if (current.mean > old.mean * thresholdFactor) {
129
+ const increasePercent = (current.mean - old.mean) / old.mean * 100;
130
+ regressions.push({
131
+ benchmark: current.key,
132
+ metric: "mean",
133
+ oldValue: old.mean,
134
+ newValue: current.mean,
135
+ increasePercent: Math.round(increasePercent * 100) / 100
136
+ });
137
+ }
138
+ }
139
+ return regressions;
140
+ }
141
+ var plugin = {
142
+ name: "performance-regression-gate",
143
+ version: "0.1.0",
144
+ description: "Compares benchmark results to detect performance regressions and reports metrics that increased beyond the configured threshold",
145
+ apiVersion: API_VERSION,
146
+ capabilities: { tools: true },
147
+ defaultConfig: { ...DEFAULTS },
148
+ configSchema: {
149
+ type: "object",
150
+ properties: {
151
+ enabled: {
152
+ type: "boolean",
153
+ default: true,
154
+ description: "Master switch."
155
+ },
156
+ thresholdPercent: {
157
+ type: "number",
158
+ minimum: 0,
159
+ maximum: 1e3,
160
+ default: 10,
161
+ description: "Percentage increase in mean execution time that counts as a regression."
162
+ }
163
+ }
164
+ },
165
+ setup(api) {
166
+ state.invocationCount = 0;
167
+ state.comparisonCount = 0;
168
+ state.regressionCount = 0;
169
+ state.missingResultsCount = 0;
170
+ state.errorCount = 0;
171
+ state.lastResult = null;
172
+ const cfg = readConfig(api.config.extensions?.["performance-regression-gate"]);
173
+ api.tools.register({
174
+ name: "perf_regression_status",
175
+ description: "Reads bench-results.json and reports performance regressions. Without baselinePath, pairs benchmarks named with (old) and (new) suffixes. With baselinePath, compares matching benchmarks across the two files.",
176
+ inputSchema: {
177
+ type: "object",
178
+ properties: {
179
+ resultsPath: {
180
+ type: "string",
181
+ default: "bench-results.json",
182
+ description: "Path to current benchmark results (JSON)."
183
+ },
184
+ baselinePath: {
185
+ type: "string",
186
+ description: "Optional path to baseline/previous results. If omitted, (old)/(new) pairs within resultsPath are compared."
187
+ },
188
+ thresholdPercent: {
189
+ type: "number",
190
+ description: "Override the configured threshold percentage."
191
+ }
192
+ }
193
+ },
194
+ permission: "auto",
195
+ category: "Diagnostics",
196
+ mutating: false,
197
+ async execute(input) {
198
+ if (!cfg.enabled) {
199
+ return { ok: false, error: "performance-regression-gate is disabled" };
200
+ }
201
+ state.invocationCount += 1;
202
+ const threshold = typeof input.thresholdPercent === "number" && input.thresholdPercent >= 0 && input.thresholdPercent <= 1e3 ? input.thresholdPercent : cfg.thresholdPercent;
203
+ const resultsPath = resolveProjectPath(input.resultsPath || "bench-results.json") ?? "";
204
+ if (!resultsPath) {
205
+ state.errorCount += 1;
206
+ return { ok: false, error: "invalid results path (must be inside project)" };
207
+ }
208
+ const results = loadResults(resultsPath);
209
+ if (!results) {
210
+ state.missingResultsCount += 1;
211
+ return {
212
+ ok: true,
213
+ hasResults: false,
214
+ thresholdPercent: threshold,
215
+ comparisons: 0,
216
+ regressions: [],
217
+ message: `No benchmark results found at ${input.resultsPath || "bench-results.json"}.`
218
+ };
219
+ }
220
+ const current = flattenResults(results);
221
+ if (current.length === 0) {
222
+ state.missingResultsCount += 1;
223
+ return {
224
+ ok: true,
225
+ hasResults: false,
226
+ thresholdPercent: threshold,
227
+ comparisons: 0,
228
+ regressions: [],
229
+ message: "No benchmarks with valid mean/period values were found."
230
+ };
231
+ }
232
+ let pairs;
233
+ if (input.baselinePath) {
234
+ const baselineResolved = resolveProjectPath(input.baselinePath) ?? "";
235
+ if (!baselineResolved) {
236
+ state.errorCount += 1;
237
+ return { ok: false, error: "invalid baseline path (must be inside project)" };
238
+ }
239
+ const baselineResults = loadResults(baselineResolved);
240
+ if (!baselineResults) {
241
+ state.errorCount += 1;
242
+ return {
243
+ ok: false,
244
+ error: `Could not read baseline results at ${input.baselinePath}.`
245
+ };
246
+ }
247
+ const baseline = flattenResults(baselineResults);
248
+ pairs = pairCrossFile(baseline, current);
249
+ } else {
250
+ pairs = pairInternal(current);
251
+ }
252
+ const regressions = comparePairs(pairs, threshold);
253
+ state.comparisonCount += pairs.length;
254
+ state.regressionCount += regressions.length;
255
+ state.lastResult = {
256
+ comparisons: pairs.length,
257
+ regressions: regressions.length,
258
+ thresholdPercent: threshold,
259
+ when: (/* @__PURE__ */ new Date()).toISOString()
260
+ };
261
+ api.metrics.counter("comparisons", pairs.length);
262
+ if (regressions.length > 0) {
263
+ api.metrics.counter("regressions", regressions.length);
264
+ }
265
+ const regression = regressions.length > 0;
266
+ return {
267
+ ok: true,
268
+ hasResults: true,
269
+ regression,
270
+ thresholdPercent: threshold,
271
+ comparisons: pairs.length,
272
+ regressions,
273
+ message: regression ? `Detected ${regressions.length} regression(s) above ${threshold}% threshold.` : `No regressions detected across ${pairs.length} benchmark comparison(s) (threshold ${threshold}%).`
274
+ };
275
+ }
276
+ });
277
+ api.log.info("performance-regression-gate plugin loaded", {
278
+ version: "0.1.0",
279
+ thresholdPercent: cfg.thresholdPercent
280
+ });
281
+ },
282
+ teardown(api) {
283
+ const final = {
284
+ invocations: state.invocationCount,
285
+ comparisons: state.comparisonCount,
286
+ regressions: state.regressionCount,
287
+ missingResults: state.missingResultsCount,
288
+ errors: state.errorCount
289
+ };
290
+ state.invocationCount = 0;
291
+ state.comparisonCount = 0;
292
+ state.regressionCount = 0;
293
+ state.missingResultsCount = 0;
294
+ state.errorCount = 0;
295
+ state.lastResult = null;
296
+ api.log.info("performance-regression-gate: teardown complete", { final });
297
+ },
298
+ async health() {
299
+ return {
300
+ ok: state.errorCount === 0,
301
+ message: state.lastResult ? `performance-regression-gate: ${state.lastResult.regressions} regression(s) across ${state.lastResult.comparisons} comparison(s) (threshold ${state.lastResult.thresholdPercent}%)` : `performance-regression-gate: ${state.invocationCount} invocation(s), ${state.comparisonCount} comparison(s)`,
302
+ counters: {
303
+ invocations: state.invocationCount,
304
+ comparisons: state.comparisonCount,
305
+ regressions: state.regressionCount,
306
+ missingResults: state.missingResultsCount,
307
+ errors: state.errorCount
308
+ },
309
+ lastResult: state.lastResult
310
+ };
311
+ }
312
+ };
313
+ var performance_regression_gate_default = plugin;
314
+
315
+ export { performance_regression_gate_default as default };
@@ -0,0 +1,35 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * plugin-stack-observer — observability over the providerRunner wrap stack.
5
+ *
6
+ * Several official plugins (llm-cache, model-router, prompt-firewall,
7
+ * token-throttle) register `AgentExtension.wrapProviderRunner` to
8
+ * intercept every provider call. The runtime composes them in
9
+ * registration order — but until now there was no way for an operator
10
+ * (or the LLM itself) to see WHICH wraps are active in what order.
11
+ *
12
+ * Each wrap plugin emits `customEvent('provider.wrap:loaded', …)` on
13
+ * setup. This plugin subscribes via `onPattern('provider.wrap:loaded')`
14
+ * and:
15
+ *
16
+ * 1. Builds an in-memory stack (preserves emission order)
17
+ * 2. Registers a system-prompt contributor that, when any wraps are
18
+ * present, emits a compact one-line block listing them. The LLM
19
+ * sees who is wrapping what, in registration order — making the
20
+ * stack visible without /diag round-trips.
21
+ *
22
+ * Disabled by default (enabled=false) because not every user wants
23
+ * the LLM to be aware of the wrap stack. The plugin_stack tool is
24
+ * always registered so operators can read the stack at runtime
25
+ * regardless of the contributor setting.
26
+ *
27
+ * Tools registered:
28
+ * - plugin_stack_status : Read the active wrap stack + load order
29
+ *
30
+ * @public
31
+ */
32
+
33
+ declare const PLUGIN: Plugin;
34
+
35
+ export { PLUGIN as default };
@@ -0,0 +1,138 @@
1
+ // src/plugin-stack-observer/index.ts
2
+ var state = {
3
+ wraps: [],
4
+ contributions: 0,
5
+ patternUnregister: null
6
+ };
7
+ function clearObserverState() {
8
+ if (state.patternUnregister) {
9
+ try {
10
+ state.patternUnregister();
11
+ } catch {
12
+ }
13
+ state.patternUnregister = null;
14
+ }
15
+ state.wraps = [];
16
+ state.contributions = 0;
17
+ }
18
+ var PLUGIN = {
19
+ name: "plugin-stack-observer",
20
+ version: "0.1.0",
21
+ description: "Observes the wrapProviderRunner stack and exposes it to operators (plugin_stack_status) and, optionally, to the LLM (system-prompt contributor).",
22
+ apiVersion: "^0.1.10",
23
+ capabilities: { tools: true },
24
+ defaultConfig: {
25
+ enabled: true,
26
+ /** Inject the stack into the LLM's system prompt. */
27
+ injectIntoSystemPrompt: false
28
+ },
29
+ configSchema: {
30
+ type: "object",
31
+ properties: {
32
+ enabled: {
33
+ type: "boolean",
34
+ default: true,
35
+ description: "Master switch. When false, no events are collected."
36
+ },
37
+ injectIntoSystemPrompt: {
38
+ type: "boolean",
39
+ default: false,
40
+ description: "When true, the active wrap stack is included as a TextBlock in every system-prompt build so the LLM knows who is wrapping its provider calls."
41
+ }
42
+ }
43
+ },
44
+ setup(api) {
45
+ clearObserverState();
46
+ const cfg = readConfig(api.config.extensions?.["plugin-stack-observer"]);
47
+ if (!cfg.enabled) {
48
+ api.log.info("plugin-stack-observer loaded (disabled)");
49
+ return;
50
+ }
51
+ state.patternUnregister = api.onPattern(
52
+ "provider.wrap:loaded",
53
+ (_eventName, payload) => {
54
+ const p = payload ?? {};
55
+ if (typeof p.plugin !== "string" || p.plugin.length === 0) return;
56
+ const wraps = Array.isArray(p.wraps) ? p.wraps.filter((w) => typeof w === "string") : [];
57
+ const kind = typeof p.kind === "string" ? p.kind : "unknown";
58
+ state.wraps.push({
59
+ plugin: p.plugin,
60
+ kind,
61
+ wraps,
62
+ loadedAt: Date.now()
63
+ });
64
+ api.metrics.counter("wrap_loaded");
65
+ }
66
+ );
67
+ if (cfg.injectIntoSystemPrompt) {
68
+ api.registerSystemPromptContributor(async () => {
69
+ if (state.wraps.length === 0) return [];
70
+ state.contributions += 1;
71
+ api.metrics.counter("system_prompt_contribution");
72
+ const lines = state.wraps.map(
73
+ (w, i) => `${i + 1}. ${w.plugin} [${w.kind}] wraps: ${w.wraps.join(", ")}`
74
+ );
75
+ return [
76
+ {
77
+ type: "text",
78
+ text: `[wrapProviderRunner stack \u2014 registration order]
79
+ The following plugins wrap every LLM provider call in this order:
80
+ ` + lines.join("\n") + `
81
+ Any failure or latency above is attributable to one of the above.`
82
+ }
83
+ ];
84
+ });
85
+ }
86
+ api.tools.register({
87
+ name: "plugin_stack_status",
88
+ description: "Returns the active wrapProviderRunner stack: plugin names, kinds, what they wrap, and registration order.",
89
+ inputSchema: { type: "object", properties: {} },
90
+ permission: "auto",
91
+ category: "Diagnostics",
92
+ mutating: false,
93
+ async execute() {
94
+ return {
95
+ ok: true,
96
+ enabled: cfg.enabled,
97
+ injectIntoSystemPrompt: cfg.injectIntoSystemPrompt,
98
+ wrapCount: state.wraps.length,
99
+ wraps: state.wraps,
100
+ contributions: state.contributions
101
+ };
102
+ }
103
+ });
104
+ api.log.info("plugin-stack-observer loaded", {
105
+ enabled: cfg.enabled,
106
+ injectIntoSystemPrompt: cfg.injectIntoSystemPrompt
107
+ });
108
+ },
109
+ teardown(api) {
110
+ const final = {
111
+ wrapCount: state.wraps.length,
112
+ contributions: state.contributions
113
+ };
114
+ clearObserverState();
115
+ api.log.info("plugin-stack-observer: teardown complete", { final });
116
+ },
117
+ async health() {
118
+ const stack = state.wraps.length === 0 ? "no wraps active" : state.wraps.map((w) => w.plugin).join(" \u2192 ");
119
+ return {
120
+ ok: true,
121
+ message: `plugin-stack-observer: ${state.wraps.length} wrap(s) loaded [${stack}]`,
122
+ wrapCount: state.wraps.length,
123
+ contributions: state.contributions,
124
+ wraps: state.wraps
125
+ };
126
+ }
127
+ };
128
+ function readConfig(raw) {
129
+ if (!raw || typeof raw !== "object") return { enabled: true, injectIntoSystemPrompt: false };
130
+ const r = raw;
131
+ return {
132
+ enabled: r["enabled"] !== false,
133
+ injectIntoSystemPrompt: r["injectIntoSystemPrompt"] === true
134
+ };
135
+ }
136
+ var plugin_stack_observer_default = PLUGIN;
137
+
138
+ export { plugin_stack_observer_default as default };
@@ -0,0 +1,35 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * pr-drafter plugin — collects work done during a session and drafts a
5
+ * pull-request description.
6
+ *
7
+ * As the session runs, the plugin listens to:
8
+ * - `tool.completed` events for `git_autocommit` (records commit messages)
9
+ * - `tool.completed` events for `write`/`edit` (records changed files)
10
+ * - `provider.response` events (records model usage)
11
+ *
12
+ * On `Stop` it composes a markdown PR body and writes it to a configurable
13
+ * file (default: `.wrongstack/PR_DRAFT.md`). A `pr_draft` tool lets the
14
+ * agent request the draft on demand.
15
+ *
16
+ * Config (`config.extensions['pr-drafter']`):
17
+ *
18
+ * ```jsonc
19
+ * {
20
+ * "enabled": true,
21
+ * "outputPath": ".wrongstack/PR_DRAFT.md",
22
+ * "writeOnStop": true,
23
+ * "includeDiff": true,
24
+ * "includeCommits": true,
25
+ * "includeFiles": true,
26
+ * "aiSummary": false
27
+ * }
28
+ * ```
29
+ *
30
+ * @public
31
+ */
32
+
33
+ declare const plugin: Plugin;
34
+
35
+ export { plugin as default };