@wrongstack/plugins 0.286.0 → 0.289.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 (74) 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 +67 -55
  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 +20 -12
  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 +63 -50
  11. package/dist/commit-validator.js +1 -1
  12. package/dist/config-validator.js +1 -1
  13. package/dist/cost-tracker/index.d.ts.map +1 -1
  14. package/dist/cost-tracker.js +4 -2
  15. package/dist/cron/index.d.ts.map +1 -1
  16. package/dist/cron.js +29 -0
  17. package/dist/dead-code-detector/index.d.ts.map +1 -1
  18. package/dist/dead-code-detector.js +28 -19
  19. package/dist/dep-guard.js +1 -1
  20. package/dist/diff-summary/index.d.ts.map +1 -1
  21. package/dist/diff-summary.js +19 -11
  22. package/dist/doc-sync-guard/index.d.ts.map +1 -1
  23. package/dist/doc-sync-guard.js +25 -16
  24. package/dist/duplicate-code-detector/index.d.ts.map +1 -1
  25. package/dist/duplicate-code-detector.js +70 -57
  26. package/dist/error-lens.js +1 -1
  27. package/dist/feature-flag-tracker/index.d.ts.map +1 -1
  28. package/dist/feature-flag-tracker.js +69 -56
  29. package/dist/file-watcher/index.d.ts.map +1 -1
  30. package/dist/file-watcher.js +21 -4
  31. package/dist/format-on-save/index.d.ts.map +1 -1
  32. package/dist/format-on-save.js +19 -11
  33. package/dist/git-autocommit.js +1 -1
  34. package/dist/import-organizer/index.d.ts.map +1 -1
  35. package/dist/import-organizer.js +23 -14
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.js +1167 -1048
  38. package/dist/interface-contract-guard/index.d.ts.map +1 -1
  39. package/dist/interface-contract-guard.js +64 -51
  40. package/dist/lint-gate/index.d.ts.map +1 -1
  41. package/dist/lint-gate.js +61 -30
  42. package/dist/migration-planner/index.d.ts +12 -2
  43. package/dist/migration-planner/index.d.ts.map +1 -1
  44. package/dist/migration-planner.js +210 -28
  45. package/dist/pr-drafter.js +1 -1
  46. package/dist/prompt-firewall/index.d.ts +14 -6
  47. package/dist/prompt-firewall/index.d.ts.map +1 -1
  48. package/dist/prompt-firewall.js +7 -6
  49. package/dist/refactor-suggester/index.d.ts.map +1 -1
  50. package/dist/refactor-suggester.js +69 -56
  51. package/dist/release-notes-generator/index.d.ts +3 -1
  52. package/dist/release-notes-generator/index.d.ts.map +1 -1
  53. package/dist/release-notes-generator.js +152 -12
  54. package/dist/runtime/index.d.ts +36 -0
  55. package/dist/runtime/index.d.ts.map +1 -1
  56. package/dist/runtime/llm.d.ts +33 -0
  57. package/dist/runtime/llm.d.ts.map +1 -0
  58. package/dist/runtime.js +119 -25
  59. package/dist/schema-evolution-guard/index.d.ts.map +1 -1
  60. package/dist/schema-evolution-guard.js +22 -13
  61. package/dist/secret-scanner/index.d.ts.map +1 -1
  62. package/dist/secret-scanner.js +17 -6
  63. package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
  64. package/dist/security-hotspot-scanner.js +24 -15
  65. package/dist/session-recap.js +1 -1
  66. package/dist/spec-linker.js +1167 -1048
  67. package/dist/test-generator/index.d.ts +5 -3
  68. package/dist/test-generator/index.d.ts.map +1 -1
  69. package/dist/test-generator.js +169 -26
  70. package/dist/test-runner-gate/index.d.ts.map +1 -1
  71. package/dist/test-runner-gate.js +65 -43
  72. package/dist/type-gate/index.d.ts.map +1 -1
  73. package/dist/type-gate.js +23 -35
  74. 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;AAwSD,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;AAoPD,QAAA,MAAM,MAAM,EAAE,MA0Nb,CAAC;eAEa,MAAM"}
