@wrongstack/plugins 0.285.0 → 0.287.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 (67) hide show
  1. package/README.md +20 -11
  2. package/dist/accessibility-auditor/index.d.ts.map +1 -1
  3. package/dist/accessibility-auditor.js +30 -26
  4. package/dist/api-compatibility-gate.js +1 -1
  5. package/dist/auto-doc.js +1 -1
  6. package/dist/auto-i18n-extractor/index.d.ts.map +1 -1
  7. package/dist/auto-i18n-extractor.js +21 -14
  8. package/dist/changelog-writer.js +1 -1
  9. package/dist/code-metrics/index.d.ts.map +1 -1
  10. package/dist/code-metrics.js +34 -27
  11. package/dist/commit-validator.js +1 -1
  12. package/dist/config-validator.js +1 -1
  13. package/dist/cron/index.d.ts.map +1 -1
  14. package/dist/cron.js +29 -0
  15. package/dist/dead-code-detector/index.d.ts.map +1 -1
  16. package/dist/dead-code-detector.js +30 -22
  17. package/dist/dep-guard.js +1 -1
  18. package/dist/diff-summary/index.d.ts.map +1 -1
  19. package/dist/diff-summary.js +19 -11
  20. package/dist/doc-sync-guard/index.d.ts.map +1 -1
  21. package/dist/doc-sync-guard.js +24 -15
  22. package/dist/duplicate-code-detector/index.d.ts.map +1 -1
  23. package/dist/duplicate-code-detector.js +27 -18
  24. package/dist/error-lens.js +1 -1
  25. package/dist/feature-flag-tracker/index.d.ts.map +1 -1
  26. package/dist/feature-flag-tracker.js +27 -19
  27. package/dist/file-watcher/index.d.ts.map +1 -1
  28. package/dist/file-watcher.js +15 -2
  29. package/dist/format-on-save/index.d.ts.map +1 -1
  30. package/dist/format-on-save.js +19 -11
  31. package/dist/git-autocommit.js +1 -1
  32. package/dist/import-organizer/index.d.ts.map +1 -1
  33. package/dist/import-organizer.js +22 -13
  34. package/dist/index.d.ts +4 -4
  35. package/dist/index.js +1007 -759
  36. package/dist/interface-contract-guard/index.d.ts.map +1 -1
  37. package/dist/interface-contract-guard.js +27 -19
  38. package/dist/license-audit-gate/index.d.ts.map +1 -1
  39. package/dist/license-audit-gate.js +1 -2
  40. package/dist/migration-planner/index.d.ts +12 -2
  41. package/dist/migration-planner/index.d.ts.map +1 -1
  42. package/dist/migration-planner.js +210 -28
  43. package/dist/pr-drafter.js +1 -1
  44. package/dist/refactor-suggester/index.d.ts.map +1 -1
  45. package/dist/refactor-suggester.js +37 -29
  46. package/dist/release-notes-generator/index.d.ts +3 -1
  47. package/dist/release-notes-generator/index.d.ts.map +1 -1
  48. package/dist/release-notes-generator.js +152 -12
  49. package/dist/runtime/index.d.ts +12 -0
  50. package/dist/runtime/index.d.ts.map +1 -1
  51. package/dist/runtime/llm.d.ts +33 -0
  52. package/dist/runtime/llm.d.ts.map +1 -0
  53. package/dist/runtime.js +57 -2
  54. package/dist/schema-evolution-guard/index.d.ts.map +1 -1
  55. package/dist/schema-evolution-guard.js +21 -12
  56. package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
  57. package/dist/security-hotspot-scanner.js +25 -16
  58. package/dist/session-recap.js +1 -1
  59. package/dist/spec-linker.js +1007 -759
  60. package/dist/test-generator/index.d.ts +5 -3
  61. package/dist/test-generator/index.d.ts.map +1 -1
  62. package/dist/test-generator.js +183 -41
  63. package/dist/test-runner-gate/index.d.ts.map +1 -1
  64. package/dist/test-runner-gate.js +65 -43
  65. package/dist/type-gate/index.d.ts.map +1 -1
  66. package/dist/type-gate.js +5 -13
  67. package/package.json +3 -3
