@wrongstack/plugins 0.283.0 → 0.284.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.
@@ -1,4 +1,4 @@
1
- import { execSync } from 'child_process';
1
+ import { execFile } from 'child_process';
2
2
 
3
3
  // src/branch-guard/index.ts
4
4
  var API_VERSION = "^0.1.10";
@@ -52,29 +52,34 @@ function hasDisabledPluginEntry(raw) {
52
52
  return name === "branch-guard" || name === "@wrongstack/plugins/branch-guard";
53
53
  });
54
54
  }
55
- function getCurrentBranch(cwd) {
55
+ function runGit(args, cwd, signal) {
56
+ return new Promise((resolve, reject) => {
57
+ execFile(
58
+ "git",
59
+ args,
60
+ { encoding: "utf-8", timeout: 3e3, cwd, windowsHide: true, signal },
61
+ (error, stdout) => {
62
+ if (error) reject(error);
63
+ else resolve(stdout);
64
+ }
65
+ );
66
+ });
67
+ }
68
+ async function getCurrentBranch(cwd, signal) {
56
69
  try {
57
- const branch = execSync("git branch --show-current", {
58
- encoding: "utf-8",
59
- timeout: 3e3,
60
- cwd,
61
- stdio: ["pipe", "pipe", "pipe"]
62
- }).trim();
70
+ const branch = (await runGit(["branch", "--show-current"], cwd, signal)).trim();
63
71
  return branch || null;
64
- } catch {
72
+ } catch (err) {
73
+ if (signal.aborted) throw err;
65
74
  return null;
66
75
  }
67
76
  }
68
- function detectUncommittedChanges(cwd) {
77
+ async function detectUncommittedChanges(cwd, signal) {
69
78
  try {
70
- const output = execSync("git status --porcelain", {
71
- encoding: "utf-8",
72
- timeout: 3e3,
73
- cwd,
74
- stdio: ["pipe", "pipe", "pipe"]
75
- }).trim();
79
+ const output = (await runGit(["status", "--porcelain"], cwd, signal)).trim();
76
80
  return output.length > 0;
77
- } catch {
81
+ } catch (err) {
82
+ if (signal.aborted) throw err;
78
83
  return false;
79
84
  }
80
85
  }
@@ -158,7 +163,7 @@ var plugin = {
158
163
  cfg = readHostConfig(next);
159
164
  });
160
165
  const cwd = typeof process.cwd === "function" ? process.cwd() : void 0;
161
- const hook = (input) => {
166
+ const hook = async (input, runtime = { signal: new AbortController().signal }) => {
162
167
  const toolName = input.toolName ?? "";
163
168
  const inp = input.toolInput ?? {};
164
169
  state.invocationCount += 1;
@@ -176,13 +181,13 @@ var plugin = {
176
181
  }
177
182
  if (!gitOp) return;
178
183
  if (!shouldBlock(gitOp.type, cfg)) return;
179
- const branch = getCurrentBranch(cwd);
184
+ const branch = await getCurrentBranch(cwd, runtime.signal);
180
185
  if (!branch) return;
181
186
  const protectedSet = new Set(cfg.branches);
182
187
  if (!protectedSet.has(branch)) return;
183
188
  const when = (/* @__PURE__ */ new Date()).toISOString();
184
189
  const opVerb = gitOp.type === "commit" ? "committing to" : gitOp.type === "push" ? "pushing from" : "merging into";
185
- const hasUncommitted = detectUncommittedChanges(cwd);
190
+ const hasUncommitted = await detectUncommittedChanges(cwd, runtime.signal);
186
191
  const retryStep = toolName === "git_autocommit" ? "retry git_autocommit" : `git ${gitOp.type} ...`;
187
192
  const suggestionParts = [];
188
193
  if (hasUncommitted) {
@@ -215,7 +220,13 @@ var plugin = {
215
220
  \u26A0\uFE0F branch-guard: you are ${opVerb} protected branch '${branch}'. ` + (hasUncommitted ? `You have uncommitted changes \u2014 consider \`git stash\` before switching branches. ` : "") + `Use a feature branch instead. Protected: ${cfg.branches.join(", ")}.`
216
221
  };
217
222
  };
218
- state.hookUnregister = api.registerHook("PreToolUse", "bash|git|git_autocommit", hook);
223
+ state.hookUnregister = api.registerHook("PreToolUse", "bash|git|git_autocommit", hook, {
224
+ name: "branch-guard",
225
+ stage: "validate",
226
+ timeoutMs: 7e3,
227
+ failurePolicy: "closed",
228
+ policy: true
229
+ });
219
230
  api.tools.register({
220
231
  name: "branch_guard_status",
221
232
  description: "Reports branch-guard state: protected branches, mode, and per-session invocation/block/warn counters.",
@@ -1,4 +1,5 @@
1
1
  import { mkdirSync, writeFileSync, statSync, readFileSync } from 'fs';
2
+ import { stat, readFile } from 'fs/promises';
2
3
  import { dirname, resolve, isAbsolute, relative } from 'path';
3
4
 
4
5
  // src/checkpoint/index.ts
@@ -51,6 +52,19 @@ function captureFile(path, maxBytes) {
51
52
  return { path, content: null, bytes: 0 };
52
53
  }
53
54
  }
55
+ async function captureFileForHook(path, maxBytes, signal) {
56
+ try {
57
+ signal.throwIfAborted();
58
+ const st = await stat(path);
59
+ if (st.size > maxBytes) return "too-large";
60
+ const content = await readFile(path, "utf-8");
61
+ signal.throwIfAborted();
62
+ return { path, content, bytes: st.size };
63
+ } catch (err) {
64
+ if (signal.aborted) throw err;
65
+ return { path, content: null, bytes: 0 };
66
+ }
67
+ }
54
68
  function pushSnapshot(snapshot, maxSnapshots) {
55
69
  state.snapshots.push(snapshot);
56
70
  if (state.snapshots.length > maxSnapshots) {
@@ -103,13 +117,13 @@ var plugin = {
103
117
  }
104
118
  const cfg = readConfig(api.config.extensions?.["checkpoint"]);
105
119
  if (cfg.enabled && cfg.autoCapture) {
106
- const hook = (input) => {
120
+ const hook = async (input, runtime = { signal: new AbortController().signal }) => {
107
121
  const ti = input.toolInput ?? {};
108
122
  const raw = ti["path"] ?? ti["file_path"] ?? ti["filePath"];
109
123
  if (typeof raw !== "string" || raw.length === 0) return;
110
124
  const safePath = resolveProjectPath(raw);
111
125
  if (!safePath) return;
112
- const captured = captureFile(safePath, cfg.maxFileBytes);
126
+ const captured = await captureFileForHook(safePath, cfg.maxFileBytes, runtime.signal);
113
127
  if (captured === "too-large") {
114
128
  state.skippedLarge += 1;
115
129
  return;
@@ -136,7 +150,13 @@ var plugin = {
136
150
  when: (/* @__PURE__ */ new Date()).toISOString()
137
151
  });
138
152
  };
139
- state.hookUnregister = api.registerHook("PreToolUse", "write|edit", hook);
153
+ state.hookUnregister = api.registerHook("PreToolUse", "write|edit", hook, {
154
+ name: "checkpoint-guard",
155
+ stage: "validate",
156
+ // Checkpointing is recovery automation, not an enforcement boundary.
157
+ // A transient read failure must not stall normal/YOLO writes.
158
+ failurePolicy: "open"
159
+ });
140
160
  }
141
161
  api.tools.register({
142
162
  name: "checkpoint_create",
@@ -292,7 +292,12 @@ Suggested rewrite (${suggest.model}):
292
292
  additionalContext: baseContext
293
293
  };
294
294
  };
295
- state.hookUnregister = api.registerHook("PreToolUse", "bash|git_autocommit", hook);
295
+ state.hookUnregister = api.registerHook("PreToolUse", "bash|git_autocommit", hook, {
296
+ name: "commit-validator",
297
+ stage: "validate",
298
+ failurePolicy: "closed",
299
+ policy: true
300
+ });
296
301
  api.tools.register({
297
302
  name: "commit_validator_status",
298
303
  description: "Reports commit-validator state: mode, allowedTypes, maxSubjectLength, and per-session valid/invalid counters.",
package/dist/dep-guard.js CHANGED
@@ -276,7 +276,12 @@ ${notes.map((n) => ` - ${n}`).join("\n")}`
276
276
  additionalContext: `dep-guard: this command adds ${packages.length} dependenc${packages.length === 1 ? "y" : "ies"}: ${packages.map((p) => p.name).join(", ")}. Confirm each is intentional.`
277
277
  };
278
278
  };
279
- state.hookUnregister = api.registerHook("PreToolUse", "bash|exec", hook);
279
+ state.hookUnregister = api.registerHook("PreToolUse", "bash|exec", hook, {
280
+ name: "dep-guard",
281
+ stage: "validate",
282
+ failurePolicy: "closed",
283
+ policy: true
284
+ });
280
285
  api.tools.register({
281
286
  name: "dep_guard_status",
282
287
  description: "Reports dep-guard state: deny/allow lists, mode, and counters (installs seen, blocks, warns).",