@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,315 @@
|
|
|
1
|
+
// src/commit-validator/index.ts
|
|
2
|
+
var API_VERSION = "^0.1.10";
|
|
3
|
+
var state = {
|
|
4
|
+
invocationCount: 0,
|
|
5
|
+
validCount: 0,
|
|
6
|
+
invalidCount: 0,
|
|
7
|
+
hookUnregister: null,
|
|
8
|
+
lastValidation: null
|
|
9
|
+
};
|
|
10
|
+
var DEFAULTS = {
|
|
11
|
+
mode: "block",
|
|
12
|
+
requireScope: false,
|
|
13
|
+
allowedTypes: [],
|
|
14
|
+
maxSubjectLength: 72,
|
|
15
|
+
bodyRequired: false,
|
|
16
|
+
minBodyLength: 10
|
|
17
|
+
};
|
|
18
|
+
function readConfig(raw) {
|
|
19
|
+
if (!raw || typeof raw !== "object") return { ...DEFAULTS };
|
|
20
|
+
const r = raw;
|
|
21
|
+
return {
|
|
22
|
+
mode: r["mode"] === "warn" ? "warn" : "block",
|
|
23
|
+
requireScope: r["requireScope"] === true,
|
|
24
|
+
allowedTypes: Array.isArray(r["allowedTypes"]) ? r["allowedTypes"].filter((x) => typeof x === "string") : [],
|
|
25
|
+
maxSubjectLength: typeof r["maxSubjectLength"] === "number" && r["maxSubjectLength"] > 0 ? r["maxSubjectLength"] : DEFAULTS.maxSubjectLength,
|
|
26
|
+
bodyRequired: r["bodyRequired"] === true,
|
|
27
|
+
minBodyLength: typeof r["minBodyLength"] === "number" && r["minBodyLength"] > 0 ? r["minBodyLength"] : DEFAULTS.minBodyLength
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
var STANDARD_TYPES = [
|
|
31
|
+
"feat",
|
|
32
|
+
"fix",
|
|
33
|
+
"docs",
|
|
34
|
+
"style",
|
|
35
|
+
"refactor",
|
|
36
|
+
"perf",
|
|
37
|
+
"test",
|
|
38
|
+
"build",
|
|
39
|
+
"ci",
|
|
40
|
+
"chore",
|
|
41
|
+
"revert"
|
|
42
|
+
];
|
|
43
|
+
function parseCommitMessage(message, cfg) {
|
|
44
|
+
const errors = [];
|
|
45
|
+
const firstLine = message.trim().split("\n")[0] ?? "";
|
|
46
|
+
if (!firstLine) {
|
|
47
|
+
return { valid: false, type: "", scope: "", subject: "", breaking: false, errors: ["empty commit message"] };
|
|
48
|
+
}
|
|
49
|
+
const match = firstLine.match(/^([a-zA-Z]+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/);
|
|
50
|
+
if (!match) {
|
|
51
|
+
errors.push(
|
|
52
|
+
`Message does not match conventional-commit format: "<type>[(scope)][!]: <description>". Got: "${firstLine.slice(0, 60)}"`
|
|
53
|
+
);
|
|
54
|
+
return { valid: false, type: "", scope: "", subject: firstLine, breaking: false, errors };
|
|
55
|
+
}
|
|
56
|
+
const [, typeRaw, scopeRaw, breakingRaw, subjectRaw] = match;
|
|
57
|
+
const type = (typeRaw ?? "").toLowerCase();
|
|
58
|
+
const scope = scopeRaw ?? "";
|
|
59
|
+
const breaking = breakingRaw === "!";
|
|
60
|
+
const subject = subjectRaw ?? "";
|
|
61
|
+
if (!type) {
|
|
62
|
+
errors.push("Missing commit type (e.g. feat, fix, docs).");
|
|
63
|
+
} else if (cfg.allowedTypes.length > 0 && !cfg.allowedTypes.includes(type)) {
|
|
64
|
+
errors.push(
|
|
65
|
+
`Type "${type}" is not in allowedTypes: ${cfg.allowedTypes.join(", ")}. Standard types: ${STANDARD_TYPES.join(", ")}.`
|
|
66
|
+
);
|
|
67
|
+
} else if (cfg.allowedTypes.length === 0 && !STANDARD_TYPES.includes(type)) ;
|
|
68
|
+
if (cfg.requireScope && !scope) {
|
|
69
|
+
errors.push("A scope is required (e.g. feat(auth): ...).");
|
|
70
|
+
}
|
|
71
|
+
if (!subject) {
|
|
72
|
+
errors.push("Missing subject description after the colon.");
|
|
73
|
+
}
|
|
74
|
+
if (subject.length > cfg.maxSubjectLength) {
|
|
75
|
+
errors.push(
|
|
76
|
+
`Subject is ${subject.length} characters \u2014 exceeds maxSubjectLength of ${cfg.maxSubjectLength}. Move details to the body.`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (subject.endsWith(".")) {
|
|
80
|
+
errors.push("Subject should not end with a period.");
|
|
81
|
+
}
|
|
82
|
+
if (cfg.bodyRequired) {
|
|
83
|
+
const lines = message.trim().split("\n");
|
|
84
|
+
const bodyStart = lines.findIndex((line, i) => i > 0 && line.trim() === "");
|
|
85
|
+
const body = bodyStart >= 0 ? lines.slice(bodyStart + 1).join("\n").trim() : "";
|
|
86
|
+
if (!body) {
|
|
87
|
+
errors.push("A commit body is required. Add a blank line after the subject, then the description.");
|
|
88
|
+
} else if (body.length < cfg.minBodyLength) {
|
|
89
|
+
errors.push(
|
|
90
|
+
`Body is ${body.length} characters \u2014 minimum is ${cfg.minBodyLength}. Add more context about what changed and why.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
valid: errors.length === 0,
|
|
96
|
+
type,
|
|
97
|
+
scope,
|
|
98
|
+
subject,
|
|
99
|
+
breaking,
|
|
100
|
+
errors
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function extractMessageFromBash(command) {
|
|
104
|
+
const flags = [];
|
|
105
|
+
const doubleQuoted = command.matchAll(/-m\s+"([^"]*)"/g);
|
|
106
|
+
const singleQuoted = command.matchAll(/-m\s+'([^']*)'/g);
|
|
107
|
+
for (const m of doubleQuoted) flags.push(m[1] ?? "");
|
|
108
|
+
for (const m of singleQuoted) flags.push(m[1] ?? "");
|
|
109
|
+
if (flags.length === 0) return null;
|
|
110
|
+
return flags.join("\n");
|
|
111
|
+
}
|
|
112
|
+
var plugin = {
|
|
113
|
+
name: "commit-validator",
|
|
114
|
+
version: "0.1.0",
|
|
115
|
+
description: "PreToolUse hook that validates conventional-commit format before git_autocommit or bash git commit runs",
|
|
116
|
+
apiVersion: API_VERSION,
|
|
117
|
+
capabilities: { tools: true, hooks: true },
|
|
118
|
+
defaultConfig: { ...DEFAULTS },
|
|
119
|
+
configSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
mode: {
|
|
123
|
+
type: "string",
|
|
124
|
+
enum: ["block", "warn"],
|
|
125
|
+
default: "block",
|
|
126
|
+
description: '"block" refuses the commit; "warn" injects errors as context but lets it through.'
|
|
127
|
+
},
|
|
128
|
+
requireScope: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
default: false,
|
|
131
|
+
description: "Require a scope in parentheses (e.g. feat(auth): ...)."
|
|
132
|
+
},
|
|
133
|
+
allowedTypes: {
|
|
134
|
+
type: "array",
|
|
135
|
+
items: { type: "string" },
|
|
136
|
+
default: [],
|
|
137
|
+
description: "Restrict to these commit types. Empty = allow all standard types (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert) plus any custom type."
|
|
138
|
+
},
|
|
139
|
+
maxSubjectLength: {
|
|
140
|
+
type: "number",
|
|
141
|
+
minimum: 10,
|
|
142
|
+
default: 72,
|
|
143
|
+
description: "Maximum subject line length in characters."
|
|
144
|
+
},
|
|
145
|
+
bodyRequired: {
|
|
146
|
+
type: "boolean",
|
|
147
|
+
default: false,
|
|
148
|
+
description: "Require a non-empty commit body after the subject line."
|
|
149
|
+
},
|
|
150
|
+
minBodyLength: {
|
|
151
|
+
type: "number",
|
|
152
|
+
minimum: 1,
|
|
153
|
+
default: 10,
|
|
154
|
+
description: "Minimum body length in characters (when bodyRequired is true)."
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
setup(api) {
|
|
159
|
+
state.invocationCount = 0;
|
|
160
|
+
state.validCount = 0;
|
|
161
|
+
state.invalidCount = 0;
|
|
162
|
+
state.hookUnregister = null;
|
|
163
|
+
state.lastValidation = null;
|
|
164
|
+
const cfg = readConfig(api.config.extensions?.["commit-validator"]);
|
|
165
|
+
const hook = (input) => {
|
|
166
|
+
const toolName = input.toolName ?? "";
|
|
167
|
+
const inp = input.toolInput ?? {};
|
|
168
|
+
let message = null;
|
|
169
|
+
if (toolName === "git_autocommit") {
|
|
170
|
+
message = inp["message"] ?? null;
|
|
171
|
+
if (!message) {
|
|
172
|
+
const type = inp["type"];
|
|
173
|
+
if (type && cfg.allowedTypes.length > 0 && !cfg.allowedTypes.includes(type)) {
|
|
174
|
+
state.invocationCount += 1;
|
|
175
|
+
state.invalidCount += 1;
|
|
176
|
+
state.lastValidation = {
|
|
177
|
+
tool: toolName,
|
|
178
|
+
valid: false,
|
|
179
|
+
type,
|
|
180
|
+
scope: "",
|
|
181
|
+
subject: "",
|
|
182
|
+
errors: [`Type "${type}" is not in allowedTypes: ${cfg.allowedTypes.join(", ")}`],
|
|
183
|
+
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
184
|
+
};
|
|
185
|
+
if (cfg.mode === "block") {
|
|
186
|
+
return {
|
|
187
|
+
decision: "block",
|
|
188
|
+
reason: `commit-validator: type "${type}" is not allowed. Allowed: ${cfg.allowedTypes.join(", ")}.`
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
decision: "allow",
|
|
193
|
+
additionalContext: `
|
|
194
|
+
\u26A0\uFE0F commit-validator: type "${type}" is not in allowedTypes.`
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
} else if (toolName === "bash") {
|
|
200
|
+
const command = inp["command"];
|
|
201
|
+
if (typeof command !== "string") return;
|
|
202
|
+
if (!/\bgit\s+commit\b/.test(command)) return;
|
|
203
|
+
message = extractMessageFromBash(command);
|
|
204
|
+
if (!message) return;
|
|
205
|
+
} else {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
state.invocationCount += 1;
|
|
209
|
+
const parsed = parseCommitMessage(message, cfg);
|
|
210
|
+
state.lastValidation = {
|
|
211
|
+
tool: toolName,
|
|
212
|
+
valid: parsed.valid,
|
|
213
|
+
type: parsed.type,
|
|
214
|
+
scope: parsed.scope,
|
|
215
|
+
subject: parsed.subject,
|
|
216
|
+
errors: parsed.errors,
|
|
217
|
+
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
218
|
+
};
|
|
219
|
+
if (parsed.valid) {
|
|
220
|
+
state.validCount += 1;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
state.invalidCount += 1;
|
|
224
|
+
const errorList = parsed.errors.map((e) => ` \u2022 ${e}`).join("\n");
|
|
225
|
+
const example = `feat: add user authentication
|
|
226
|
+
fix(api): correct response parsing
|
|
227
|
+
docs: update README`;
|
|
228
|
+
if (cfg.mode === "block") {
|
|
229
|
+
return {
|
|
230
|
+
decision: "block",
|
|
231
|
+
reason: `commit-validator: invalid conventional-commit message.
|
|
232
|
+
Errors:
|
|
233
|
+
${errorList}
|
|
234
|
+
|
|
235
|
+
Expected format: <type>[(scope)][!]: <description>
|
|
236
|
+
Examples:
|
|
237
|
+
${example}`
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
decision: "allow",
|
|
242
|
+
additionalContext: `
|
|
243
|
+
\u26A0\uFE0F commit-validator: commit message has ${parsed.errors.length} issue(s):
|
|
244
|
+
${errorList}
|
|
245
|
+
Expected: <type>[(scope)][!]: <description>`
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
state.hookUnregister = api.registerHook("PreToolUse", "bash|git_autocommit", hook);
|
|
249
|
+
api.tools.register({
|
|
250
|
+
name: "commit_validator_status",
|
|
251
|
+
description: "Reports commit-validator state: mode, allowedTypes, maxSubjectLength, and per-session valid/invalid counters.",
|
|
252
|
+
inputSchema: { type: "object", properties: {} },
|
|
253
|
+
permission: "auto",
|
|
254
|
+
category: "Git",
|
|
255
|
+
mutating: false,
|
|
256
|
+
async execute() {
|
|
257
|
+
return {
|
|
258
|
+
ok: true,
|
|
259
|
+
mode: cfg.mode,
|
|
260
|
+
requireScope: cfg.requireScope,
|
|
261
|
+
allowedTypes: cfg.allowedTypes,
|
|
262
|
+
maxSubjectLength: cfg.maxSubjectLength,
|
|
263
|
+
bodyRequired: cfg.bodyRequired,
|
|
264
|
+
minBodyLength: cfg.minBodyLength,
|
|
265
|
+
standardTypes: STANDARD_TYPES,
|
|
266
|
+
counters: {
|
|
267
|
+
invocations: state.invocationCount,
|
|
268
|
+
valid: state.validCount,
|
|
269
|
+
invalid: state.invalidCount
|
|
270
|
+
},
|
|
271
|
+
lastValidation: state.lastValidation
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
api.log.info("commit-validator plugin loaded", {
|
|
276
|
+
version: "0.1.0",
|
|
277
|
+
mode: cfg.mode,
|
|
278
|
+
requireScope: cfg.requireScope
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
teardown(api) {
|
|
282
|
+
if (state.hookUnregister) {
|
|
283
|
+
try {
|
|
284
|
+
state.hookUnregister();
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
state.hookUnregister = null;
|
|
288
|
+
}
|
|
289
|
+
const final = {
|
|
290
|
+
invocations: state.invocationCount,
|
|
291
|
+
valid: state.validCount,
|
|
292
|
+
invalid: state.invalidCount
|
|
293
|
+
};
|
|
294
|
+
state.invocationCount = 0;
|
|
295
|
+
state.validCount = 0;
|
|
296
|
+
state.invalidCount = 0;
|
|
297
|
+
state.lastValidation = null;
|
|
298
|
+
api.log.info("commit-validator: teardown complete", { final });
|
|
299
|
+
},
|
|
300
|
+
async health() {
|
|
301
|
+
return {
|
|
302
|
+
ok: true,
|
|
303
|
+
message: state.lastValidation === null ? `commit-validator: ${state.invocationCount} validation(s), ${state.validCount} valid, ${state.invalidCount} invalid` : state.lastValidation.valid ? `commit-validator: last commit "${state.lastValidation.type}: ${state.lastValidation.subject.slice(0, 40)}" was valid` : `commit-validator: last commit was invalid (${state.lastValidation.errors.length} error(s)) at ${state.lastValidation.when}`,
|
|
304
|
+
counters: {
|
|
305
|
+
invocations: state.invocationCount,
|
|
306
|
+
valid: state.validCount,
|
|
307
|
+
invalid: state.invalidCount
|
|
308
|
+
},
|
|
309
|
+
lastValidation: state.lastValidation
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
var commit_validator_default = plugin;
|
|
314
|
+
|
|
315
|
+
export { commit_validator_default as default };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Plugin } from '@wrongstack/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* config-validator plugin — instant syntax feedback after config-file
|
|
5
|
+
* writes.
|
|
6
|
+
*
|
|
7
|
+
* A broken `package.json` or malformed YAML often goes unnoticed
|
|
8
|
+
* until a later build fails with an unrelated-looking error. This
|
|
9
|
+
* plugin validates config files immediately: a `PostToolUse` hook on
|
|
10
|
+
* `write|edit` re-reads the target file from disk (what actually
|
|
11
|
+
* landed, not what the tool intended) and reports problems via
|
|
12
|
+
* `additionalContext` in the same turn:
|
|
13
|
+
*
|
|
14
|
+
* - `.json` / `.jsonc` → strict parse (JSONC comments stripped
|
|
15
|
+
* first); parse errors include the line/column
|
|
16
|
+
* - `package.json` → parse + shape checks (name/version
|
|
17
|
+
* present, `dependencies` is an object)
|
|
18
|
+
* - `.yaml` / `.yml` → structural lint: tab indentation
|
|
19
|
+
* (illegal in YAML), duplicate keys at the same indent within a
|
|
20
|
+
* block, unclosed quotes
|
|
21
|
+
* - `.toml` → duplicate table headers, duplicate
|
|
22
|
+
* keys within a table
|
|
23
|
+
*
|
|
24
|
+
* Valid files produce no output — zero noise on the happy path.
|
|
25
|
+
*
|
|
26
|
+
* Config (`config.extensions['config-validator']`):
|
|
27
|
+
*
|
|
28
|
+
* ```jsonc
|
|
29
|
+
* {
|
|
30
|
+
* "enabled": true,
|
|
31
|
+
* "extensions": [".json", ".jsonc", ".yaml", ".yml", ".toml"],
|
|
32
|
+
* "maxFileBytes": 1048576
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Toggle off with `{ "name": "config-validator", "enabled": false }`
|
|
37
|
+
* in `config.plugins`, or `"enabled": false` in the options above.
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
declare function validateJson(text: string, isJsonc: boolean, fileName: string): string[];
|
|
43
|
+
declare function validateYaml(text: string): string[];
|
|
44
|
+
declare function validateToml(text: string): string[];
|
|
45
|
+
declare function validateFile(path: string, text: string): string[];
|
|
46
|
+
declare const plugin: Plugin;
|
|
47
|
+
|
|
48
|
+
export { plugin as default, validateFile, validateJson, validateToml, validateYaml };
|