@wrongstack/plugins 0.286.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 (65) 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 +25 -16
  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 +26 -17
  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 +27 -18
  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 +26 -17
  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 +961 -699
  36. package/dist/interface-contract-guard/index.d.ts.map +1 -1
  37. package/dist/interface-contract-guard.js +26 -17
  38. package/dist/migration-planner/index.d.ts +12 -2
  39. package/dist/migration-planner/index.d.ts.map +1 -1
  40. package/dist/migration-planner.js +210 -28
  41. package/dist/pr-drafter.js +1 -1
  42. package/dist/refactor-suggester/index.d.ts.map +1 -1
  43. package/dist/refactor-suggester.js +26 -17
  44. package/dist/release-notes-generator/index.d.ts +3 -1
  45. package/dist/release-notes-generator/index.d.ts.map +1 -1
  46. package/dist/release-notes-generator.js +152 -12
  47. package/dist/runtime/index.d.ts +12 -0
  48. package/dist/runtime/index.d.ts.map +1 -1
  49. package/dist/runtime/llm.d.ts +33 -0
  50. package/dist/runtime/llm.d.ts.map +1 -0
  51. package/dist/runtime.js +56 -1
  52. package/dist/schema-evolution-guard/index.d.ts.map +1 -1
  53. package/dist/schema-evolution-guard.js +21 -12
  54. package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
  55. package/dist/security-hotspot-scanner.js +24 -15
  56. package/dist/session-recap.js +1 -1
  57. package/dist/spec-linker.js +961 -699
  58. package/dist/test-generator/index.d.ts +5 -3
  59. package/dist/test-generator/index.d.ts.map +1 -1
  60. package/dist/test-generator.js +169 -26
  61. package/dist/test-runner-gate/index.d.ts.map +1 -1
  62. package/dist/test-runner-gate.js +65 -43
  63. package/dist/type-gate/index.d.ts.map +1 -1
  64. package/dist/type-gate.js +4 -12
  65. 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;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,
@@ -188,7 +197,7 @@ function auditFile(filePath, projectRoot) {
188
197
  }
189
198
  function auditPath(rawPath, cfg) {
190
199
  const root = process.cwd();
191
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
200
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
192
201
  const exts = normalizeExtensions(cfg.includeExtensions);
193
202
  const files = collectUiFiles(resolved, exts);
194
203
  const findings = [];
@@ -198,7 +207,7 @@ function auditPath(rawPath, cfg) {
198
207
  if (findings.length >= cfg.maxFindings) break;
199
208
  }
200
209
  return {
201
- path: relative(root, resolved),
210
+ path: relative2(root, resolved),
202
211
  findings: findings.slice(0, cfg.maxFindings),
203
212
  fileCount: files.length
204
213
  };
@@ -289,7 +298,7 @@ var plugin = {
289
298
  }
290
299
  return { additionalContext: summary };
291
300
  };
