@wrongstack/plugins 0.277.2 → 0.280.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 (72) hide show
  1. package/README.md +838 -0
  2. package/dist/auto-doc.d.ts +8 -0
  3. package/dist/auto-doc.js +175 -13
  4. package/dist/auto-escalate.d.ts +45 -0
  5. package/dist/auto-escalate.js +190 -0
  6. package/dist/branch-guard.d.ts +33 -0
  7. package/dist/branch-guard.js +228 -0
  8. package/dist/changelog-writer.d.ts +73 -0
  9. package/dist/changelog-writer.js +369 -0
  10. package/dist/checkpoint.d.ts +55 -0
  11. package/dist/checkpoint.js +305 -0
  12. package/dist/commit-validator.d.ts +33 -0
  13. package/dist/commit-validator.js +315 -0
  14. package/dist/config-validator.d.ts +48 -0
  15. package/dist/config-validator.js +347 -0
  16. package/dist/context-pins.d.ts +45 -0
  17. package/dist/context-pins.js +240 -0
  18. package/dist/cost-tracker.d.ts +40 -1
  19. package/dist/cost-tracker.js +105 -4
  20. package/dist/dep-guard.d.ts +65 -0
  21. package/dist/dep-guard.js +316 -0
  22. package/dist/diff-summary.d.ts +36 -0
  23. package/dist/diff-summary.js +235 -0
  24. package/dist/error-lens.d.ts +67 -0
  25. package/dist/error-lens.js +280 -0
  26. package/dist/format-on-save.d.ts +35 -0
  27. package/dist/format-on-save.js +219 -0
  28. package/dist/git-autocommit.js +186 -26
  29. package/dist/import-organizer.d.ts +52 -0
  30. package/dist/import-organizer.js +274 -0
  31. package/dist/index.d.ts +32 -6
  32. package/dist/index.js +10151 -1628
  33. package/dist/injection-shield.d.ts +49 -0
  34. package/dist/injection-shield.js +205 -0
  35. package/dist/lint-gate.d.ts +33 -0
  36. package/dist/lint-gate.js +394 -0
  37. package/dist/llm-cache.d.ts +56 -0
  38. package/dist/llm-cache.js +251 -0
  39. package/dist/loop-breaker.d.ts +43 -0
  40. package/dist/loop-breaker.js +241 -0
  41. package/dist/model-router.d.ts +69 -0
  42. package/dist/model-router.js +198 -0
  43. package/dist/notify-hub.d.ts +45 -0
  44. package/dist/notify-hub.js +304 -0
  45. package/dist/path-guard.d.ts +54 -0
  46. package/dist/path-guard.js +235 -0
  47. package/dist/prompt-firewall.d.ts +57 -0
  48. package/dist/prompt-firewall.js +290 -0
  49. package/dist/secret-scanner.d.ts +34 -0
  50. package/dist/secret-scanner.js +409 -0
  51. package/dist/semver-bump.js +45 -0
  52. package/dist/session-recap.d.ts +50 -0
  53. package/dist/session-recap.js +421 -0
  54. package/dist/shell-check.js +52 -4
  55. package/dist/spec-linker.d.ts +51 -0
  56. package/dist/spec-linker.js +541 -0
  57. package/dist/template-engine.js +19 -1
  58. package/dist/test-runner-gate.d.ts +37 -0
  59. package/dist/test-runner-gate.js +356 -0
  60. package/dist/todo-listener.d.ts +37 -0
  61. package/dist/todo-listener.js +216 -0
  62. package/dist/todo-tracker.d.ts +5 -0
  63. package/dist/todo-tracker.js +441 -0
  64. package/dist/token-budget.d.ts +40 -0
  65. package/dist/token-budget.js +254 -0
  66. package/dist/token-throttle.d.ts +54 -0
  67. package/dist/token-throttle.js +203 -0
  68. package/package.json +116 -12
  69. package/dist/json-path.d.ts +0 -18
  70. package/dist/json-path.js +0 -15
  71. package/dist/web-search.d.ts +0 -19
  72. package/dist/web-search.js +0 -15
