@wrongstack/plugins 0.277.2 → 0.280.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.
- package/README.md +838 -0
- package/dist/auto-doc.d.ts +8 -0
- package/dist/auto-doc.js +175 -13
- package/dist/auto-escalate.d.ts +45 -0
- package/dist/auto-escalate.js +190 -0
- package/dist/branch-guard.d.ts +33 -0
- package/dist/branch-guard.js +228 -0
- package/dist/changelog-writer.d.ts +73 -0
- package/dist/changelog-writer.js +369 -0
- package/dist/checkpoint.d.ts +55 -0
- package/dist/checkpoint.js +305 -0
- package/dist/commit-validator.d.ts +33 -0
- package/dist/commit-validator.js +315 -0
- package/dist/config-validator.d.ts +48 -0
- package/dist/config-validator.js +347 -0
- package/dist/context-pins.d.ts +45 -0
- package/dist/context-pins.js +240 -0
- package/dist/cost-tracker.d.ts +40 -1
- package/dist/cost-tracker.js +105 -4
- package/dist/dep-guard.d.ts +65 -0
- package/dist/dep-guard.js +316 -0
- package/dist/diff-summary.d.ts +36 -0
- package/dist/diff-summary.js +235 -0
- package/dist/error-lens.d.ts +67 -0
- package/dist/error-lens.js +280 -0
- package/dist/format-on-save.d.ts +35 -0
- package/dist/format-on-save.js +219 -0
- package/dist/git-autocommit.js +186 -26
- package/dist/import-organizer.d.ts +52 -0
- package/dist/import-organizer.js +274 -0
- package/dist/index.d.ts +32 -6
- package/dist/index.js +10151 -1628
- package/dist/injection-shield.d.ts +49 -0
- package/dist/injection-shield.js +205 -0
- package/dist/lint-gate.d.ts +33 -0
- package/dist/lint-gate.js +394 -0
- package/dist/llm-cache.d.ts +56 -0
- package/dist/llm-cache.js +251 -0
- package/dist/loop-breaker.d.ts +43 -0
- package/dist/loop-breaker.js +241 -0
- package/dist/model-router.d.ts +69 -0
- package/dist/model-router.js +198 -0
- package/dist/notify-hub.d.ts +45 -0
- package/dist/notify-hub.js +304 -0
- package/dist/path-guard.d.ts +54 -0
- package/dist/path-guard.js +235 -0
- package/dist/prompt-firewall.d.ts +57 -0
- package/dist/prompt-firewall.js +290 -0
- package/dist/secret-scanner.d.ts +34 -0
- package/dist/secret-scanner.js +409 -0
- package/dist/semver-bump.js +45 -0
- package/dist/session-recap.d.ts +50 -0
- package/dist/session-recap.js +421 -0
- package/dist/shell-check.js +52 -4
- package/dist/spec-linker.d.ts +51 -0
- package/dist/spec-linker.js +541 -0
- package/dist/template-engine.js +19 -1
- package/dist/test-runner-gate.d.ts +37 -0
- package/dist/test-runner-gate.js +356 -0
- package/dist/todo-listener.d.ts +37 -0
- package/dist/todo-listener.js +216 -0
- package/dist/todo-tracker.d.ts +5 -0
- package/dist/todo-tracker.js +441 -0
- package/dist/token-budget.d.ts +40 -0
- package/dist/token-budget.js +254 -0
- package/dist/token-throttle.d.ts +54 -0
- package/dist/token-throttle.js +203 -0
- package/package.json +116 -12
- package/dist/json-path.d.ts +0 -18
- package/dist/json-path.js +0 -15
- package/dist/web-search.d.ts +0 -19
- 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 };
|