292
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
301
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
293
302
  api.tools.register({
294
303
  name: "a11y_audit",
295
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;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, 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;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,7 +87,7 @@ 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;
@@ -155,7 +164,7 @@ function analyzeFile(filePath, content) {
155
164
  }
156
165
  function measurePath(rawPath, cfg) {
157
166
  const root = process.cwd();
158
- const resolved = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
167
+ const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
159
168
  const exts = normalizeExtensions(cfg.extensions);
160
169
  const allFiles = collectSourceFiles(resolved, exts);
161
170
  const files = allFiles.slice(0, cfg.maxFiles);
@@ -218,7 +227,7 @@ var plugin = {
218
227
  const exts = normalizeExtensions(cfg.extensions);
219
228
  if (!matchesExtension(sourcePath, exts)) return;
220
229
  state.hookInvocationCount += 1;
221
- const resolved = resolve(process.cwd(), sourcePath);
230
+ const resolved = resolve2(process.cwd(), sourcePath);
222
231
  let content;
223
232
  try {
224
233
  content = readFileSync(resolved, "utf-8");
@@ -232,7 +241,7 @@ var plugin = {
232
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}.`
233
242
  };
234
243
  };
235
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
244
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
236
245
  api.tools.register({
237
246
  name: "measure_code_metrics",
238
247
  description: "Measure lines, comments, blank lines, function count, and cyclomatic-complexity-like score for a source file or directory.",
@@ -262,7 +271,7 @@ var plugin = {
262
271
  state.fileCount += result.files.length;
263
272
  return {
264
273
  ok: true,
265
- path: relativePath(resolve(process.cwd(), rawPath)),
274
+ path: relativePath(resolve2(process.cwd(), rawPath)),
266
275
  files: result.files,
267
276
  totalFiles: result.totalFiles,
268
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;AAyQ/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"}
@@ -1,6 +1,25 @@
1
1
  // src/dead-code-detector/index.ts
2
2
  import { readdirSync, readFileSync, statSync } from "node:fs";
3
- import { extname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { extname, join, 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/dead-code-detector/index.ts
4
23
  var API_VERSION = "^0.1.10";
5
24
  var state = {
6
25
  scanCount: 0,
@@ -51,7 +70,7 @@ function gatherFiles(root, depth, cfg) {
51
70
  }
52
71
  }
53
72
  }
54
- walk(resolve(root), depth);
73
+ walk(resolve2(root), depth);
55
74
  return files;
56
75
  }
57
76
  function stripNoise(content) {
@@ -134,22 +153,12 @@ function scan(root, depth, cfg) {
134
153
  }
135
154
  return { findings: findUnusedSymbols(files), scannedFiles: files.length };
136
155
  }
137
- function withinProject(p) {
138
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
139
- const root = process.cwd();
140
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
141
- const rel = relative(root, resolved);
142
- if (rel === "" || rel === ".") return true;
143
- if (rel.startsWith("..")) return false;
144
- if (isAbsolute(rel)) return false;
145
- return true;
146
- }
147
156
  function resolveScanRoot(rawPath) {
148
- const resolved = resolve(process.cwd(), rawPath);
157
+ const resolved = resolve2(process.cwd(), rawPath);
149
158
  try {
150
159
  const stats = statSync(resolved);
151
160
  if (!stats.isDirectory()) {
152
- return resolve(resolved, "..");
161
+ return resolve2(resolved, "..");
153
162
  }
154
163
  } catch {
155
164
  }
@@ -159,7 +168,7 @@ function toPosix(p) {
159
168
  return p.replace(/\\/g, "/");
160
169
  }
161
170
  function relativePath(p) {
162
- return toPosix(relative(process.cwd(), p));
171
+ return toPosix(relative2(process.cwd(), p));
163
172
  }
164
173
  var plugin = {
165
174
  name: "dead-code-detector",
@@ -230,8 +239,8 @@ var plugin = {
230
239
  state.errorCount += 1;
231
240
  return;
232
241
  }
233
- const changedFile = resolve(process.cwd(), sourcePath);
234
- const relevant = result.findings.filter((f) => resolve(f.file) === changedFile);
242
+ const changedFile = resolve2(process.cwd(), sourcePath);
243
+ const relevant = result.findings.filter((f) => resolve2(f.file) === changedFile);
235
244
  if (relevant.length === 0) {
236
245
  state.missCount += 1;
237
246
  return;
@@ -247,7 +256,7 @@ ${list}
247
256
  Consider removing the export if it is not part of the public API.`;
248
257
  return { additionalContext: message };
249
258
  };
250
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
259
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
251
260
  api.tools.register({
252
261
  name: "dead_code_scan",
253
262
  description: "Scan TypeScript/JavaScript source files for exported symbols that appear unused anywhere in the project.",
package/dist/dep-guard.js CHANGED
@@ -138,7 +138,7 @@ var plugin = {
138
138
  version: "0.1.0",
139
139
  description: "Supervises dependency installs: blocks deny-listed packages, flags typosquat lookalikes, and optionally warns on unpinned versions",
140
140
  apiVersion: "^0.1.10",
141
- capabilities: { tools: true, hooks: true },
141
+ capabilities: { tools: true, hooks: 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/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAgQ/C,QAAA,MAAM,MAAM,EAAE,MA4Pb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/diff-summary/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAwP/C,QAAA,MAAM,MAAM,EAAE,MA4Pb,CAAC;eAEa,MAAM"}
@@ -1,17 +1,25 @@
1
1
  // src/diff-summary/index.ts
2
2
  import { execFileSync } from "node:child_process";
3
- import { isAbsolute, relative, resolve } from "node:path";
4
- var API_VERSION = "^0.1.10";
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
+ }
5
17
  function withinProject(p) {
6
- if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
7
- const root = process.cwd();
8
- const resolved = isAbsolute(p) ? resolve(p) : resolve(root, p);
9
- const rel = relative(root, resolved);
10
- if (rel === "" || rel === ".") return true;
11
- if (rel.startsWith("..")) return false;
12
- if (isAbsolute(rel)) return false;
13
- return true;
18
+ return withinProjectPath(process.cwd(), p) || relative(process.cwd(), p) === ".";
14
19
  }
20
+
21
+ // src/diff-summary/index.ts
22
+ var API_VERSION = "^0.1.10";
15
23
  var state = {
16
24
  invocationCount: 0,
17
25
  /** Times a diff was successfully generated and injected. */
@@ -243,7 +251,7 @@ var plugin = {
243
251
  additionalContext: header + summary
244
252
  };
245
253
  };
246
- state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook);
254
+ state.hookUnregister = api.registerHook("PostToolUse", "write|edit", hook, { background: true });
247
255
  api.tools.register({
248
256
  name: "diff_summary_status",
249
257
  description: "Reports diff-summary state: mode, maxLines, and per-session invocation/injected/fallback counters.",
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAkJ/C,QAAA,MAAM,MAAM,EAAE,MAuJb,CAAC;eAEa,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/doc-sync-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AA0I/C,QAAA,MAAM,MAAM,EAAE,MAuJb,CAAC;eAEa,MAAM"}