@@ -7,6 +7,14 @@ import { Plugin } from '@wrongstack/core';
7
7
  * - auto_doc: Generate and inject doc comments into JS/TS files.
8
8
  * Pass `dry_run: true` to preview without writing (replaces the former
9
9
  * `auto_doc (dry_run)` tool).
10
+ *
11
+ * LLM mode (opt-in): with `config.extensions['auto-doc'].useLlm = true`
12
+ * (or per-call `use_llm: true`) and a host that wires `api.llm`, each
13
+ * doc comment's prose is written by the LLM from the entity's signature
14
+ * and body instead of the `TODO: describe …` placeholder. Provider/model
15
+ * follow the plugin's `config.extensions['auto-doc'].llm` override, then
16
+ * the session default. Best-effort: any LLM failure (or a bad response)
17
+ * falls back to the template for that entity — a doc comment always lands.
10
18
  */
11
19
 
12
20
  declare const plugin: Plugin;
package/dist/auto-doc.js CHANGED
@@ -1,5 +1,14 @@
1
1
  // src/auto-doc/index.ts
2
2
  var AUTO_DOC_API_VERSION = "^0.1.10";
3
+ var state = {
4
+ invocationCount: 0,
5
+ /** Doc comments whose prose came from the LLM this session. */
6
+ llmDocs: 0,
7
+ /** LLM calls that failed and fell back to the template. */
8
+ llmFallbacks: 0,
9
+ /** Last invocation summary — surfaced by health() for /diag plugins. */
10
+ lastInvocation: null
11
+ };
3
12
  function parseSource(content) {
4
13
  const entities = [];
5
14
  const lines = content.split("\n");
@@ -75,6 +84,54 @@ ${params}${returns}
75
84
  */`;
76
85
  }
77
86
  }
87
+ function entitySnippet(content, entity, maxLines = 25) {
88
+ const lines = content.split("\n");
89
+ const start = entity.startLine - 1;
90
+ return lines.slice(start, start + maxLines).join("\n");
91
+ }
92
+ async function generateDocCommentLlm(entity, snippet, includeTypes, api) {
93
+ if (!api.llm) return null;
94
+ const paramList = entity.kind === "function" ? entity.params : [];
95
+ try {
96
+ const result = await api.llm.complete(
97
+ `Write documentation for this ${entity.kind} named "${entity.name}". Respond with ONLY a JSON object of the form {"summary": string, "params": {"<name>": string}, "returns": string}. summary is one concise sentence. params has one entry per parameter` + (paramList.length > 0 ? ` (${paramList.join(", ")})` : " (may be empty)") + ". returns describes the return value (empty string if none). No prose outside the JSON.\n\n```\n" + snippet + "\n```",
98
+ {
99
+ system: "You are a precise API documentation writer. Output only JSON.",
100
+ maxTokens: 400,
101
+ responseFormat: "json"
102
+ }
103
+ );
104
+ const parsed = JSON.parse(extractJsonObject(result.text));
105
+ const summary = typeof parsed.summary === "string" && parsed.summary.trim() ? parsed.summary.trim() : `Describe ${entity.name}`;
106
+ if (entity.kind !== "function") {
107
+ return `/**
108
+ * ${summary}
109
+ */`;
110
+ }
111
+ const paramLines = paramList.map((p) => {
112
+ const desc = parsed.params && typeof parsed.params[p] === "string" && parsed.params[p].trim() ? parsed.params[p].trim() : "parameter";
113
+ return ` * @param ${p} - ${desc}`;
114
+ });
115
+ const returnsDesc = typeof parsed.returns === "string" && parsed.returns.trim() ? parsed.returns.trim() : "";
116
+ const returnsLine = entity.returnType && returnsDesc ? `
117
+ * @returns ${includeTypes ? `{${entity.returnType}} ` : ""}${returnsDesc}` : entity.returnType ? `
118
+ * @returns ${includeTypes ? `{${entity.returnType}} ` : ""}return value` : "";
119
+ const paramBlock = paramLines.length > 0 ? `
120
+ ${paramLines.join("\n")}` : "";
121
+ return `/**
122
+ * ${summary}${paramBlock}${returnsLine}
123
+ */`;
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+ function extractJsonObject(text) {
129
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
130
+ const body = fenced?.[1] ?? text;
131
+ const start = body.indexOf("{");
132
+ const end = body.lastIndexOf("}");
133
+ return start >= 0 && end > start ? body.slice(start, end + 1) : body.trim();
134
+ }
78
135
  function needsDocComment(content, entity) {
79
136
  const lines = content.split("\n");
80
137
  const lineIdx = entity.startLine - 1;
@@ -92,13 +149,27 @@ function injectDocComment(content, entity, doc) {
92
149
  }
93
150
  async function runAutoDoc(input, api) {
94
151
  if (!input.files || typeof input.files !== "object" || !Array.isArray(input.files)) {
95
- return { ok: false, error: "input.files must be an array of file paths", filesProcessed: 0, changes: [] };
152
+ return {
153
+ ok: false,
154
+ error: "input.files must be an array of file paths",
155
+ filesProcessed: 0,
156
+ changes: []
157
+ };
96
158
  }
97
159
  if (input.files.length === 0) {
98
- return { ok: false, error: "input.files is empty \u2014 provide at least one file path", filesProcessed: 0, changes: [] };
160
+ return {
161
+ ok: false,
162
+ error: "input.files is empty \u2014 provide at least one file path",
163
+ filesProcessed: 0,
164
+ changes: []
165
+ };
99
166
  }
100
- const includeTypes = api.config.extensions?.["auto-doc"]?.["includeTypes"] ?? false;
167
+ const extConfig = api.config.extensions?.["auto-doc"] ?? {};
168
+ const includeTypes = extConfig["includeTypes"] ?? false;
169
+ const useLlm = (input.use_llm ?? extConfig["useLlm"] ?? false) === true && Boolean(api.llm);
170
+ const maxLlmEntities = typeof extConfig["maxLlmEntities"] === "number" && extConfig["maxLlmEntities"] >= 0 ? extConfig["maxLlmEntities"] : 25;
101
171
  const results = [];
172
+ let llmBudget = maxLlmEntities;
102
173
  for (const file of input.files) {
103
174
  try {
104
175
  const { readFileSync, writeFileSync } = await import('fs');
@@ -113,9 +184,28 @@ async function runAutoDoc(input, api) {
113
184
  let modified = content;
114
185
  for (const entity of entities) {
115
186
  if (!input.force && !needsDocComment(modified, entity)) continue;
116
- const doc = generateDocComment(entity, includeTypes);
187
+ let doc = null;
188
+ let source = "template";
189
+ if (useLlm && llmBudget > 0) {
190
+ llmBudget -= 1;
191
+ doc = await generateDocCommentLlm(
192
+ entity,
193
+ entitySnippet(modified, entity),
194
+ includeTypes,
195
+ api
196
+ );
197
+ if (doc) {
198
+ source = "llm";
199
+ state.llmDocs += 1;
200
+ api.metrics.counter("llm_docs");
201
+ } else {
202
+ state.llmFallbacks += 1;
203
+ api.metrics.counter("llm_fallbacks");
204
+ }
205
+ }
206
+ if (!doc) doc = generateDocComment(entity, includeTypes);
117
207
  modified = injectDocComment(modified, entity, doc);
118
- results.push({ file, entity: entity.name });
208
+ results.push({ file, entity: entity.name, source });
119
209
  }
120
210
  if (!input.dry_run && results.length > 0) {
121
211
  writeFileSync(file, modified, "utf-8");
@@ -125,7 +215,12 @@ async function runAutoDoc(input, api) {
125
215
  api.log.error(`auto-doc: error processing ${file}: ${err}`);
126
216
  }
127
217
  }
128
- return { ok: true, filesProcessed: input.files.length, changes: results };
218
+ return {
219
+ ok: true,
220
+ filesProcessed: input.files.length,
221
+ changes: results,
222
+ llm: useLlm ? { docs: state.llmDocs, fallbacks: state.llmFallbacks } : void 0
223
+ };
129
224
  }
130
225
  var plugin = {
131
226
  name: "auto-doc",
@@ -133,26 +228,68 @@ var plugin = {
133
228
  description: "Auto-generates JSDoc/TSDoc comments for functions, classes, types, and interfaces",
134
229
  apiVersion: AUTO_DOC_API_VERSION,
135
230
  capabilities: { tools: true, pipelines: ["toolCall"] },
136
- defaultConfig: { style: "tsdoc", includeTypes: false, dryRun: false },
231
+ defaultConfig: {
232
+ style: "tsdoc",
233
+ includeTypes: false,
234
+ dryRun: false,
235
+ useLlm: false,
236
+ maxLlmEntities: 25
237
+ },
137
238
  configSchema: {
138
239
  type: "object",
139
240
  properties: {
140
241
  style: { type: "string", enum: ["jsdoc", "tsdoc"], default: "tsdoc" },
141
242
  includeTypes: { type: "boolean", default: false },
142
- dryRun: { type: "boolean", default: false }
243
+ dryRun: { type: "boolean", default: false },
244
+ useLlm: {
245
+ type: "boolean",
246
+ default: false,
247
+ description: 'Write doc prose with the LLM (api.llm) instead of TODO placeholders. Provider/model follow extensions["auto-doc"].llm, then the session default.'
248
+ },
249
+ maxLlmEntities: {
250
+ type: "number",
251
+ minimum: 0,
252
+ default: 25,
253
+ description: "Cap on LLM-written doc comments per invocation; entities beyond it use the template."
254
+ },
255
+ llm: {
256
+ type: "object",
257
+ description: "Optional { provider, model } override for LLM doc prose."
258
+ }
143
259
  }
144
260
  },
145
261
  setup(api) {
262
+ state.invocationCount = 0;
263
+ state.llmDocs = 0;
264
+ state.llmFallbacks = 0;
265
+ state.lastInvocation = null;
146
266
  api.tools.register({
147
267
  name: "auto_doc",
148
268
  description: "Auto-generate JSDoc/TSDoc comments for functions, classes, types, and interfaces in source files. Set `dry_run: true` to preview without writing.",
149
269
  inputSchema: {
150
270
  type: "object",
151
271
  properties: {
152
- files: { type: "array", items: { type: "string" }, description: "Source files to document" },
153
- style: { type: "string", enum: ["jsdoc", "tsdoc"], default: "tsdoc", description: "Comment style" },
272
+ files: {
273
+ type: "array",
274
+ items: { type: "string" },
275
+ description: "Source files to document"
276
+ },
277
+ style: {
278
+ type: "string",
279
+ enum: ["jsdoc", "tsdoc"],
280
+ default: "tsdoc",
281
+ description: "Comment style"
282
+ },
154
283
  force: { type: "boolean", default: false, description: "Overwrite existing docstrings" },
155
- dry_run: { type: "boolean", default: false, description: "Preview generated comments without writing to files" }
284
+ dry_run: {
285
+ type: "boolean",
286
+ default: false,
287
+ description: "Preview generated comments without writing to files"
288
+ },
289
+ use_llm: {
290
+ type: "boolean",
291
+ description: "Write doc prose with the LLM (api.llm) instead of TODO placeholders. Overrides the useLlm config flag for this call."
292
+ }
156
293
  },
157
294
  required: ["files"]
158
295
  },
@@ -160,13 +297,38 @@ var plugin = {
160
297
  mutating: true,
161
298
  category: "Project",
162
299
  async execute(input) {
163
- return runAutoDoc(input, api);
300
+ const inp = input;
301
+ state.invocationCount += 1;
302
+ const result = await runAutoDoc(inp, api);
303
+ state.lastInvocation = {
304
+ when: (/* @__PURE__ */ new Date()).toISOString(),
305
+ files: Array.isArray(inp.files) ? inp.files.length : 0,
306
+ style: inp.style === "jsdoc" ? "jsdoc" : "tsdoc",
307
+ dryRun: inp.dry_run === true
308
+ };
309
+ return result;
164
310
  }
165
311
  });
166
312
  api.log.info("auto-doc plugin loaded", { version: "0.2.0", capabilities: ["auto_doc"] });
167
313
  },
168
314
  teardown(api) {
169
- api.log.info("auto-doc plugin unloaded");
315
+ const finalCount = state.invocationCount;
316
+ const finalLlmDocs = state.llmDocs;
317
+ state.invocationCount = 0;
318
+ state.llmDocs = 0;
319
+ state.llmFallbacks = 0;
320
+ state.lastInvocation = null;
321
+ api.log.info("auto-doc: teardown complete", { invocations: finalCount, llmDocs: finalLlmDocs });
322
+ },
323
+ async health() {
324
+ return {
325
+ ok: true,
326
+ message: state.lastInvocation === null ? `auto-doc: ${state.invocationCount} invocation(s) this session` : `auto-doc: last run ${state.lastInvocation.files} file(s) at ${state.lastInvocation.when} (${state.lastInvocation.dryRun ? "dry-run" : "write"}); ${state.llmDocs} LLM doc(s), ${state.llmFallbacks} fallback(s)`,
327
+ invocationCount: state.invocationCount,
328
+ llmDocs: state.llmDocs,
329
+ llmFallbacks: state.llmFallbacks,
330
+ lastInvocation: state.lastInvocation
331
+ };
170
332
  }
171
333
  };
172
334
  var auto_doc_default = plugin;
@@ -0,0 +1,45 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * auto-escalate plugin — turns transient provider errors into a graceful
5
+ * model-escalation ladder instead of a hard failure.
6
+ *
7
+ * Registers an `AgentExtension.onError` hook (wired at agent-loop.ts:636).
8
+ * When a provider call fails with a retryable error (overload, rate
9
+ * limit, timeout, 5xx), the plugin asks the agent loop to retry the turn
10
+ * with the NEXT model in a configured escalation ladder — e.g. a busy
11
+ * fast model escalates to a more capable one that may have spare
12
+ * capacity. On a non-retryable error, or once the ladder is exhausted,
13
+ * it steps aside (returns nothing) so the host's built-in error recovery
14
+ * runs unchanged.
15
+ *
16
+ * Loop constraint: the agent loop caps recovery retries at 2 per turn
17
+ * (agent-loop.ts), so at most the first ~2 rungs of the ladder are used
18
+ * before the loop fails. Order the ladder cheapest→most-capable.
19
+ *
20
+ * Safety posture: opt-in — loads inert until
21
+ * `config.extensions['auto-escalate'].enabled = true`. It never forces a
22
+ * `fail`; it only requests retries with a better model, and otherwise
23
+ * defers to the default handler.
24
+ *
25
+ * Config (`config.extensions['auto-escalate']`):
26
+ *
27
+ * ```jsonc
28
+ * {
29
+ * "enabled": false,
30
+ * "escalation": ["claude-sonnet-5", "claude-opus-4-8"],
31
+ * "retryablePatterns": ["overload", "rate.?limit", "429", "50[023]", "timeout", "ETIMEDOUT", "ECONNRESET"]
32
+ * }
33
+ * ```
34
+ *
35
+ * Tools:
36
+ * - `auto_escalate_status` — ladder, patterns, and escalation counters
37
+ *
38
+ * @public
39
+ */
40
+
41
+ declare function errorText(err: unknown): string;
42
+ declare function isRetryable(text: string, patterns: RegExp[]): boolean;
43
+ declare const plugin: Plugin;
44
+
45
+ export { plugin as default, errorText, isRetryable };
@@ -0,0 +1,190 @@
1
+ // src/auto-escalate/index.ts
2
+ var DEFAULT_PATTERNS = [
3
+ "overload",
4
+ "rate.?limit",
5
+ "\\b429\\b",
6
+ "\\b50[023]\\b",
7
+ "timeout",
8
+ "ETIMEDOUT",
9
+ "ECONNRESET",
10
+ "ECONNREFUSED",
11
+ "temporarily unavailable"
12
+ ];
13
+ function readConfig(raw) {
14
+ const base = {
15
+ enabled: false,
16
+ escalation: [],
17
+ retryablePatterns: DEFAULT_PATTERNS.map((p) => new RegExp(p, "i"))
18
+ };
19
+ if (!raw || typeof raw !== "object") return base;
20
+ const r = raw;
21
+ const escalation = Array.isArray(r["escalation"]) ? r["escalation"].filter((m) => typeof m === "string" && m.length > 0) : [];
22
+ const patterns = Array.isArray(r["retryablePatterns"]) ? r["retryablePatterns"].filter((p) => typeof p === "string" && p.length > 0).flatMap((p) => {
23
+ try {
24
+ return [new RegExp(p, "i")];
25
+ } catch {
26
+ return [];
27
+ }
28
+ }) : base.retryablePatterns;
29
+ return {
30
+ enabled: r["enabled"] === true,
31
+ escalation,
32
+ retryablePatterns: patterns.length > 0 ? patterns : base.retryablePatterns
33
+ };
34
+ }
35
+ var state = {
36
+ errorsSeen: 0,
37
+ escalationsRequested: 0,
38
+ laddersExhausted: 0,
39
+ runEscalations: 0,
40
+ lastEscalation: null,
41
+ extensionUnregister: null
42
+ };
43
+ function errorText(err) {
44
+ if (err instanceof Error) return `${err.name}: ${err.message}`;
45
+ if (typeof err === "string") return err;
46
+ try {
47
+ return JSON.stringify(err);
48
+ } catch {
49
+ return String(err);
50
+ }
51
+ }
52
+ function isRetryable(text, patterns) {
53
+ return patterns.some((re) => re.test(text));
54
+ }
55
+ var plugin = {
56
+ name: "auto-escalate",
57
+ version: "0.1.0",
58
+ description: "On retryable provider errors, retries the turn with the next model in an escalation ladder (onError). Opt-in; defers to default recovery otherwise.",
59
+ apiVersion: "^0.1.10",
60
+ capabilities: { tools: true },
61
+ defaultConfig: { enabled: false, escalation: [], retryablePatterns: DEFAULT_PATTERNS },
62
+ configSchema: {
63
+ type: "object",
64
+ properties: {
65
+ enabled: {
66
+ type: "boolean",
67
+ default: false,
68
+ description: "Master switch. OFF by default because it changes error-recovery behavior."
69
+ },
70
+ escalation: {
71
+ type: "array",
72
+ items: { type: "string" },
73
+ default: [],
74
+ description: "Ordered model ids (cheapest\u2192most-capable) to retry with on successive retryable errors."
75
+ },
76
+ retryablePatterns: {
77
+ type: "array",
78
+ items: { type: "string" },
79
+ description: "Case-insensitive regexes; an error whose text matches any is treated as retryable."
80
+ }
81
+ }
82
+ },
83
+ setup(api) {
84
+ state.errorsSeen = 0;
85
+ state.escalationsRequested = 0;
86
+ state.laddersExhausted = 0;
87
+ state.runEscalations = 0;
88
+ state.lastEscalation = null;
89
+ if (state.extensionUnregister) {
90
+ try {
91
+ state.extensionUnregister();
92
+ } catch {
93
+ }
94
+ state.extensionUnregister = null;
95
+ }
96
+ const cfg = readConfig(api.config.extensions?.["auto-escalate"]);
97
+ if (cfg.enabled && cfg.escalation.length > 0) {
98
+ state.extensionUnregister = api.extensions.register({
99
+ name: "auto-escalate",
100
+ owner: "auto-escalate",
101
+ // Fresh run → reset the ladder position.
102
+ beforeRun() {
103
+ state.runEscalations = 0;
104
+ },
105
+ onError(_ctx, err, phase) {
106
+ if (phase !== "provider") return;
107
+ state.errorsSeen += 1;
108
+ const text = errorText(err);
109
+ if (!isRetryable(text, cfg.retryablePatterns)) return;
110
+ if (state.runEscalations >= cfg.escalation.length) {
111
+ state.laddersExhausted += 1;
112
+ return;
113
+ }
114
+ const nextModel = cfg.escalation[state.runEscalations];
115
+ state.runEscalations += 1;
116
+ state.escalationsRequested += 1;
117
+ state.lastEscalation = {
118
+ to: nextModel,
119
+ reason: text.slice(0, 200),
120
+ when: (/* @__PURE__ */ new Date()).toISOString()
121
+ };
122
+ api.metrics.counter("escalations", 1, { to: nextModel });
123
+ api.log.warn("auto-escalate: retrying with escalated model", { to: nextModel });
124
+ return { action: "retry", model: nextModel };
125
+ }
126
+ });
127
+ }
128
+ api.tools.register({
129
+ name: "auto_escalate_status",
130
+ description: "Reports auto-escalate state: enabled, escalation ladder, and escalation counters.",
131
+ inputSchema: { type: "object", properties: {} },
132
+ permission: "auto",
133
+ category: "Diagnostics",
134
+ mutating: false,
135
+ async execute() {
136
+ return {
137
+ ok: true,
138
+ enabled: cfg.enabled,
139
+ escalation: cfg.escalation,
140
+ retryablePatterns: cfg.retryablePatterns.map((r) => r.source),
141
+ counters: {
142
+ errorsSeen: state.errorsSeen,
143
+ escalationsRequested: state.escalationsRequested,
144
+ laddersExhausted: state.laddersExhausted
145
+ },
146
+ lastEscalation: state.lastEscalation
147
+ };
148
+ }
149
+ });
150
+ api.log.info("auto-escalate plugin loaded", {
151
+ version: "0.1.0",
152
+ enabled: cfg.enabled,
153
+ ladder: cfg.escalation
154
+ });
155
+ },
156
+ teardown(api) {
157
+ if (state.extensionUnregister) {
158
+ try {
159
+ state.extensionUnregister();
160
+ } catch {
161
+ }
162
+ state.extensionUnregister = null;
163
+ }
164
+ const final = {
165
+ errorsSeen: state.errorsSeen,
166
+ escalationsRequested: state.escalationsRequested,
167
+ laddersExhausted: state.laddersExhausted
168
+ };
169
+ state.errorsSeen = 0;
170
+ state.escalationsRequested = 0;
171
+ state.laddersExhausted = 0;
172
+ state.runEscalations = 0;
173
+ state.lastEscalation = null;
174
+ api.log.info("auto-escalate: teardown complete", { final });
175
+ },
176
+ async health() {
177
+ return {
178
+ ok: true,
179
+ message: `auto-escalate: ${state.escalationsRequested} escalation(s) of ${state.errorsSeen} error(s), ${state.laddersExhausted} ladder(s) exhausted`,
180
+ counters: {
181
+ errorsSeen: state.errorsSeen,
182
+ escalationsRequested: state.escalationsRequested,
183
+ laddersExhausted: state.laddersExhausted
184
+ }
185
+ };
186
+ }
187
+ };
188
+ var auto_escalate_default = plugin;
189
+
190
+ export { auto_escalate_default as default, errorText, isRetryable };
@@ -0,0 +1,33 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * branch-guard plugin — PreToolUse hook that blocks commits, pushes,
5
+ * and merges to protected branches (default: main, master).
6
+ *
7
+ * Tools registered:
8
+ * - branch_guard_status : Show protected branches, mode, and counters.
9
+ *
10
+ * Hooks registered:
11
+ * - PreToolUse with matcher `bash|git_autocommit`. Inspects the tool
12
+ * input for git commit / push / merge / rebase commands (bash) or
13
+ * the tool call itself (git_autocommit). If the current branch is
14
+ * protected, the call is blocked with a clear reason.
15
+ *
16
+ * Config (`config.extensions['branch-guard']`):
17
+ *
18
+ * ```jsonc
19
+ * {
20
+ * "branches": ["main", "master"], // protected branch names
21
+ * "mode": "block", // "block" | "warn"
22
+ * "blockMerge": true, // also block merges into protected
23
+ * "blockPush": true, // also block pushes from protected
24
+ * "blockCommit": true // also block commits on protected
25
+ * }
26
+ * ```
27
+ *
28
+ * @public
29
+ */
30
+
31
+ declare const plugin: Plugin;
32
+
33
+ export { plugin as default };