package/README.md CHANGED
@@ -68,7 +68,7 @@ under the `BUILTIN_PLUGIN_FACTORIES` array. The canonical default is
68
68
  | 41 | [`knowledge-graph`](./src/knowledge-graph) | `kg_add_fact`, `kg_query`, `kg_remove_fact`, `kg_status` | System prompt contributor | Persistent structured (subject, relation, object) project facts |
69
69
  | 42 | [`dead-code-detector`](./src/dead-code-detector) | `dead_code_scan` | `PostToolUse` | Regex-based scan for exported identifiers that appear unused |
70
70
  | 43 | [`dependency-vulnerability-gate`](./src/dependency-vulnerability-gate) | `dependency_audit_status` | `PostToolUse` | Opt-in blocking `npm/pnpm audit` after installs |
71
- | 44 | [`migration-planner`](./src/migration-planner) | `migration_plan`, `migration_status` | `PostToolUse` | Reads changelogs and produces version-migration checklists |
71
+ | 44 | [`migration-planner`](./src/migration-planner) | `migration_plan`, `migration_status` | `PostToolUse` | Evidence-backed checklists + optional `api.llm` risk/verification analysis |
72
72
  | 45 | [`semantic-search-indexer`](./src/semantic-search-indexer) | `semantic_search`, `semantic_index_status` | — | Lightweight keyword index over project source files |
73
73
  | 46 | [`auto-i18n-extractor`](./src/auto-i18n-extractor) | `i18n_extract`, `i18n_status` | `PostToolUse` | Detects hardcoded user-facing strings and suggests i18n keys |
74
74
  | 47 | [`doc-sync-guard`](./src/doc-sync-guard) | `doc_sync_status` | `PostToolUse` | Warns when README/docs edits omit recently changed source files |
@@ -82,11 +82,12 @@ under the `BUILTIN_PLUGIN_FACTORIES` array. The canonical default is
82
82
  | 55 | [`duplicate-code-detector`](./src/duplicate-code-detector) | `detect_duplicate_code`, `duplicate_code_status` | `PostToolUse` | Detects duplicate/similar code blocks using normalized-line fingerprints |
83
83
  | 56 | [`code-metrics`](./src/code-metrics) | `measure_code_metrics`, `metrics_status` | `PostToolUse` | Computes LOC, comment ratio, function count, and approximate cyclomatic complexity |
84
84
  | 57 | [`refactor-suggester`](./src/refactor-suggester) | `suggest_refactors`, `refactor_status` | `PostToolUse` | Flags long functions, deep nesting, many parameters, magic numbers, console.log |
85
- | 58 | [`test-generator`](./src/test-generator) | `generate_unit_tests` | — | Generates a unit-test skeleton from exported functions/classes |
86
- | 59 | [`release-notes-generator`](./src/release-notes-generator) | `generate_release_notes` | — | Groups conventional commits into a release-notes markdown draft |
85
+ | 58 | [`test-generator`](./src/test-generator) | `generate_unit_tests` | — | Framework-correct skeletons + optional behavior-focused `api.llm` tests |
86
+ | 59 | [`release-notes-generator`](./src/release-notes-generator) | `generate_release_notes` | — | Traceable notes + optional hash-preserving `api.llm` polish |
87
87
  | 60 | [`smart-rename`](./src/smart-rename) | `smart_rename` | — | Whole-word symbol rename with preview and optional apply |
88
88
  | 61 | [`feature-flag-tracker`](./src/feature-flag-tracker) | `scan_feature_flags`, `feature_flag_status` | `PostToolUse` | Scans source files for feature-flag usage and reports flag inventory |
89
89
  | 62 | [`interface-contract-guard`](./src/interface-contract-guard) | `check_interface_contracts`, `interface_contract_status` | `PostToolUse` | Warns when TypeScript interfaces change or lack implementers |
90
+ | 63 | [`plugin-stack-observer`](./src/plugin-stack-observer) | `plugin_stack_status` | Event listener + optional system prompt contributor | Reports active provider-runner wrappers in registration order |
90
91
 
91
92
  ### Removed plugins (use built-in tools instead)
92
93
 
@@ -97,7 +98,7 @@ under the `BUILTIN_PLUGIN_FACTORIES` array. The canonical default is
97
98
 