@@ -1,61 +1,37 @@
1
1
  // src/accessibility-auditor/index.ts
2
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { isAbsolute, relative, resolve } from "node:path";
4
- var API_VERSION = "^0.1.10";
5
- var state = {
6
- auditCount: 0,
7
- fileCount: 0,
8
- findingCount: 0,
9
- hookInvocationCount: 0,
10
- lastResult: null,
11
- hookUnregister: null
12
- };
13
- var DEFAULTS = {
14
- enabled: false,
15
- includeExtensions: [".tsx", ".jsx", ".html", ".vue"],
16
- maxFindings: 50,
17
- severity: "warn",
18
- onWriteEdit: true
19
- };
20
- function readConfig(raw) {
21
- if (!raw || typeof raw !== "object") return { ...DEFAULTS };
22
- const r = raw;
23
- return {
24
- enabled: r["enabled"] !== false,
25
- includeExtensions: Array.isArray(r["includeExtensions"]) ? r["includeExtensions"].filter((x) => typeof x === "string") : DEFAULTS.includeExtensions,
26
- maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings,
27
- severity: r["severity"] === "block" ? "block" : DEFAULTS.severity,
28
- onWriteEdit: r["onWriteEdit"] !== false
29
- };
30
- }
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;
2
+ import { readFileSync } from "node:fs";
3
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { existsSync, readdirSync, statSync } from "node:fs";
7
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
8
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
9
+ function hasLeadingDash(arg) {
10
+ return arg.length > 0 && arg.startsWith("-");
40
11
  }
