@wrongstack/plugins 0.277.1 → 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
@@ -0,0 +1,55 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * checkpoint plugin — in-session file snapshots with one-call undo.
5
+ *
6
+ * Before every `write`/`edit` the plugin captures the file's current
7
+ * content (a `PreToolUse` hook reads the file from disk *before* the
8
+ * tool mutates it). Snapshots are held in an in-memory ring, so the
9
+ * agent can always roll back a bad edit — even one made outside git
10
+ * (untracked files, mid-refactor states, dirty worktrees).
11
+ *
12
+ * Tools:
13
+ * - `checkpoint_list` — list captured snapshots (newest first)
14
+ * - `checkpoint_restore` — restore a file (or all files of a
15
+ * checkpoint) to its captured content; files that did not exist
16
+ * at capture time are noted, never deleted
17
+ * - `checkpoint_create` — manually snapshot a list of files before
18
+ * a risky operation (bulk rename, codemod, script run)
19
+ *
20
+ * This deliberately complements git: `git checkout` needs a commit
21
+ * to restore to; checkpoint restores to *any* pre-edit state from
22
+ * this session, including states that were never committed.
23
+ *
24
+ * Config (`config.extensions['checkpoint']`):
25
+ *
26
+ * ```jsonc
27
+ * {
28
+ * "enabled": true,
29
+ * "autoCapture": true, // snapshot before every write/edit
30
+ * "maxSnapshots": 50, // ring size
31
+ * "maxFileBytes": 1048576 // skip files larger than this (1 MiB)
32
+ * }
33
+ * ```
34
+ *
35
+ * Toggle off with `{ "name": "checkpoint", "enabled": false }` in
36
+ * `config.plugins`, or `"enabled": false` in the options above.
37
+ *
38
+ * @public
39
+ */
40
+
41
+ interface Snapshot {
42
+ id: string;
43
+ createdAt: string;
44
+ /** What triggered the capture: 'auto:write', 'auto:edit', or 'manual'. */
45
+ origin: string;
46
+ files: Array<{
47
+ path: string;
48
+ /** null = file did not exist at capture time. */
49
+ content: string | null;
50
+ bytes: number;
51
+ }>;
52
+ }
53
+ declare const plugin: Plugin;
54
+
55
+ export { type Snapshot, plugin as default };
@@ -0,0 +1,305 @@
1
+ import { mkdirSync, writeFileSync, statSync, readFileSync } from 'fs';
2
+ import { dirname } from 'path';
3
+
4
+ // src/checkpoint/index.ts
5
+ var state = {
6
+ snapshots: [],
7
+ nextId: 1,
8
+ captures: 0,
9
+ restores: 0,
10
+ skippedLarge: 0,
11
+ hookUnregister: null
12
+ };
13
+ var DEFAULTS = {
14
+ enabled: true,
15
+ autoCapture: true,
16
+ maxSnapshots: 50,
17
+ maxFileBytes: 1048576
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
+ autoCapture: r["autoCapture"] !== false,
25
+ maxSnapshots: typeof r["maxSnapshots"] === "number" && r["maxSnapshots"] >= 1 && r["maxSnapshots"] <= 500 ? r["maxSnapshots"] : DEFAULTS.maxSnapshots,
26
+ maxFileBytes: typeof r["maxFileBytes"] === "number" && r["maxFileBytes"] >= 1024 ? r["maxFileBytes"] : DEFAULTS.maxFileBytes
27
+ };
28
+ }
29
+ function captureFile(path, maxBytes) {
30
+ try {
31
+ const st = statSync(path);
32
+ if (st.size > maxBytes) return "too-large";
33
+ const content = readFileSync(path, "utf-8");
34
+ return { path, content, bytes: st.size };
35
+ } catch {
36
+ return { path, content: null, bytes: 0 };
37
+ }
38
+ }
39
+ function pushSnapshot(snapshot, maxSnapshots) {
40
+ state.snapshots.push(snapshot);
41
+ if (state.snapshots.length > maxSnapshots) {
42
+ state.snapshots.splice(0, state.snapshots.length - maxSnapshots);
43
+ }
44
+ }
45
+ var plugin = {
46
+ name: "checkpoint",
47
+ version: "0.1.0",
48
+ description: "In-session file snapshots: auto-captures content before every write/edit and restores any pre-edit state on demand",
49
+ apiVersion: "^0.1.10",
50
+ capabilities: { tools: true, hooks: true },
51
+ defaultConfig: { ...DEFAULTS },
52
+ configSchema: {
53
+ type: "object",
54
+ properties: {
55
+ enabled: { type: "boolean", default: true, description: "Master switch." },
56
+ autoCapture: {
57
+ type: "boolean",
58
+ default: true,
59
+ description: "Snapshot the target file before every write/edit tool call."
60
+ },
61
+ maxSnapshots: {
62
+ type: "number",
63
+ minimum: 1,
64
+ maximum: 500,
65
+ default: 50,
66
+ description: "Snapshot ring size \u2014 oldest snapshots are dropped first."
67
+ },
68
+ maxFileBytes: {
69
+ type: "number",
70
+ minimum: 1024,
71
+ default: 1048576,
72
+ description: "Files larger than this are not captured."
73
+ }
74
+ }
75
+ },
76
+ setup(api) {
77
+ state.snapshots = [];
78
+ state.nextId = 1;
79
+ state.captures = 0;
80
+ state.restores = 0;
81
+ state.skippedLarge = 0;
82
+ if (state.hookUnregister) {
83
+ try {
84
+ state.hookUnregister();
85
+ } catch {
86
+ }
87
+ state.hookUnregister = null;
88
+ }
89
+ const cfg = readConfig(api.config.extensions?.["checkpoint"]);
90
+ if (cfg.enabled && cfg.autoCapture) {
91
+ const hook = (input) => {
92
+ const ti = input.toolInput ?? {};
93
+ const raw = ti["path"] ?? ti["file_path"] ?? ti["filePath"];
94
+ if (typeof raw !== "string" || raw.length === 0) return;
95
+ const captured = captureFile(raw, cfg.maxFileBytes);
96
+ if (captured === "too-large") {
97
+ state.skippedLarge += 1;
98
+ return;
99
+ }
100
+ pushSnapshot(
101
+ {
102
+ id: `cp-${state.nextId++}`,
103
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
104
+ origin: `auto:${input.toolName ?? "unknown"}`,
105
+ files: [captured]
106
+ },
107
+ cfg.maxSnapshots
108
+ );
109
+ state.captures += 1;
110
+ api.metrics.counter("captures");
111
+ };
112
+ state.hookUnregister = api.registerHook("PreToolUse", "write|edit", hook);
113
+ }
114
+ api.tools.register({
115
+ name: "checkpoint_create",
116
+ description: "Manually snapshot the current content of one or more files before a risky operation (codemod, bulk rename, script run). Restore later with checkpoint_restore.",
117
+ inputSchema: {
118
+ type: "object",
119
+ properties: {
120
+ paths: {
121
+ type: "array",
122
+ items: { type: "string" },
123
+ description: "File paths to snapshot."
124
+ },
125
+ label: { type: "string", description: "Optional label recorded as the origin." }
126
+ },
127
+ required: ["paths"]
128
+ },
129
+ permission: "auto",
130
+ category: "Safety",
131
+ mutating: false,
132
+ async execute(input) {
133
+ if (!cfg.enabled) return { ok: false, error: "checkpoint is disabled" };
134
+ const paths = Array.isArray(input.paths) ? input.paths.filter((p) => typeof p === "string" && p.length > 0) : [];
135
+ if (paths.length === 0) return { ok: false, error: "paths must not be empty" };
136
+ const files = [];
137
+ let skipped = 0;
138
+ for (const p of paths) {
139
+ const captured = captureFile(p, cfg.maxFileBytes);
140
+ if (captured === "too-large") {
141
+ skipped += 1;
142
+ state.skippedLarge += 1;
143
+ continue;
144
+ }
145
+ files.push(captured);
146
+ }
147
+ if (files.length === 0) {
148
+ return { ok: false, error: "all files were skipped (too large)" };
149
+ }
150
+ const snapshot = {
151
+ id: `cp-${state.nextId++}`,
152
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
153
+ origin: input.label?.trim() ? `manual:${input.label.trim()}` : "manual",
154
+ files
155
+ };
156
+ pushSnapshot(snapshot, cfg.maxSnapshots);
157
+ state.captures += 1;
158
+ api.metrics.counter("captures");
159
+ return {
160
+ ok: true,
161
+ id: snapshot.id,
162
+ capturedFiles: files.map((f) => ({ path: f.path, existed: f.content !== null })),
163
+ skippedTooLarge: skipped
164
+ };
165
+ }
166
+ });
167
+ api.tools.register({
168
+ name: "checkpoint_list",
169
+ description: "List captured file snapshots (newest first) with ids for checkpoint_restore.",
170
+ inputSchema: {
171
+ type: "object",
172
+ properties: {
173
+ limit: { type: "number", description: "Max entries to return (default 20)." }
174
+ }
175
+ },
176
+ permission: "auto",
177
+ category: "Safety",
178
+ mutating: false,
179
+ async execute(input) {
180
+ const limit = typeof input.limit === "number" && input.limit >= 1 ? Math.floor(input.limit) : 20;
181
+ return {
182
+ ok: true,
183
+ enabled: cfg.enabled,
184
+ autoCapture: cfg.autoCapture,
185
+ total: state.snapshots.length,
186
+ snapshots: [...state.snapshots].reverse().slice(0, limit).map((s) => ({
187
+ id: s.id,
188
+ createdAt: s.createdAt,
189
+ origin: s.origin,
190
+ files: s.files.map((f) => ({
191
+ path: f.path,
192
+ existed: f.content !== null,
193
+ bytes: f.bytes
194
+ }))
195
+ })),
196
+ counters: {
197
+ captures: state.captures,
198
+ restores: state.restores,
199
+ skippedLarge: state.skippedLarge
200
+ }
201
+ };
202
+ }
203
+ });
204
+ api.tools.register({
205
+ name: "checkpoint_restore",
206
+ description: "Restore file(s) to the content captured in a snapshot (see checkpoint_list). Restores every file in the snapshot, or a single file when `path` is given. Files that did not exist at capture time are reported but never deleted.",
207
+ inputSchema: {
208
+ type: "object",
209
+ properties: {
210
+ id: { type: "string", description: "Snapshot id (cp-N). Default: the newest snapshot." },
211
+ path: {
212
+ type: "string",
213
+ description: "Restore only this file from the snapshot (optional)."
214
+ }
215
+ }
216
+ },
217
+ permission: "confirm",
218
+ category: "Safety",
219
+ mutating: true,
220
+ async execute(input) {
221
+ if (!cfg.enabled) return { ok: false, error: "checkpoint is disabled" };
222
+ const snapshot = input.id ? state.snapshots.find((s) => s.id === input.id) : state.snapshots[state.snapshots.length - 1];
223
+ if (!snapshot) {
224
+ return {
225
+ ok: false,
226
+ error: input.id ? `no snapshot with id "${input.id}"` : "no snapshots captured yet"
227
+ };
228
+ }
229
+ const targets = input.path ? snapshot.files.filter((f) => f.path === input.path) : snapshot.files;
230
+ if (targets.length === 0) {
231
+ return { ok: false, error: `snapshot ${snapshot.id} has no entry for "${input.path}"` };
232
+ }
233
+ const restored = [];
234
+ const createdByTool = [];
235
+ const errors = [];
236
+ for (const f of targets) {
237
+ if (f.content === null) {
238
+ createdByTool.push(f.path);
239
+ continue;
240
+ }
241
+ try {
242
+ mkdirSync(dirname(f.path), { recursive: true });
243
+ writeFileSync(f.path, f.content);
244
+ restored.push(f.path);
245
+ } catch (err) {
246
+ errors.push({ path: f.path, error: err instanceof Error ? err.message : String(err) });
247
+ }
248
+ }
249
+ if (restored.length > 0) {
250
+ state.restores += 1;
251
+ api.metrics.counter("restores");
252
+ }
253
+ return {
254
+ ok: errors.length === 0,
255
+ snapshotId: snapshot.id,
256
+ restored,
257
+ notRestoredFileDidNotExist: createdByTool,
258
+ errors
259
+ };
260
+ }
261
+ });
262
+ api.log.info("checkpoint plugin loaded", {
263
+ version: "0.1.0",
264
+ enabled: cfg.enabled,
265
+ autoCapture: cfg.autoCapture,
266
+ maxSnapshots: cfg.maxSnapshots
267
+ });
268
+ },
269
+ teardown(api) {
270
+ if (state.hookUnregister) {
271
+ try {
272
+ state.hookUnregister();
273
+ } catch {
274
+ }
275
+ state.hookUnregister = null;
276
+ }
277
+ const final = {
278
+ captures: state.captures,
279
+ restores: state.restores,
280
+ skippedLarge: state.skippedLarge,
281
+ snapshotsHeld: state.snapshots.length
282
+ };
283
+ state.snapshots = [];
284
+ state.nextId = 1;
285
+ state.captures = 0;
286
+ state.restores = 0;
287
+ state.skippedLarge = 0;
288
+ api.log.info("checkpoint: teardown complete", { final });
289
+ },
290
+ async health() {
291
+ return {
292
+ ok: true,
293
+ message: `checkpoint: ${state.snapshots.length} snapshot(s) held, ${state.captures} capture(s), ${state.restores} restore(s), ${state.skippedLarge} skipped (too large)`,
294
+ counters: {
295
+ snapshotsHeld: state.snapshots.length,
296
+ captures: state.captures,
297
+ restores: state.restores,
298
+ skippedLarge: state.skippedLarge
299
+ }
300
+ };
301
+ }
302
+ };
303
+ var checkpoint_default = plugin;
304
+
305
+ export { checkpoint_default as default };
@@ -0,0 +1,33 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * commit-message-validator plugin — PreToolUse hook that validates
5
+ * conventional-commit format on `git_autocommit` and `bash` (git commit)
6
+ * before the commit is created.
7
+ *
8
+ * Tools registered:
9
+ * - commit_validator_status : Show config + per-session counters.
10
+ *
11
+ * Hooks registered:
12
+ * - PreToolUse with matcher `bash|git_autocommit`. Inspects the
13
+ * commit message (from toolInput.message for git_autocommit, or
14
+ * parsed from `-m` flag for bash git commit). If the message does
15
+ * not match the conventional-commit format, the call is blocked.
16
+ *
17
+ * Config (`config.extensions['commit-validator']`):
18
+ *
19
+ * ```jsonc
20
+ * {
21
+ * "mode": "block", // "block" | "warn"
22
+ * "requireScope": false, // require a scope in parentheses
23
+ * "allowedTypes": [], // empty = all types allowed; or ["feat","fix","docs",...]
24
+ * "maxSubjectLength": 72 // subject line character limit
25
+ * }
26
+ * ```
27
+ *
28
+ * @public
29
+ */
30
+
31
+ declare const plugin: Plugin;
32
+
33
+ export { plugin as default };