98
99
  If a user lists either name in `config.plugins`, the loader emits a
99
100
  one-shot `log.warn` and skips loading. See
100
- [`DEPRECATED_PLUGIN_NAMES`](../../cli/src/wiring/plugins.ts)
101
+ [`DEPRECATED_PLUGIN_NAMES`](../cli/src/wiring/plugins.ts)
101
102
  in `packages/cli/src/wiring/plugins.ts` for the canonical list and
102
103
  migration hints.
103
104
 
@@ -701,7 +702,7 @@ context. Increase `includeTranscriptTail` for more history.
701
702
  Two hooks working together:
702
703
 
703
704
  **PostToolUse** (always active when the plugin is enabled):
704
- scans markdown files for *unlinked* references to one of the 62
705
+ scans markdown files for *unlinked* references to one of the 63
705
706
  known plugins and surfaces them to the LLM via
706
707
  `additionalContext`. The plugin does NOT modify the file — it
707
708
  only injects a low-noise context block listing the unlinked
@@ -1043,18 +1044,26 @@ There are two surfaces for plugin configuration:
1043
1044
  {
1044
1045
  "plugins": [
1045
1046
  { "name": "auto-doc", "enabled": true },
1046
- { "name": "git-autocommit", "options": { "conventionalCommits": true } }
1047
+ {
1048
+ "name": "git-autocommit",
1049
+ "enabled": true,
1050
+ "options": { "conventionalCommits": true }
1051
+ }
1047
1052
  ]
1048
1053
  }
1049
1054
  ```
1050
- 2. **Options** — `config.extensions["<plugin-name>"]` stores each
1051
- plugin's runtime options. The plugin's `configSchema` validates
1052
- this section before `setup()` runs.
1055
+ 2. **Options** — set an entry's `options` field or use
1056
+ `config.extensions["<plugin-name>"]`; the CLI merges both before `setup()`.
1057
+ If both are present, `config.extensions` wins for duplicate keys.
1053
1058
 
1054
1059
  ```jsonc
