@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,409 @@
|
|
|
1
|
+
// src/secret-scanner/index.ts
|
|
2
|
+
var BASE_PATTERNS = [
|
|
3
|
+
// LLM provider keys
|
|
4
|
+
{ type: "anthropic_key", regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
5
|
+
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
6
|
+
// GitHub
|
|
7
|
+
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
|
|
8
|
+
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
|
|
9
|
+
// AWS
|
|
10
|
+
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
|
|
11
|
+
// GCP
|
|
12
|
+
{ type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
|
|
13
|
+
// Slack
|
|
14
|
+
{ type: "slack_token", regex: /(?<![A-Za-z0-9-])xox[abpos]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
|
|
15
|
+
// Stripe
|
|
16
|
+
{ type: "stripe_key", regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g },
|
|
17
|
+
// Twilio
|
|
18
|
+
{ type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
|
|
19
|
+
// Telegram
|
|
20
|
+
{
|
|
21
|
+
type: "telegram_bot_token",
|
|
22
|
+
regex: /\/bot\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
23
|
+
},
|
|
24
|
+
// JWT
|
|
25
|
+
{
|
|
26
|
+
type: "jwt",
|
|
27
|
+
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
|
|
28
|
+
},
|
|
29
|
+
// Private keys
|
|
30
|
+
{
|
|
31
|
+
type: "private_key",
|
|
32
|
+
regex: /(?:^|\n)-----BEGIN (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----[\s\S]*?-----END (?:RSA|EC|OPENSSH|DSA|PGP)? ?PRIVATE KEY-----(?!\S)/g
|
|
33
|
+
},
|
|
34
|
+
// AI/ML provider tokens
|
|
35
|
+
{ type: "huggingface_token", regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g },
|
|
36
|
+
{ type: "replicate_token", regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
37
|
+
{ type: "perplexity_key", regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
38
|
+
{ type: "groq_key", regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
39
|
+
// Bearer tokens
|
|
40
|
+
{
|
|
41
|
+
type: "bearer_token",
|
|
42
|
+
regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?![A-Za-z0-9._~+/-])/g
|
|
43
|
+
},
|
|
44
|
+
// Database URIs
|
|
45
|
+
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s"'`]+/g },
|
|
46
|
+
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
47
|
+
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
48
|
+
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g }
|
|
49
|
+
];
|
|
50
|
+
var PATTERNS = [...BASE_PATTERNS];
|
|
51
|
+
var COMBINED_REGEX = buildCombinedRegex(PATTERNS);
|
|
52
|
+
function buildCombinedRegex(patterns) {
|
|
53
|
+
return new RegExp(
|
|
54
|
+
patterns.map((p) => `(${p.regex.source})`).join("|"),
|
|
55
|
+
"g"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
var state = {
|
|
59
|
+
blockCount: 0,
|
|
60
|
+
redactCount: 0,
|
|
61
|
+
allowCount: 0,
|
|
62
|
+
/** PostToolUse: secrets detected in tool output. */
|
|
63
|
+
leakCount: 0,
|
|
64
|
+
/** Most recent PreToolUse block — surfaced by `secret_scanner_status`. */
|
|
65
|
+
lastBlock: null,
|
|
66
|
+
/** Most recent PostToolUse leak — surfaced by `secret_scanner_status`. */
|
|
67
|
+
lastLeak: null,
|
|
68
|
+
/** PreToolUse hook handle so teardown can unregister. */
|
|
69
|
+
hookUnregister: null,
|
|
70
|
+
/** PostToolUse hook handle so teardown can unregister. */
|
|
71
|
+
postHookUnregister: null
|
|
72
|
+
};
|
|
73
|
+
function findMatches(text) {
|
|
74
|
+
if (!text) return [];
|
|
75
|
+
const found = /* @__PURE__ */ new Set();
|
|
76
|
+
COMBINED_REGEX.lastIndex = 0;
|
|
77
|
+
let m;
|
|
78
|
+
while ((m = COMBINED_REGEX.exec(text)) !== null) {
|
|
79
|
+
for (let i = 0; i < PATTERNS.length; i++) {
|
|
80
|
+
if (m[i + 1] !== void 0) {
|
|
81
|
+
found.add(PATTERNS[i].type);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (m.index === COMBINED_REGEX.lastIndex) {
|
|
86
|
+
COMBINED_REGEX.lastIndex += 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return Array.from(found);
|
|
90
|
+
}
|
|
91
|
+
function scanInput(input) {
|
|
92
|
+
if (input === null || input === void 0) return null;
|
|
93
|
+
if (typeof input === "string") {
|
|
94
|
+
const found = findMatches(input);
|
|
95
|
+
return found.length > 0 ? found : null;
|
|
96
|
+
}
|
|
97
|
+
if (Array.isArray(input)) {
|
|
98
|
+
for (const item of input) {
|
|
99
|
+
const found = scanInput(item);
|
|
100
|
+
if (found) return found;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if (typeof input === "object") {
|
|
105
|
+
for (const value of Object.values(input)) {
|
|
106
|
+
const found = scanInput(value);
|
|
107
|
+
if (found) return found;
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
function redactInput(input) {
|
|
114
|
+
if (input === null || input === void 0) return input;
|
|
115
|
+
if (typeof input === "string") {
|
|
116
|
+
return input.replace(COMBINED_REGEX, (_match, ...groups) => {
|
|
117
|
+
for (let i = 0; i < PATTERNS.length; i++) {
|
|
118
|
+
if (groups[i] !== void 0) {
|
|
119
|
+
return `[REDACTED:${PATTERNS[i].type}]`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return "[REDACTED:unknown]";
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (Array.isArray(input)) {
|
|
126
|
+
return input.map((item) => redactInput(item));
|
|
127
|
+
}
|
|
128
|
+
if (typeof input === "object") {
|
|
129
|
+
const out = {};
|
|
130
|
+
for (const [k, v] of Object.entries(input)) {
|
|
131
|
+
out[k] = redactInput(v);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
return input;
|
|
136
|
+
}
|
|
137
|
+
var DEFAULTS = {
|
|
138
|
+
matcher: "bash|write|edit",
|
|
139
|
+
postToolUseMatcher: "*",
|
|
140
|
+
mode: "block",
|
|
141
|
+
enabled: true,
|
|
142
|
+
customPatterns: []
|
|
143
|
+
};
|
|
144
|
+
function readConfig(raw) {
|
|
145
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
146
|
+
const r = raw;
|
|
147
|
+
const mode = r["mode"] === "redact" || r["mode"] === "allow" ? r["mode"] : "block";
|
|
148
|
+
const customPatterns = [];
|
|
149
|
+
if (Array.isArray(r["customPatterns"])) {
|
|
150
|
+
for (const entry of r["customPatterns"]) {
|
|
151
|
+
if (!entry || typeof entry !== "object") continue;
|
|
152
|
+
const e = entry;
|
|
153
|
+
const type = e["type"];
|
|
154
|
+
const regex = e["regex"];
|
|
155
|
+
if (typeof type !== "string" || typeof regex !== "string") continue;
|
|
156
|
+
try {
|
|
157
|
+
new RegExp(regex, "g");
|
|
158
|
+
} catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
customPatterns.push({ type, regex, description: typeof e["description"] === "string" ? e["description"] : void 0 });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
matcher: typeof r["matcher"] === "string" ? r["matcher"] : DEFAULTS.matcher,
|
|
166
|
+
postToolUseMatcher: typeof r["postToolUseMatcher"] === "string" ? r["postToolUseMatcher"] : DEFAULTS.postToolUseMatcher,
|
|
167
|
+
mode,
|
|
168
|
+
enabled: r["enabled"] !== false,
|
|
169
|
+
customPatterns
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function buildHook(cfg, log) {
|
|
173
|
+
return (input) => {
|
|
174
|
+
if (!cfg.enabled) return;
|
|
175
|
+
const toolName = input.toolName ?? "unknown";
|
|
176
|
+
const matched = scanInput(input.toolInput);
|
|
177
|
+
if (!matched) return;
|
|
178
|
+
const summary = matched.join(", ");
|
|
179
|
+
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
180
|
+
if (cfg.mode === "block") {
|
|
181
|
+
state.blockCount += 1;
|
|
182
|
+
state.lastBlock = { toolName, matchedTypes: matched, when };
|
|
183
|
+
log.warn(
|
|
184
|
+
`[secret-scanner] blocked ${toolName} \u2014 matched: ${summary}`
|
|
185
|
+
);
|
|
186
|
+
return {
|
|
187
|
+
decision: "block",
|
|
188
|
+
reason: `secret-scanner: refused to run '${toolName}' because the arguments appear to contain plaintext credentials (${summary}). Move the secret to a secret manager, env var, or config file and re-issue the call.`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (cfg.mode === "redact") {
|
|
192
|
+
const redacted = redactInput(input.toolInput);
|
|
193
|
+
if (redacted !== null && typeof redacted === "object" && !Array.isArray(redacted)) {
|
|
194
|
+
state.redactCount += 1;
|
|
195
|
+
log.info(
|
|
196
|
+
`[secret-scanner] redacted ${toolName} \u2014 matched: ${summary}`
|
|
197
|
+
);
|
|
198
|
+
return {
|
|
199
|
+
decision: "allow",
|
|
200
|
+
modifiedInput: redacted,
|
|
201
|
+
additionalContext: `secret-scanner: redacted ${matched.length} credential pattern(s) from the ${toolName} arguments before execution.`
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
state.blockCount += 1;
|
|
205
|
+
state.lastBlock = { toolName, matchedTypes: matched, when };
|
|
206
|
+
return {
|
|
207
|
+
decision: "block",
|
|
208
|
+
reason: `secret-scanner: cannot safely redact '${toolName}' input (non-object shape); refusing to run.`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
state.allowCount += 1;
|
|
212
|
+
log.warn(
|
|
213
|
+
`[secret-scanner] allow-mode: ${toolName} matched ${summary} but mode='allow' lets it through.`
|
|
214
|
+
);
|
|
215
|
+
return void 0;
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function buildPostHook(cfg, log) {
|
|
219
|
+
return (input) => {
|
|
220
|
+
if (!cfg.enabled) return;
|
|
221
|
+
const result = input.toolResult;
|
|
222
|
+
if (!result || typeof result.content !== "string") return;
|
|
223
|
+
const matched = findMatches(result.content);
|
|
224
|
+
if (matched.length === 0) return;
|
|
225
|
+
const toolName = input.toolName ?? "unknown";
|
|
226
|
+
const summary = matched.join(", ");
|
|
227
|
+
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
228
|
+
state.leakCount += 1;
|
|
229
|
+
state.lastLeak = { toolName, matchedTypes: matched, when };
|
|
230
|
+
log.warn(
|
|
231
|
+
`[secret-scanner] POST-TOOL LEAK: ${toolName} output matched ${summary}`
|
|
232
|
+
);
|
|
233
|
+
return {
|
|
234
|
+
additionalContext: `
|
|
235
|
+
\u26A0\uFE0F secret-scanner: the output of '${toolName}' contains what appears to be plaintext credential(s) (${summary}). Do NOT echo, store, commit, or transmit this value. Treat it as compromised and advise the user to rotate it.`
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
var plugin = {
|
|
240
|
+
name: "secret-scanner",
|
|
241
|
+
version: "0.1.0",
|
|
242
|
+
description: "Pre-tool hook that blocks (or optionally redacts) tools whose arguments contain plaintext credentials",
|
|
243
|
+
apiVersion: "^0.1.10",
|
|
244
|
+
capabilities: { tools: true, hooks: true },
|
|
245
|
+
defaultConfig: { ...DEFAULTS },
|
|
246
|
+
configSchema: {
|
|
247
|
+
type: "object",
|
|
248
|
+
properties: {
|
|
249
|
+
matcher: {
|
|
250
|
+
type: "string",
|
|
251
|
+
description: 'PreToolUse: Tool-name matcher (pipe-delimited case-insensitive, or "*")'
|
|
252
|
+
},
|
|
253
|
+
postToolUseMatcher: {
|
|
254
|
+
type: "string",
|
|
255
|
+
default: "*",
|
|
256
|
+
description: 'PostToolUse: Tool-name matcher for output leak detection. Default "*" scans all tool outputs.'
|
|
257
|
+
},
|
|
258
|
+
mode: {
|
|
259
|
+
type: "string",
|
|
260
|
+
enum: ["block", "redact", "allow"],
|
|
261
|
+
description: 'PreToolUse action on a match: "block" refuses the tool call, "redact" rewrites the input with [REDACTED:type], "allow" only logs'
|
|
262
|
+
},
|
|
263
|
+
enabled: { type: "boolean", default: true },
|
|
264
|
+
customPatterns: {
|
|
265
|
+
type: "array",
|
|
266
|
+
description: "User-supplied custom credential patterns. Each entry is { type: string, regex: string, description?: string }. Appended to the 20 built-in patterns at setup() time.",
|
|
267
|
+
items: {
|
|
268
|
+
type: "object",
|
|
269
|
+
properties: {
|
|
270
|
+
type: { type: "string", description: "Unique identifier (used in block reason + [REDACTED:type] label)" },
|
|
271
|
+
regex: { type: "string", description: "Regex source string (without /\u2026/g delimiters). Must be a valid JS regex." },
|
|
272
|
+
description: { type: "string", description: "Optional human-readable description" }
|
|
273
|
+
},
|
|
274
|
+
required: ["type", "regex"]
|
|
275
|
+
},
|
|
276
|
+
default: []
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
setup(api) {
|
|
281
|
+
state.blockCount = 0;
|
|
282
|
+
state.redactCount = 0;
|
|
283
|
+
state.allowCount = 0;
|
|
284
|
+
state.leakCount = 0;
|
|
285
|
+
state.lastBlock = null;
|
|
286
|
+
state.lastLeak = null;
|
|
287
|
+
state.hookUnregister = null;
|
|
288
|
+
state.postHookUnregister = null;
|
|
289
|
+
const cfg = readConfig(api.config.extensions?.["secret-scanner"]);
|
|
290
|
+
PATTERNS = [...BASE_PATTERNS];
|
|
291
|
+
for (const cp of cfg.customPatterns) {
|
|
292
|
+
try {
|
|
293
|
+
PATTERNS.push({ type: cp.type, regex: new RegExp(cp.regex, "g") });
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
COMBINED_REGEX = buildCombinedRegex(PATTERNS);
|
|
298
|
+
const log = {
|
|
299
|
+
warn: (msg, ...rest) => api.log.warn(msg, ...rest),
|
|
300
|
+
info: (msg, ...rest) => api.log.info(msg, ...rest)
|
|
301
|
+
};
|
|
302
|
+
const hook = buildHook(cfg, log);
|
|
303
|
+
state.hookUnregister = api.registerHook("PreToolUse", cfg.matcher, hook);
|
|
304
|
+
const postHook = buildPostHook(cfg, log);
|
|
305
|
+
state.postHookUnregister = api.registerHook("PostToolUse", cfg.postToolUseMatcher, postHook);
|
|
306
|
+
api.tools.register({
|
|
307
|
+
name: "secret_scanner_status",
|
|
308
|
+
description: "Reports the current secret-scanner state: pattern count, last block (if any), and per-mode invocation counters.",
|
|
309
|
+
inputSchema: { type: "object", properties: {} },
|
|
310
|
+
permission: "auto",
|
|
311
|
+
mutating: false,
|
|
312
|
+
async execute() {
|
|
313
|
+
return {
|
|
314
|
+
ok: true,
|
|
315
|
+
enabled: cfg.enabled,
|
|
316
|
+
mode: cfg.mode,
|
|
317
|
+
matcher: cfg.matcher,
|
|
318
|
+
postToolUseMatcher: cfg.postToolUseMatcher,
|
|
319
|
+
patternCount: PATTERNS.length,
|
|
320
|
+
patternTypes: PATTERNS.map((p) => p.type),
|
|
321
|
+
counters: {
|
|
322
|
+
block: state.blockCount,
|
|
323
|
+
redact: state.redactCount,
|
|
324
|
+
allow: state.allowCount,
|
|
325
|
+
leak: state.leakCount
|
|
326
|
+
},
|
|
327
|
+
lastBlock: state.lastBlock,
|
|
328
|
+
lastLeak: state.lastLeak
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
api.tools.register({
|
|
333
|
+
name: "secret_scanner_test",
|
|
334
|
+
description: "Run the scanner against a user-supplied string and report which patterns matched. Useful for verifying config and tuning the matcher.",
|
|
335
|
+
inputSchema: {
|
|
336
|
+
type: "object",
|
|
337
|
+
properties: {
|
|
338
|
+
text: { type: "string", description: "Text to scan for credential patterns" }
|
|
339
|
+
},
|
|
340
|
+
required: ["text"]
|
|
341
|
+
},
|
|
342
|
+
permission: "auto",
|
|
343
|
+
mutating: false,
|
|
344
|
+
async execute(input) {
|
|
345
|
+
const text = typeof input["text"] === "string" ? input["text"] : "";
|
|
346
|
+
const matched = findMatches(text);
|
|
347
|
+
return {
|
|
348
|
+
ok: true,
|
|
349
|
+
matched,
|
|
350
|
+
count: matched.length
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
api.log.info("secret-scanner plugin loaded", {
|
|
355
|
+
version: "0.1.0",
|
|
356
|
+
mode: cfg.mode,
|
|
357
|
+
matcher: cfg.matcher,
|
|
358
|
+
patterns: PATTERNS.length
|
|
359
|
+
});
|
|
360
|
+
},
|
|
361
|
+
teardown(api) {
|
|
362
|
+
if (state.hookUnregister) {
|
|
363
|
+
try {
|
|
364
|
+
state.hookUnregister();
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
state.hookUnregister = null;
|
|
368
|
+
}
|
|
369
|
+
if (state.postHookUnregister) {
|
|
370
|
+
try {
|
|
371
|
+
state.postHookUnregister();
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
state.postHookUnregister = null;
|
|
375
|
+
}
|
|
376
|
+
const finalCounters = {
|
|
377
|
+
block: state.blockCount,
|
|
378
|
+
redact: state.redactCount,
|
|
379
|
+
allow: state.allowCount,
|
|
380
|
+
leak: state.leakCount
|
|
381
|
+
};
|
|
382
|
+
state.blockCount = 0;
|
|
383
|
+
state.redactCount = 0;
|
|
384
|
+
state.allowCount = 0;
|
|
385
|
+
state.leakCount = 0;
|
|
386
|
+
state.lastBlock = null;
|
|
387
|
+
state.lastLeak = null;
|
|
388
|
+
PATTERNS = [...BASE_PATTERNS];
|
|
389
|
+
COMBINED_REGEX = buildCombinedRegex(PATTERNS);
|
|
390
|
+
api.log.info("secret-scanner: teardown complete", { counters: finalCounters });
|
|
391
|
+
},
|
|
392
|
+
async health() {
|
|
393
|
+
return {
|
|
394
|
+
ok: true,
|
|
395
|
+
message: state.lastLeak !== null ? `secret-scanner: last leak at ${state.lastLeak.when} on ${state.lastLeak.toolName} (${state.lastLeak.matchedTypes.join(", ")})` : state.lastBlock !== null ? `secret-scanner: last block at ${state.lastBlock.when} on ${state.lastBlock.toolName} (${state.lastBlock.matchedTypes.join(", ")})` : `secret-scanner: ${state.blockCount + state.redactCount + state.allowCount + state.leakCount} invocations, no blocks or leaks`,
|
|
396
|
+
counters: {
|
|
397
|
+
block: state.blockCount,
|
|
398
|
+
redact: state.redactCount,
|
|
399
|
+
allow: state.allowCount,
|
|
400
|
+
leak: state.leakCount
|
|
401
|
+
},
|
|
402
|
+
lastBlock: state.lastBlock,
|
|
403
|
+
lastLeak: state.lastLeak
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
var secret_scanner_default = plugin;
|
|
408
|
+
|
|
409
|
+
export { secret_scanner_default as default };
|
package/dist/semver-bump.js
CHANGED
|
@@ -6,6 +6,14 @@ import { join } from 'path';
|
|
|
6
6
|
|
|
7
7
|
// src/semver-bump/index.ts
|
|
8
8
|
var API_VERSION = "^0.1.10";
|
|
9
|
+
var state = {
|
|
10
|
+
/** Total invocations across all three tools this session. */
|
|
11
|
+
invocationCount: 0,
|
|
12
|
+
/** Per-tool invocation counts so /diag can show "bumps: 2, current: 5". */
|
|
13
|
+
perTool: { semver_bump: 0, semver_current: 0, semver_changelog: 0 },
|
|
14
|
+
/** Most recent bump result, surfaced by health() (null until first call). */
|
|
15
|
+
lastBump: null
|
|
16
|
+
};
|
|
9
17
|
function runGit(args, cwd) {
|
|
10
18
|
try {
|
|
11
19
|
return execFileSync("git", args, {
|
|
@@ -169,6 +177,9 @@ var plugin = {
|
|
|
169
177
|
}
|
|
170
178
|
},
|
|
171
179
|
setup(api) {
|
|
180
|
+
state.invocationCount = 0;
|
|
181
|
+
state.perTool = { semver_bump: 0, semver_current: 0, semver_changelog: 0 };
|
|
182
|
+
state.lastBump = null;
|
|
172
183
|
const tagPrefix = api.config.extensions?.["semver-bump"]?.["tagPrefix"] ?? "v";
|
|
173
184
|
const autoTag = api.config.extensions?.["semver-bump"]?.["autoTag"] ?? true;
|
|
174
185
|
const VALID_PARTS = ["major", "minor", "patch", "auto"];
|
|
@@ -263,6 +274,14 @@ var plugin = {
|
|
|
263
274
|
to: newVersion,
|
|
264
275
|
bump: bumpPart
|
|
265
276
|
});
|
|
277
|
+
state.lastBump = {
|
|
278
|
+
when: (/* @__PURE__ */ new Date()).toISOString(),
|
|
279
|
+
from: currentVersion,
|
|
280
|
+
to: newVersion,
|
|
281
|
+
type: bumpPart,
|
|
282
|
+
commitCount: commits.length,
|
|
283
|
+
breakingCount: commits.filter((c) => c.breaking).length
|
|
284
|
+
};
|
|
266
285
|
return {
|
|
267
286
|
ok: true,
|
|
268
287
|
currentVersion,
|
|
@@ -286,6 +305,8 @@ var plugin = {
|
|
|
286
305
|
permission: "confirm",
|
|
287
306
|
mutating: true,
|
|
288
307
|
async execute(input) {
|
|
308
|
+
state.invocationCount += 1;
|
|
309
|
+
state.perTool["semver_bump"] = (state.perTool["semver_bump"] ?? 0) + 1;
|
|
289
310
|
const cwd = input["cwd"];
|
|
290
311
|
const dryRun = input["dry_run"] ?? false;
|
|
291
312
|
const part = input["part"] ?? defaultPart;
|
|
@@ -358,6 +379,8 @@ var plugin = {
|
|
|
358
379
|
permission: "auto",
|
|
359
380
|
mutating: false,
|
|
360
381
|
async execute(input) {
|
|
382
|
+
state.invocationCount += 1;
|
|
383
|
+
state.perTool["semver_current"] = (state.perTool["semver_current"] ?? 0) + 1;
|
|
361
384
|
const cwd = input["cwd"];
|
|
362
385
|
const pkg = getPackageJson(cwd);
|
|
363
386
|
const currentVersion = pkg?.version ?? "unknown";
|
|
@@ -397,6 +420,8 @@ var plugin = {
|
|
|
397
420
|
permission: "auto",
|
|
398
421
|
mutating: false,
|
|
399
422
|
async execute(input) {
|
|
423
|
+
state.invocationCount += 1;
|
|
424
|
+
state.perTool["semver_changelog"] = (state.perTool["semver_changelog"] ?? 0) + 1;
|
|
400
425
|
const from = input["from"];
|
|
401
426
|
const to = input["to"] ?? "HEAD";
|
|
402
427
|
const cwd = input["cwd"];
|
|
@@ -435,6 +460,26 @@ var plugin = {
|
|
|
435
460
|
}
|
|
436
461
|
});
|
|
437
462
|
api.log.info("semver-bump plugin loaded", { version: "0.1.0", tagPrefix, autoTag });
|
|
463
|
+
},
|
|
464
|
+
teardown(api) {
|
|
465
|
+
const finalTotal = state.invocationCount;
|
|
466
|
+
const finalPerTool = { ...state.perTool };
|
|
467
|
+
state.invocationCount = 0;
|
|
468
|
+
state.perTool = { semver_bump: 0, semver_current: 0, semver_changelog: 0 };
|
|
469
|
+
state.lastBump = null;
|
|
470
|
+
api.log.info("semver-bump: teardown complete", {
|
|
471
|
+
invocations: finalTotal,
|
|
472
|
+
perTool: finalPerTool
|
|
473
|
+
});
|
|
474
|
+
},
|
|
475
|
+
async health() {
|
|
476
|
+
return {
|
|
477
|
+
ok: true,
|
|
478
|
+
message: state.lastBump === null ? `semver-bump: ${state.invocationCount} call(s) this session` : `semver-bump: last bump ${state.lastBump.from} \u2192 ${state.lastBump.to} (${state.lastBump.type}) at ${state.lastBump.when}`,
|
|
479
|
+
invocationCount: state.invocationCount,
|
|
480
|
+
perTool: { ...state.perTool },
|
|
481
|
+
lastBump: state.lastBump
|
|
482
|
+
};
|
|
438
483
|
}
|
|
439
484
|
};
|
|
440
485
|
var semver_bump_default = plugin;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Plugin } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* session-recap plugin — Stop hook that posts a one-page summary
|
|
5
|
+
* of the session to the project mailbox when the agent loop ends.
|
|
6
|
+
*
|
|
7
|
+
* As the session runs, the plugin accumulates lightweight metrics
|
|
8
|
+
* from the agent's EventBus:
|
|
9
|
+
*
|
|
10
|
+
* - Total input/output tokens (per model)
|
|
11
|
+
* - Tool-call counts (per tool name)
|
|
12
|
+
* - Git commits made during the session
|
|
13
|
+
* - Elapsed wall-clock time
|
|
14
|
+
*
|
|
15
|
+
* On `Stop`, the hook reads `api.session.transcriptPath` (the JSONL
|
|
16
|
+
* session log) for any extra detail the metrics stream didn't catch
|
|
17
|
+
* — last user prompt, last assistant output, final todo state —
|
|
18
|
+
* then composes a compact summary and posts it to `api.mailbox.send`
|
|
19
|
+
* with `type: 'status'` and a high-signal subject.
|
|
20
|
+
*
|
|
21
|
+
* Use cases:
|
|
22
|
+
* - End-of-day handoff — a second agent opens the mailbox and
|
|
23
|
+
* sees what the previous session finished
|
|
24
|
+
* - Audit — every session leaves a breadcrumb
|
|
25
|
+
* - Shadow agents can monitor the recap stream for anomalies
|
|
26
|
+
*
|
|
27
|
+
* Config (`config.extensions['session-recap']`):
|
|
28
|
+
*
|
|
29
|
+
* ```jsonc
|
|
30
|
+
* {
|
|
31
|
+
* "enabled": true,
|
|
32
|
+
* "subjectPrefix": "session recap: ",
|
|
33
|
+
* "includeTranscriptTail": 3,
|
|
34
|
+
* "maxBodyChars": 8000
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* Host requirements:
|
|
39
|
+
* - Requires `api.mailbox` (added in commit 31dde5ba). When absent
|
|
40
|
+
* the hook logs a one-shot warn and silently no-ops.
|
|
41
|
+
* - Requires `api.session.transcriptPath` to read the JSONL.
|
|
42
|
+
* Minimal hosts without a session writer skip the transcript tail
|
|
43
|
+
* but still post the metrics summary.
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
declare const plugin: Plugin;
|
|
49
|
+
|
|
50
|
+
export { plugin as default };
|