@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,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 };