1055
1060
  {
1056
- "plugins": {
1057
- "auto-doc": { "enabled": true },
1061
+ "plugins": [
1062
+ { "name": "auto-doc", "enabled": true },
1063
+ { "name": "git-autocommit", "enabled": true },
1064
+ { "name": "secret-scanner", "enabled": true }
1065
+ ],
1066
+ "extensions": {
1058
1067
  "git-autocommit": { "conventionalCommits": true },
1059
1068
  "secret-scanner": { "mode": "block", "matcher": "bash|write|edit" }
1060
1069
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/accessibility-auditor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAQ/C,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,qBAAqB,GACrB,0BAA0B,GAC1B,qBAAqB,GACrB,cAAc,CAAC;AAEnB,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AA6SD,QAAA,MAAM,MAAM,EAAE,MA0Nb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/accessibility-auditor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAS/C,MAAM,MAAM,QAAQ,GAChB,aAAa,GACb,qBAAqB,GACrB,0BAA0B,GAC1B,qBAAqB,GACrB,cAAc,CAAC;AAEnB,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AA+RD,QAAA,MAAM,MAAM,EAAE,MA0Nb,CAAC;eAEa,MAAM"}
@@ -1,6 +1,25 @@
1
1
  // src/accessibility-auditor/index.ts
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { isAbsolute, relative, resolve } from "node:path";
3
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { basename, isAbsolute, relative, resolve } from "node:path";
7
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
8
+ function hasLeadingDash(arg) {
9
+ return arg.length > 0 && arg.startsWith("-");
10
+ }
11
+ function withinProjectPath(projectRoot, candidate) {
12
+ if (candidate.length === 0 || candidate.length > 4096) return false;
13
+ if (hasLeadingDash(candidate)) return false;
14
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
15
+ const rel = relative(projectRoot, resolved);
16
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
17
+ }
18
+ function withinProject(p) {
19
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
20
+ }
21
+
22
+ // src/accessibility-auditor/index.ts
4
23
  var API_VERSION = "^0.1.10";
5
24
  var state = {
6
25
  auditCount: 0,
@@ -28,16 +47,6 @@ function readConfig(raw) {
28
47
  onWriteEdit: r["onWriteEdit"] !== false
29
48
  };
30
49
  }
31
- function withinProject(p) {
32
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
33
- const root = process.cwd();
34
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
35
- const rel = relative(root, resolved);
36
- if (rel === "" || rel === ".") return true;
37
- if (rel.startsWith("..")) return false;
38
- if (isAbsolute(rel)) return false;
39
- return true;
40
- }
41
50
  function normalizeExtensions(exts) {
42
51
  return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
43
52
  }
@@ -63,7 +72,7 @@ function collectUiFiles(root, exts) {
63
72
  return;
64
73
  }
65
74
  for (const entry of entries) {
66
- const full = resolve(dir, entry);
75
+ const full = resolve2(dir, entry);
67
76
  let st;
68
77
  try {
69
78
  st = statSync(full);
@@ -109,7 +118,7 @@ function auditFile(filePath, projectRoot) {
109
118
  const idsByValue = /* @__PURE__ */ new Map();
110
119
  function add(line, rule, severity, message) {
111
120
  findings.push({
112
- file: relative(projectRoot, filePath),
121
+ file: relative2(projectRoot, filePath),
113
122
  line,
114
123
  rule,
115
124
  severity,
@@ -119,24 +128,21 @@ function auditFile(filePath, projectRoot) {
119
128
  for (let i = 0; i < lines.length; i++) {
120
129
  const line = lines[i];
121
130
  const lineNo = i + 1;
122
- let idMatch;
123
- while ((idMatch = ATTR_ID.exec(line)) !== null) {
131
+ for (const idMatch of line.matchAll(ATTR_ID)) {
124
132
  const id = idMatch[1];
125
133
  const list = idsByValue.get(id) ?? [];
126
134
  list.push(lineNo);
127
135
  idsByValue.set(id, list);
128
136
  }
129
137
  ATTR_ID.lastIndex = 0;
130
- let imgMatch;
131
- while ((imgMatch = TAG_IMG.exec(line)) !== null) {
138
+ for (const imgMatch of line.matchAll(TAG_IMG)) {
132
139
  const tag = imgMatch[0];
133
140
  if (!hasMeaningfulAlt(tag)) {
134
141
  add(lineNo, "missing-alt", "error", "<img> is missing meaningful alt text");
135
142
  }
136
143
  }
137
144
  TAG_IMG.lastIndex = 0;
138
- let inputMatch;
139
- while ((inputMatch = TAG_INPUT.exec(line)) !== null) {
145
+ for (const inputMatch of line.matchAll(TAG_INPUT)) {
140
146
  const tag = inputMatch[0];
141
147
  const typeMatch = tag.match(/\btype\s*=\s*["']?([^"'\s>]*)["']?/i);
142
148
  const type = typeMatch ? typeMatch[1].toLowerCase() : "text";
@@ -161,8 +167,7 @@ function auditFile(filePath, projectRoot) {
161
167
  }
162
168
  }
163
169
  TAG_INPUT.lastIndex = 0;
164
- let buttonMatch;
165
- while ((buttonMatch = TAG_BUTTON.exec(line)) !== null) {
170
+ for (const buttonMatch of line.matchAll(TAG_BUTTON)) {
166
171
  const tag = buttonMatch[0];
167
172
  const inner = buttonMatch[1] ?? "";
168
173
  const hasText = inner.replace(/\s+/g, "").length > 0;
@@ -173,8 +178,7 @@ function auditFile(filePath, projectRoot) {
173
178
  }
174
179
  }
175
180
  TAG_BUTTON.lastIndex = 0;
176
- let inputButtonMatch;
177
- while ((inputButtonMatch = INPUT_BUTTON.exec(line)) !== null) {
181
+ for (const inputButtonMatch of line.matchAll(INPUT_BUTTON)) {
178
182
  const tag = inputButtonMatch[0];
179
183
  if (!ATTR_VALUE.test(tag) && !ATTR_ARIA_LABEL.test(tag) && !ATTR_TITLE.test(tag)) {
180
184
  add(lineNo, "missing-button-text", "error", `<input type="${inputButtonMatch[1]}"> is missing value/aria-label/title`);
@@ -193,7 +197,7 @@ function auditFile(filePath, projectRoot) {
193
197
  }
194
198
  function auditPath(rawPath, cfg) {
195
199
  const root = process.cwd();
196
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
200
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
197
201
  const exts = normalizeExtensions(cfg.includeExtensions);
198
202
  const files = collectUiFiles(resolved, exts);
199
203
  const findings = [];
@@ -203,7 +207,7 @@ function auditPath(rawPath, cfg) {
203
207
  if (findings.length >= cfg.maxFindings) break;
204
208
  }
205
209
  return {
206
- path: relative(root, resolved),
210
+ path: relative2(root, resolved),
207
211
  findings: findings.slice(0, cfg.maxFindings),
208
212
  fileCount: files.length
209
213
  };
@@ -294,7 +298,7 @@ var plugin = {
294
298
  }
295
299
  return { additionalContext: summary };
296
300
  };
297
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
301
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
298
302
  api.tools.register({
299
303
  name: "a11y_audit",
300
304
  description: "Audit a file or directory for accessibility issues. Scans .tsx/.jsx/.html/.vue files for missing alt text, missing labels, low-contrast placeholders, missing button text, and duplicate ids.",
@@ -279,7 +279,7 @@ var plugin = {
279
279
  }
280
280
  return { additionalContext: message };
281
281
  };
282
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
282
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
283
283
  api.tools.register({
284
284
  name: "api_compat_status",
285
285
  description: "Reports api-compatibility-gate state: severity, entry-point patterns, and per-session counters.",
package/dist/auto-doc.js CHANGED
@@ -241,7 +241,7 @@ var plugin = {
241
241
  version: "0.2.0",
242
242
  description: "Auto-generates JSDoc/TSDoc comments for functions, classes, types, and interfaces",
243
243
  apiVersion: AUTO_DOC_API_VERSION,
244
- capabilities: { tools: true, pipelines: ["toolCall"] },
244
+ capabilities: { tools: true, pipelines: ["toolCall"], llm: true },
245
245
  defaultConfig: {
246
246
  style: "tsdoc",
247
247
  includeTypes: false,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auto-i18n-extractor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAiL/C,QAAA,MAAM,MAAM,EAAE,MAsQb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auto-i18n-extractor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAwK/C,QAAA,MAAM,MAAM,EAAE,MAsQb,CAAC;eAEa,MAAM"}
@@ -1,6 +1,24 @@
1
1
  // src/auto-i18n-extractor/index.ts
2
2
  import { readFileSync } from "node:fs";
3
- import { isAbsolute, relative, resolve } from "node:path";
3
+
4
+ // src/runtime/index.ts
5
+ import { basename, isAbsolute, relative, resolve } from "node:path";
6
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
7
+ function hasLeadingDash(arg) {
8
+ return arg.length > 0 && arg.startsWith("-");
9
+ }
10
+ function withinProjectPath(projectRoot, candidate) {
11
+ if (candidate.length === 0 || candidate.length > 4096) return false;
12
+ if (hasLeadingDash(candidate)) return false;
13
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
14
+ const rel = relative(projectRoot, resolved);
15
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
16
+ }
17
+ function withinProject(p) {
18
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
19
+ }
20
+
21
+ // src/auto-i18n-extractor/index.ts
4
22
  var API_VERSION = "^0.1.10";
5
23
  var state = {
6
24
  filesScanned: 0,
@@ -41,16 +59,6 @@ function readConfig(raw) {
41
59
  excludeAttributes: Array.isArray(r["excludeAttributes"]) ? r["excludeAttributes"].filter((x) => typeof x === "string") : DEFAULTS.excludeAttributes
42
60
  };
43
61
  }
44
- function withinProject(p) {
45
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
46
- const root = process.cwd();
47
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
48
- const rel = relative(root, resolved);
49
- if (rel === "" || rel === ".") return true;
50
- if (rel.startsWith("..")) return false;
51
- if (isAbsolute(rel)) return false;
52
- return true;
53
- }
54
62
  function fileExtension(p) {
55
63
  const dot = p.lastIndexOf(".");
56
64
  return dot > 0 ? p.slice(dot).toLowerCase() : "";
@@ -83,8 +91,7 @@ function extractStrings(content, cfg) {
83
91
  for (let i = 0; i < lines.length; i += 1) {
84
92
  const line = lines[i];
85
93
  stringRegex.lastIndex = 0;
86
- let match;
87
- while ((match = stringRegex.exec(line)) !== null) {
94
+ for (const match of line.matchAll(stringRegex)) {
88
95
  const value = match[2] ?? "";
89
96
  const quoteIndex = match.index + (match[1]?.length ?? 1);
90
97
  if (!looksLikeUserText(value, cfg.minLength)) continue;
@@ -202,7 +209,7 @@ var plugin = {
202
209
  ${lines.join("\n")}${more}`;
203
210
  return { additionalContext: context };
204
211
  };
205
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
212
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
206
213
  api.tools.register({
207
214
  name: "i18n_extract",
208
215
  description: "Scan a UI source file (.tsx/.jsx/.vue) for hardcoded user-facing string literals and return suggested i18n keys.",
@@ -138,7 +138,7 @@ var plugin = {
138
138
  version: "0.1.0",
139
139
  description: "Collects session work (commits, edits, manual notes) and writes Keep-a-Changelog entries under [Unreleased] on demand",
140
140
  apiVersion: "^0.1.10",
141
- capabilities: { tools: true },
141
+ capabilities: { tools: true, llm: true },
142
142
  defaultConfig: { ...DEFAULTS },
143
143
  configSchema: {
144
144
  type: "object",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/code-metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA8P/C,QAAA,MAAM,MAAM,EAAE,MAgMb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/code-metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAoP/C,QAAA,MAAM,MAAM,EAAE,MAgMb,CAAC;eAEa,MAAM"}
@@ -1,6 +1,25 @@
1
1
  // src/code-metrics/index.ts
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { extname, isAbsolute, relative, resolve } from "node:path";
3
+ import { extname, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { basename, isAbsolute, relative, resolve } from "node:path";
7
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
8
+ function hasLeadingDash(arg) {
9
+ return arg.length > 0 && arg.startsWith("-");
10
+ }
11
+ function withinProjectPath(projectRoot, candidate) {
12
+ if (candidate.length === 0 || candidate.length > 4096) return false;
13
+ if (hasLeadingDash(candidate)) return false;
14
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
15
+ const rel = relative(projectRoot, resolved);
16
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
17
+ }
18
+ function withinProject(p) {
19
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
20
+ }
21
+
22
+ // src/code-metrics/index.ts
4
23
  var API_VERSION = "^0.1.10";
5
24
  var state = {
6
25
  measureCount: 0,
@@ -23,16 +42,6 @@ function readConfig(raw) {
23
42
  maxFiles: typeof r["maxFiles"] === "number" && r["maxFiles"] >= 1 && r["maxFiles"] <= 500 ? r["maxFiles"] : DEFAULTS.maxFiles
24
43
  };
25
44
  }
26
- function withinProject(p) {
27
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
28
- const root = process.cwd();
29
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
30
- const rel = relative(root, resolved);
31
- if (rel === "" || rel === ".") return true;
32
- if (rel.startsWith("..")) return false;
33
- if (isAbsolute(rel)) return false;
34
- return true;
35
- }
36
45
  function normalizeExtensions(exts) {
37
46
  return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
38
47
  }
@@ -57,7 +66,7 @@ function collectSourceFiles(root, exts) {
57
66
  }
58
67
  for (const entry of entries) {
59
68
  if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
60
- const full = resolve(dir, entry);
69
+ const full = resolve2(dir, entry);
61
70
  let st;
62
71
  try {
63
72
  st = statSync(full);
@@ -78,40 +87,38 @@ function toPosix(p) {
78
87
  return p.replace(/\\/g, "/");
79
88
  }
80
89
  function relativePath(p) {
81
- return toPosix(relative(process.cwd(), p));
90
+ return toPosix(relative2(process.cwd(), p));
82
91
  }
83
92
  function countFunctions(content) {
84
93
  let count = 0;
85
94
  const declRe = /\bfunction\s+[A-Za-z_$][A-Za-z0-9_$]*\s*\(/g;
86
95
  const methodRe = /\b[A-Za-z_$][A-Za-z0-9_$]*\s*\([^)]*\)\s*\{/g;
87
96
  const arrowRe = /=>\s*(?:\{|\(|[^\s;{}])/g;
88
- let m;
89
97
  declRe.lastIndex = 0;
90
- while ((m = declRe.exec(content)) !== null) count++;
98
+ for (const _match of content.matchAll(declRe)) count++;
91
99
  methodRe.lastIndex = 0;
92
- while ((m = methodRe.exec(content)) !== null) {
93
- const prefix = content.slice(0, m.index).trimEnd();
100
+ for (const match of content.matchAll(methodRe)) {
101
+ const prefix = content.slice(0, match.index).trimEnd();
94
102
  if (/\bfunction\s*$/.test(prefix)) continue;
95
103
  count++;
96
104
  }
97
105
  arrowRe.lastIndex = 0;
98
- while ((m = arrowRe.exec(content)) !== null) count++;
106
+ for (const _match of content.matchAll(arrowRe)) count++;
99
107
  return count;
100
108
  }
101
109
  function countComplexity(content) {
102
110
  const controlRe = /\b(if|else\s+if|for|while|switch|catch)\b/g;
103
111
  const operatorRe = /[?&|]/g;
104
112
  let complexity = 0;
105
- let m;
106
113
  controlRe.lastIndex = 0;
107
- while ((m = controlRe.exec(content)) !== null) complexity++;
114
+ for (const _match of content.matchAll(controlRe)) complexity++;
108
115
  operatorRe.lastIndex = 0;
109
- while ((m = operatorRe.exec(content)) !== null) {
110
- const ch = m[0];
116
+ for (const match of content.matchAll(operatorRe)) {
117
+ const ch = match[0];
111
118
  if (ch === "?") {
112
119
  complexity++;
113
120
  } else {
114
- const next = content[m.index + 1];
121
+ const next = content[match.index + 1];
115
122
  if (next === ch) complexity++;
116
123
  }
117
124
  }
@@ -157,7 +164,7 @@ function analyzeFile(filePath, content) {
157
164
  }
158
165
  function measurePath(rawPath, cfg) {
159
166
  const root = process.cwd();
160
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
167
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
161
168
  const exts = normalizeExtensions(cfg.extensions);
162
169
  const allFiles = collectSourceFiles(resolved, exts);
163
170
  const files = allFiles.slice(0, cfg.maxFiles);
@@ -220,7 +227,7 @@ var plugin = {
220
227
  const exts = normalizeExtensions(cfg.extensions);
221
228
  if (!matchesExtension(sourcePath, exts)) return;
222
229
  state.hookInvocationCount += 1;
223
- const resolved = resolve(process.cwd(), sourcePath);
230
+ const resolved = resolve2(process.cwd(), sourcePath);
224
231
  let content;
225
232
  try {
226
233
  content = readFileSync(resolved, "utf-8");
@@ -234,7 +241,7 @@ var plugin = {
234
241
  \u{1F4CA} code-metrics: ${relativePath(resolved)} \u2014 ${metrics.lines} lines (${metrics.codeLines} code, ${metrics.commentLines} comments, ${metrics.blankLines} blank), ${metrics.functionCount} function(s), complexity ${metrics.complexity}.`
235
242
  };
236
243
  };
237
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
244
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
238
245
  api.tools.register({
239
246
  name: "measure_code_metrics",
240
247
  description: "Measure lines, comments, blank lines, function count, and cyclomatic-complexity-like score for a source file or directory.",
@@ -264,7 +271,7 @@ var plugin = {
264
271
  state.fileCount += result.files.length;
265
272
  return {
266
273
  ok: true,
267
- path: relativePath(resolve(process.cwd(), rawPath)),
274
+ path: relativePath(resolve2(process.cwd(), rawPath)),
268
275
  files: result.files,
269
276
  totalFiles: result.totalFiles,
270
277
  capped: result.totalFiles > cfg.maxFiles
@@ -130,7 +130,7 @@ var plugin = {
130
130
  version: "0.1.0",
131
131
  description: "PreToolUse hook that validates conventional-commit format before git_autocommit or bash git commit runs",
132
132
  apiVersion: API_VERSION,
133
- capabilities: { tools: true, hooks: true },
133
+ capabilities: { tools: true, hooks: true, llm: true },
134
134
  defaultConfig: { ...DEFAULTS },
135
135
  configSchema: {
136
136
  type: "object",
@@ -282,7 +282,7 @@ var plugin = {
282
282
  \u2026 and ${problems.length - 10} more` : "") + "\nFix these before moving on \u2014 downstream tools will fail on this file."
283
283
  };
284
284
  };
285
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
285
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
286
286
  api.tools.register({
287
287
  name: "config_validator_status",
288
288
  description: "Reports config-validator state: watched extensions and counters (files checked, problems found).",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cron/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAoE/C,QAAA,MAAM,MAAM,EAAE,MA2Pb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cron/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA+F/C,QAAA,MAAM,MAAM,EAAE,MAqRb,CAAC;eAEa,MAAM"}
package/dist/cron.js CHANGED
@@ -11,6 +11,19 @@ function formatNextRun(intervalMs) {
11
11
  const ms = Number.isNaN(intervalMs) || intervalMs <= 0 ? 6e4 : intervalMs;
12
12
  return new Date(Date.now() + ms).toISOString();
13
13
  }
14
+ function buildSnapshot(s, maxConcurrent) {
15
+ const jobs = Array.from(s.jobs.values()).map((j) => ({
16
+ name: j.name,
17
+ intervalMs: j.intervalMs,
18
+ action: j.action,
19
+ enabled: j.enabled,
20
+ lastRun: j.lastRun,
21
+ nextRun: j.nextRun,
22
+ runCount: j.runCount,
23
+ overdue: new Date(j.nextRun).getTime() < Date.now()
24
+ }));
25
+ return { count: jobs.length, maxConcurrent, jobs };
26
+ }
14
27
  function clearCronResources() {
15
28
  for (const timer of state.timers.values()) {
16
29
  clearTimeout(timer);
@@ -64,6 +77,7 @@ var plugin = {
64
77
  runCount: job.runCount,
65
78
  ts: (/* @__PURE__ */ new Date()).toISOString()
66
79
  });
80
+ api.emitCustom("cron:state_snapshot", buildSnapshot(state, maxConcurrent));
67
81
  api.metrics.counter("cron_job_fired", 1, { job: name });
68
82
  api.metrics.histogram("cron_job_interval_ms", job.intervalMs, { job: name });
69
83
  scheduleNextRun(name);
@@ -165,6 +179,7 @@ var plugin = {
165
179
  state.jobs.set(name, job);
166
180
  scheduleNextRun(name);
167
181
  api.metrics.gauge("cron_active_jobs", state.jobs.size);
182
+ api.emitCustom("cron:state_snapshot", buildSnapshot(state, maxConcurrent));
168
183
  return {
169
184
  ok: true,
170
185
  name,
@@ -220,6 +235,7 @@ var plugin = {
220
235
  }
221
236
  cancelJob(name);
222
237
  api.metrics.gauge("cron_active_jobs", state.jobs.size);
238
+ api.emitCustom("cron:state_snapshot", buildSnapshot(state, maxConcurrent));
223
239
  return {
224
240
  ok: true,
225
241
  name,
@@ -232,6 +248,19 @@ var plugin = {
232
248
  teardown(api) {
233
249
  clearCronResources();
234
250
  api.log.info("cron plugin unloaded");
251
+ },
252
+ async health() {
253
+ const jobs = Array.from(state.jobs.values());
254
+ const overdue = jobs.filter(
255
+ (job) => job.enabled && new Date(job.nextRun).getTime() < Date.now()
256
+ ).length;
257
+ return {
258
+ ok: overdue === 0,
259
+ message: overdue > 0 ? `cron: ${overdue} overdue job(s) out of ${jobs.length}` : `cron: ${jobs.length} active job(s)`,
260
+ activeJobs: jobs.length,
261
+ overdueJobs: overdue,
262
+ totalRuns: jobs.reduce((sum, job) => sum + job.runCount, 0)
263
+ };
235
264
  }
236
265
  };
237
266
  var cron_default = plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dead-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA2Q/C,QAAA,MAAM,MAAM,EAAE,MAkOb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dead-code-detector/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAiQ/C,QAAA,MAAM,MAAM,EAAE,MAkOb,CAAC;eAEa,MAAM"}