41
- function normalizeExtensions(exts) {
42
- return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
12
+ function withinProjectPath(projectRoot, candidate) {
13
+ if (candidate.length === 0 || candidate.length > 4096) return false;
14
+ if (hasLeadingDash(candidate)) return false;
15
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
16
+ const rel = relative(projectRoot, resolved);
17
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
43
18
  }
44
- function matchesExtension(p, exts) {
45
- const dot = p.lastIndexOf(".");
46
- if (dot === -1) return false;
47
- return exts.includes(p.slice(dot).toLowerCase());
19
+ function withinProject(p) {
20
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
48
21
  }
49
- function collectUiFiles(root, exts) {
22
+ var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
23
+ function collectSourceFiles(root, opts) {
50
24
  const files = [];
51
25
  if (!existsSync(root)) return files;
52
26
  const s = statSync(root);
53
27
  if (s.isFile()) {
54
- if (matchesExtension(root, exts)) files.push(root);
28
+ if (matchesExtension(root, opts.extensions)) files.push(root);
55
29
  return files;
56
30
  }
57
31
  if (!s.isDirectory()) return files;
58
- function walk(dir) {
32
+ const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
33
+ function walk(dir, depth) {
34
+ if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
59
35
  let entries;
60
36
  try {
61
37
  entries = readdirSync(dir);
@@ -63,6 +39,7 @@ function collectUiFiles(root, exts) {
63
39
  return;
64
40
  }
65
41
  for (const entry of entries) {
42
+ if (exclude.includes(entry)) continue;
66
43
  const full = resolve(dir, entry);
67
44
  let st;
68
45
  try {
@@ -71,15 +48,50 @@ function collectUiFiles(root, exts) {
71
48
  continue;
72
49
  }
73
50
  if (st.isDirectory()) {
74
- walk(full);
75
- } else if (st.isFile() && matchesExtension(full, exts)) {
51
+ walk(full, depth + 1);
52
+ } else if (st.isFile() && matchesExtension(full, opts.extensions)) {
76
53
  files.push(full);
77
54
  }
78
55
  }
79
56
  }
80
- walk(root);
57
+ walk(root, 0);
81
58
  return files;
82
59
  }
60
+ function matchesExtension(p, exts) {
61
+ return exts.includes(extname(p).toLowerCase());
62
+ }
63
+
64
+ // src/accessibility-auditor/index.ts
65
+ var API_VERSION = "^0.1.10";
66
+ var state = {
67
+ auditCount: 0,
68
+ fileCount: 0,
69
+ findingCount: 0,
70
+ hookInvocationCount: 0,
71
+ lastResult: null,
72
+ hookUnregister: null
73
+ };
74
+ var DEFAULTS = {
75
+ enabled: false,
76
+ includeExtensions: [".tsx", ".jsx", ".html", ".vue"],
77
+ maxFindings: 50,
78
+ severity: "warn",
79
+ onWriteEdit: true
80
+ };
81
+ function readConfig(raw) {
82
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
83
+ const r = raw;
84
+ return {
85
+ enabled: r["enabled"] !== false,
86
+ includeExtensions: Array.isArray(r["includeExtensions"]) ? r["includeExtensions"].filter((x) => typeof x === "string") : DEFAULTS.includeExtensions,
87
+ maxFindings: typeof r["maxFindings"] === "number" && r["maxFindings"] >= 1 && r["maxFindings"] <= 500 ? r["maxFindings"] : DEFAULTS.maxFindings,
88
+ severity: r["severity"] === "block" ? "block" : DEFAULTS.severity,
89
+ onWriteEdit: r["onWriteEdit"] !== false
90
+ };
91
+ }
92
+ function normalizeExtensions(exts) {
93
+ return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
94
+ }
83
95
  var TAG_IMG = /<img\b[^>]*>/gi;
84
96
  var TAG_INPUT = /<input\b[^>]*\/?>/gi;
85
97
  var TAG_BUTTON = /<button\b[^>]*>([\s\S]*?)<\/button>/gi;
@@ -109,7 +121,7 @@ function auditFile(filePath, projectRoot) {
109
121
  const idsByValue = /* @__PURE__ */ new Map();
110
122
  function add(line, rule, severity, message) {
111
123
  findings.push({
112
- file: relative(projectRoot, filePath),
124
+ file: relative2(projectRoot, filePath),
113
125
  line,
114
126
  rule,
115
127
  severity,
@@ -188,9 +200,9 @@ function auditFile(filePath, projectRoot) {
188
200
  }
189
201
  function auditPath(rawPath, cfg) {
190
202
  const root = process.cwd();
191
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
203
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
192
204
  const exts = normalizeExtensions(cfg.includeExtensions);
193
- const files = collectUiFiles(resolved, exts);
205
+ const files = collectSourceFiles(resolved, { extensions: exts });
194
206
  const findings = [];
195
207
  for (const file of files) {
196
208
  const fileFindings = auditFile(file, root);
@@ -198,7 +210,7 @@ function auditPath(rawPath, cfg) {
198
210
  if (findings.length >= cfg.maxFindings) break;
199
211
  }
200
212
  return {
201
- path: relative(root, resolved),
213
+ path: relative2(root, resolved),
202
214
  findings: findings.slice(0, cfg.maxFindings),
203
215
  fileCount: files.length
204
216
  };
@@ -289,7 +301,7 @@ var plugin = {
289
301
  }
290
302
  return { additionalContext: summary };
291
303
  };
292
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
304
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
293
305
  api.tools.register({
294
306
  name: "a11y_audit",
295
307
  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;AAgL/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, extname, 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() : "";
@@ -201,7 +209,7 @@ var plugin = {
201
209
  ${lines.join("\n")}${more}`;
202
210
  return { additionalContext: context };
203
211
  };
204
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
212
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
205
213
  api.tools.register({
206
214
  name: "i18n_extract",
207
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;AA4P/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;AA0M/C,QAAA,MAAM,MAAM,EAAE,MAgMb,CAAC;eAEa,MAAM"}
@@ -1,54 +1,37 @@
1
1
  // src/code-metrics/index.ts
2
- import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
- import { extname, isAbsolute, relative, resolve } from "node:path";
4
- var API_VERSION = "^0.1.10";
5
- var state = {
6
- measureCount: 0,
7
- fileCount: 0,
8
- hookInvocationCount: 0,
9
- errorCount: 0,
10
- hookUnregister: null
11
- };
12
- var DEFAULTS = {
13
- enabled: false,
14
- extensions: [".ts", ".tsx", ".js", ".jsx"],
15
- maxFiles: 50
16
- };
17
- function readConfig(raw) {
18
- if (!raw || typeof raw !== "object") return { ...DEFAULTS };
19
- const r = raw;
20
- return {
21
- enabled: r["enabled"] !== false,
22
- extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
23
- maxFiles: typeof r["maxFiles"] === "number" && r["maxFiles"] >= 1 && r["maxFiles"] <= 500 ? r["maxFiles"] : DEFAULTS.maxFiles
24
- };
25
- }
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;
2
+ import { readFileSync } from "node:fs";
3
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
4
+
5
+ // src/runtime/index.ts
6
+ import { existsSync, readdirSync, statSync } from "node:fs";
7
+ import { basename, extname, isAbsolute, relative, resolve } from "node:path";
8
+ var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
9
+ function hasLeadingDash(arg) {
10
+ return arg.length > 0 && arg.startsWith("-");
35
11
  }
36
- function normalizeExtensions(exts) {
37
- return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
12
+ function withinProjectPath(projectRoot, candidate) {
13
+ if (candidate.length === 0 || candidate.length > 4096) return false;
14
+ if (hasLeadingDash(candidate)) return false;
15
+ const resolved = isAbsolute(candidate) ? resolve(candidate) : resolve(projectRoot, candidate);
16
+ const rel = relative(projectRoot, resolved);
17
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
38
18
  }
39
- function matchesExtension(p, exts) {
40
- return exts.includes(extname(p).toLowerCase());
19
+ function withinProject(p) {
20
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
41
21
  }
42
- function collectSourceFiles(root, exts) {
22
+ var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
23
+ function collectSourceFiles(root, opts) {
43
24
  const files = [];
44
25
  if (!existsSync(root)) return files;
45
26
  const s = statSync(root);
46
27
  if (s.isFile()) {
47
- if (matchesExtension(root, exts)) files.push(root);
28
+ if (matchesExtension(root, opts.extensions)) files.push(root);
48
29
  return files;
49
30
  }
50
31
  if (!s.isDirectory()) return files;
51
- function walk(dir) {
32
+ const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
33
+ function walk(dir, depth) {
34
+ if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
52
35
  let entries;
53
36
  try {
54
37
  entries = readdirSync(dir);
@@ -56,7 +39,7 @@ function collectSourceFiles(root, exts) {
56
39
  return;
57
40
  }
58
41
  for (const entry of entries) {
59
- if (entry === "node_modules" || entry === "dist" || entry === ".git" || entry === "coverage") continue;
42
+ if (exclude.includes(entry)) continue;
60
43
  const full = resolve(dir, entry);
61
44
  let st;
62
45
  try {
@@ -65,20 +48,50 @@ function collectSourceFiles(root, exts) {
65
48
  continue;
66
49
  }
67
50
  if (st.isDirectory()) {
68
- walk(full);
69
- } else if (st.isFile() && matchesExtension(full, exts)) {
51
+ walk(full, depth + 1);
52
+ } else if (st.isFile() && matchesExtension(full, opts.extensions)) {
70
53
  files.push(full);
71
54
  }
72
55
  }
73
56
  }
74
- walk(root);
57
+ walk(root, 0);
75
58
  return files;
76
59
  }
60
+ function matchesExtension(p, exts) {
61
+ return exts.includes(extname(p).toLowerCase());
62
+ }
63
+
64
+ // src/code-metrics/index.ts
65
+ var API_VERSION = "^0.1.10";
66
+ var state = {
67
+ measureCount: 0,
68
+ fileCount: 0,
69
+ hookInvocationCount: 0,
70
+ errorCount: 0,
71
+ hookUnregister: null
72
+ };
73
+ var DEFAULTS = {
74
+ enabled: false,
75
+ extensions: [".ts", ".tsx", ".js", ".jsx"],
76
+ maxFiles: 50
77
+ };
78
+ function readConfig(raw) {
79
+ if (!raw || typeof raw !== "object") return { ...DEFAULTS };
80
+ const r = raw;
81
+ return {
82
+ enabled: r["enabled"] !== false,
83
+ extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS.extensions,
84
+ maxFiles: typeof r["maxFiles"] === "number" && r["maxFiles"] >= 1 && r["maxFiles"] <= 500 ? r["maxFiles"] : DEFAULTS.maxFiles
85
+ };
86
+ }
87
+ function normalizeExtensions(exts) {
88
+ return exts.map((e) => e.startsWith(".") ? e.toLowerCase() : `.${e.toLowerCase()}`);
89
+ }
77
90
  function toPosix(p) {
78
91
  return p.replace(/\\/g, "/");
79
92
  }
80
93
  function relativePath(p) {
81
- return toPosix(relative(process.cwd(), p));
94
+ return toPosix(relative2(process.cwd(), p));
82
95
  }
83
96
  function countFunctions(content) {
84
97
  let count = 0;
@@ -155,9 +168,9 @@ function analyzeFile(filePath, content) {
155
168
  }
156
169
  function measurePath(rawPath, cfg) {
157
170
  const root = process.cwd();
158
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
171
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
159
172
  const exts = normalizeExtensions(cfg.extensions);
160
- const allFiles = collectSourceFiles(resolved, exts);
173
+ const allFiles = collectSourceFiles(resolved, { extensions: exts });
161
174
  const files = allFiles.slice(0, cfg.maxFiles);
162
175
  const metrics = [];
163
176
  for (const p of files) {
@@ -218,7 +231,7 @@ var plugin = {
218
231
  const exts = normalizeExtensions(cfg.extensions);
219
232
  if (!matchesExtension(sourcePath, exts)) return;
220
233
  state.hookInvocationCount += 1;
221
- const resolved = resolve(process.cwd(), sourcePath);
234
+ const resolved = resolve2(process.cwd(), sourcePath);
222
235
  let content;
223
236
  try {
224
237
  content = readFileSync(resolved, "utf-8");
@@ -232,7 +245,7 @@ var plugin = {
232
245
  \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}.`
233
246
  };
234
247
  };
235
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
248
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
236
249
  api.tools.register({
237
250
  name: "measure_code_metrics",
238
251
  description: "Measure lines, comments, blank lines, function count, and cyclomatic-complexity-like score for a source file or directory.",
@@ -262,7 +275,7 @@ var plugin = {
262
275
  state.fileCount += result.files.length;
263
276
  return {
264
277
  ok: true,
265
- path: relativePath(resolve(process.cwd(), rawPath)),
278
+ path: relativePath(resolve2(process.cwd(), rawPath)),
266
279
  files: result.files,
267
280
  totalFiles: result.totalFiles,
268
281
  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/cost-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK/C;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AA8KD,QAAA,MAAM,MAAM,EAAE,MA8Zb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cost-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAK/C;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;CAChB;AAyKD,QAAA,MAAM,MAAM,EAAE,MA8Zb,CAAC;eAEa,MAAM"}
@@ -19,11 +19,13 @@ var digestCounters = {
19
19
  mailboxDigestErrors: 0
20
20
  };
21
21
  function readCostTrackerConfig(raw) {
22
+ const digestEveryN = raw?.["mailboxDigestEveryN"];
23
+ const digestTo = raw?.["mailboxDigestTo"];
22
24
  return {
23
25
  budgetLimit: typeof raw?.["budgetLimit"] === "number" ? raw["budgetLimit"] : 0,
24
26
  warningThreshold: typeof raw?.["warningThreshold"] === "number" ? raw["warningThreshold"] : 80,
25
- mailboxDigestEveryN: typeof raw?.["mailboxDigestEveryN"] === "number" && raw?.["mailboxDigestEveryN"] >= 0 ? Math.floor(raw?.["mailboxDigestEveryN"]) : 0,
26
- mailboxDigestTo: typeof raw?.["mailboxDigestTo"] === "string" && (raw?.["mailboxDigestTo"]).length > 0 ? raw?.["mailboxDigestTo"] : "cost-tracker"
27
+ mailboxDigestEveryN: typeof digestEveryN === "number" && digestEveryN >= 0 ? Math.floor(digestEveryN) : 0,
28
+ mailboxDigestTo: typeof digestTo === "string" && digestTo.length > 0 ? digestTo : "cost-tracker"
27
29
  };
28
30
  }
29
31
  function estimateCost(model, promptTokens, completionTokens) {
@@ -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;